2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
5 * A set of classes that provide co-operative multitasking support. See
6 * wvtask.h for more information.
9 #include "wvautoconf.h"
11 # define alloca __builtin_alloca
15 # define alloca _alloca
23 # ifndef alloca /* predefined by HP cc +Olibcalls */
38 #include <sys/resource.h>
40 #ifdef HAVE_VALGRIND_MEMCHECK_H
41 #include <valgrind/memcheck.h>
42 // Compatibility for Valgrind 3.1 and previous
43 #ifndef VALGRIND_MAKE_MEM_DEFINED
44 #define VALGRIND_MAKE_MEM_DEFINED VALGRIND_MAKE_READABLE
47 #define VALGRIND_MAKE_MEM_DEFINED(x, y)
48 #define RUNNING_ON_VALGRIND 0
53 # define Dprintf(fmt, args...) fprintf(stderr, fmt, ##args)
55 # define Dprintf(fmt, args...)
58 int WvTask::taskcount
, WvTask::numtasks
, WvTask::numrunning
;
60 WvTaskMan
*WvTaskMan::singleton
;
61 int WvTaskMan::links
, WvTaskMan::magic_number
;
62 WvTaskList
WvTaskMan::all_tasks
, WvTaskMan::free_tasks
;
63 ucontext_t
WvTaskMan::stackmaster_task
, WvTaskMan::get_stack_return
,
65 WvTask
*WvTaskMan::current_task
, *WvTaskMan::stack_target
;
66 char *WvTaskMan::stacktop
;
68 static int context_return
;
71 static bool use_shared_stack()
73 return RUNNING_ON_VALGRIND
;
77 static void valgrind_fix(char *stacktop
)
79 #ifdef HAVE_VALGRIND_MEMCHECK_H
81 //printf("valgrind fix: %p-%p\n", &val, stacktop);
82 assert(stacktop
> &val
);
84 VALGRIND_MAKE_MEM_DEFINED(&val
, stacktop
- &val
);
88 WvTask::WvTask(WvTaskMan
&_man
, size_t _stacksize
) : man(_man
)
90 stacksize
= _stacksize
;
91 running
= recycled
= false;
97 magic_number
= WVTASK_MAGIC
;
100 man
.get_stack(*this, stacksize
);
102 man
.all_tasks
.append(this, false);
115 void WvTask::start(WvStringParm _name
, TaskFunc
*_func
, void *_userdata
)
120 userdata
= _userdata
;
126 void WvTask::recycle()
130 if (!running
&& !recycled
)
132 man
.free_tasks
.append(this, true);
138 WvTaskMan
*WvTaskMan::get()
141 singleton
= new WvTaskMan
;
147 void WvTaskMan::unlink()
158 static inline const char *Yes_No(bool val
)
160 return val
? "Yes": "No";
164 WvString
WvTaskMan::debugger_tasks_run_cb(WvStringParm cmd
, WvStringList
&args
,
165 WvStreamsDebugger::ResultCallback result_cb
, void *)
167 const char *format_str
= "%5s%s%7s%s%8s%s%6s%s%s";
169 result
.append(format_str
, "--TID", "-", "Running", "-", "Recycled", "-", "-StkSz", "-", "Name-----");
170 result_cb(cmd
, result
);
171 WvTaskList::Iter
i(all_tasks
);
172 for (i
.rewind(); i
.next(); )
175 result
.append(format_str
, i
->tid
, " ",
176 Yes_No(i
->running
), " ",
177 Yes_No(i
->recycled
), " ",
180 result_cb(cmd
, result
);
182 return WvString::null
;
186 WvTaskMan::WvTaskMan()
188 static bool first
= true;
192 WvStreamsDebugger::add_command("tasks", 0, debugger_tasks_run_cb
, 0);
197 magic_number
= -WVTASK_MAGIC
;
199 stacktop
= (char *)alloca(0);
202 assert(getcontext(&get_stack_return
) == 0);
203 if (context_return
== 0)
205 // initial setup - start the stackmaster() task (never returns!)
208 // if we get here, stackmaster did a longjmp back to us.
212 WvTaskMan::~WvTaskMan()
219 WvTask
*WvTaskMan::start(WvStringParm name
,
220 WvTask::TaskFunc
*func
, void *userdata
,
225 WvTaskList::Iter
i(free_tasks
);
226 for (i
.rewind(); i
.next(); )
228 if (i().stacksize
>= stacksize
)
231 i
.set_autofree(false);
234 t
->start(name
, func
, userdata
);
239 // if we get here, no matching task was found.
240 t
= new WvTask(*this, stacksize
);
241 t
->start(name
, func
, userdata
);
246 int WvTaskMan::run(WvTask
&task
, int val
)
248 assert(magic_number
== -WVTASK_MAGIC
);
249 assert(task
.magic_number
== WVTASK_MAGIC
);
250 assert(!task
.recycled
);
252 Dprintf("WvTaskMan: running task #%d with value %d (%s)\n",
253 task
.tid
, val
, (const char *)task
.name
);
255 if (&task
== current_task
)
256 return val
; // that's easy!
258 WvTask
*old_task
= current_task
;
259 current_task
= &task
;
263 state
= &toplevel
; // top-level call (not in an actual task yet)
265 state
= &old_task
->mystate
;
268 assert(getcontext(state
) == 0);
269 int newval
= context_return
;
272 // saved the state, now run the task.
273 context_return
= val
;
274 setcontext(&task
.mystate
);
279 // need to make state readable to see if we need to make more readable..
280 VALGRIND_MAKE_MEM_DEFINED(&state
, sizeof(state
));
281 // someone did yield() (if toplevel) or run() on our old task; done.
282 if (state
!= &toplevel
)
283 valgrind_fix(stacktop
);
284 current_task
= old_task
;
290 int WvTaskMan::yield(int val
)
293 return 0; // weird...
295 Dprintf("WvTaskMan: yielding from task #%d with value %d (%s)\n",
296 current_task
->tid
, val
, (const char *)current_task
->name
);
298 assert(current_task
->stack_magic
);
300 // if this fails, this task overflowed its stack. Make it bigger!
301 VALGRIND_MAKE_MEM_DEFINED(current_task
->stack_magic
,
302 sizeof(current_task
->stack_magic
));
303 assert(*current_task
->stack_magic
== WVTASK_MAGIC
);
306 if (use_shared_stack())
309 char *stackbottom
= (char *)(current_task
->stack_magic
+ 1);
310 for (stackleft
= 0; stackleft
< current_task
->stacksize
; stackleft
++)
312 if (stackbottom
[stackleft
] != 0x42)
315 Dprintf("WvTaskMan: remaining stack after #%d (%s): %ld/%ld\n",
316 current_task
->tid
, current_task
->name
.cstr(), (long)stackleft
,
317 (long)current_task
->stacksize
);
322 assert(getcontext(¤t_task
->mystate
) == 0);
323 int newval
= context_return
;
326 // saved the task state; now yield to the toplevel.
327 context_return
= val
;
328 setcontext(&toplevel
);
333 // back via longjmp, because someone called run() again. Let's go
334 // back to our running task...
335 valgrind_fix(stacktop
);
341 void WvTaskMan::get_stack(WvTask
&task
, size_t size
)
344 assert(getcontext(&get_stack_return
) == 0);
345 if (context_return
== 0)
347 assert(magic_number
== -WVTASK_MAGIC
);
348 assert(task
.magic_number
== WVTASK_MAGIC
);
350 if (!use_shared_stack())
352 #if defined(__linux__) && (defined(__386__) || defined(__i386) || defined(__i386__))
353 static char *next_stack_addr
= (char *)0xB0000000;
354 static const size_t stack_shift
= 0x00100000;
356 next_stack_addr
-= stack_shift
;
358 static char *next_stack_addr
= NULL
;
361 task
.stack
= mmap(next_stack_addr
, task
.stacksize
,
362 PROT_READ
| PROT_WRITE
,
364 MAP_PRIVATE
| MAP_ANONYMOUS
,
372 stack_target
= &task
;
373 context_return
= size
/1024 + (size
%1024 > 0);
374 setcontext(&stackmaster_task
);
379 valgrind_fix(stacktop
);
380 assert(magic_number
== -WVTASK_MAGIC
);
381 assert(task
.magic_number
== WVTASK_MAGIC
);
383 // back from stackmaster - the task is now set up.
389 void WvTaskMan::stackmaster()
391 // leave lots of room on the "main" stack before doing our magic
398 void WvTaskMan::_stackmaster()
403 Dprintf("stackmaster 1\n");
405 // the main loop runs once from the constructor, and then once more
406 // after each stack allocation.
409 assert(magic_number
== -WVTASK_MAGIC
);
412 assert(getcontext(&stackmaster_task
) == 0);
413 val
= context_return
;
416 assert(magic_number
== -WVTASK_MAGIC
);
418 // just did setjmp; save stackmaster's current state (with
419 // all current stack allocations) and go back to get_stack
420 // (or the constructor, if that's what called us)
422 setcontext(&get_stack_return
);
426 valgrind_fix(stacktop
);
427 assert(magic_number
== -WVTASK_MAGIC
);
429 total
= (val
+1) * (size_t)1024;
431 if (!use_shared_stack())
432 total
= 1024; // enough to save the do_task stack frame
434 // set up a stack frame for the new task. This runs once
436 //alloc_stack_and_switch(total);
439 assert(magic_number
== -WVTASK_MAGIC
);
441 // allocate the stack area so we never use it again
444 // a little sentinel so we can detect stack overflows
445 stack_target
->stack_magic
= (int *)alloca(sizeof(int));
446 *stack_target
->stack_magic
= WVTASK_MAGIC
;
448 // clear the stack to 0x42 so we can count unused stack
451 memset(stack_target
->stack_magic
+ 1, 0x42, total
- 1024);
458 void WvTaskMan::call_func(WvTask
*task
)
460 Dprintf("WvTaskMan: calling task #%d (%s)\n",
461 task
->tid
, (const char *)task
->name
);
462 task
->func(task
->userdata
);
463 Dprintf("WvTaskMan: returning from task #%d (%s)\n",
464 task
->tid
, (const char *)task
->name
);
469 void WvTaskMan::do_task()
471 assert(magic_number
== -WVTASK_MAGIC
);
472 WvTask
*task
= stack_target
;
473 assert(task
->magic_number
== WVTASK_MAGIC
);
475 // back here from longjmp; someone wants stack space.
477 assert(getcontext(&task
->mystate
) == 0);
478 if (context_return
== 0)
480 // done the setjmp; that means the target task now has
481 // a working jmp_buf all set up. Leave space on the stack
482 // for his data, then repeat the loop in _stackmaster (so we can
483 // return to get_stack(), and allocate more stack for someone later)
485 // Note that nothing on the allocated stack needs to be valid; when
486 // they longjmp to task->mystate, they'll have a new stack pointer
487 // and they'll already know what to do (in the 'else' clause, below)
488 Dprintf("stackmaster 5\n");
493 // someone did a run() on the task, which
494 // means they're ready to make it go. Do it.
495 valgrind_fix(stacktop
);
498 assert(magic_number
== -WVTASK_MAGIC
);
500 assert(task
->magic_number
== WVTASK_MAGIC
);
502 if (task
->func
&& task
->running
)
504 if (use_shared_stack())
506 // this is the task's main function. It can call yield()
507 // to give up its timeslice if it wants. Either way, it
508 // only returns to *us* if the function actually finishes.
509 task
->func(task
->userdata
);
513 assert(getcontext(&task
->func_call
) == 0);
514 task
->func_call
.uc_stack
.ss_size
= task
->stacksize
;
515 task
->func_call
.uc_stack
.ss_sp
= task
->stack
;
516 task
->func_call
.uc_stack
.ss_flags
= 0;
517 task
->func_call
.uc_link
= &task
->func_return
;
518 Dprintf("WvTaskMan: makecontext #%d (%s)\n",
519 task
->tid
, (const char *)task
->name
);
520 makecontext(&task
->func_call
,
521 (void (*)(void))call_func
, 1, task
);
524 assert(getcontext(&task
->func_return
) == 0);
525 if (context_return
== 0)
526 setcontext(&task
->func_call
);
529 // the task's function terminated.
531 task
->running
= false;
540 const void *WvTaskMan::current_top_of_stack()
542 #ifdef HAVE_LIBC_STACK_END
543 extern const void *__libc_stack_end
;
544 if (use_shared_stack() || current_task
== NULL
)
545 return __libc_stack_end
;
547 return (const char *)current_task
->stack
+ current_task
->stacksize
;
554 size_t WvTaskMan::current_stacksize_limit()
556 if (use_shared_stack() || current_task
== NULL
)
559 if (getrlimit(RLIMIT_STACK
, &rl
) == 0)
560 return size_t(rl
.rlim_cur
);
565 return size_t(current_task
->stacksize
);