wvtask.cc

00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  * 
00005  * A set of classes that provide co-operative multitasking support.  See
00006  * wvtask.h for more information.
00007  */
00008 
00009 #include "wvautoconf.h"
00010 #ifdef __GNUC__
00011 # define alloca __builtin_alloca
00012 #else
00013 # ifdef _MSC_VER
00014 #  include <malloc.h>
00015 #  define alloca _alloca
00016 # else
00017 #  if HAVE_ALLOCA_H
00018 #   include <alloca.h>
00019 #  else
00020 #   ifdef _AIX
00021 #pragma alloca
00022 #   else
00023 #    ifndef alloca /* predefined by HP cc +Olibcalls */
00024 char *alloca ();
00025 #    endif
00026 #   endif
00027 #  endif
00028 # endif
00029 #endif
00030 
00031 #include "wvtask.h"
00032 #include <stdio.h>
00033 #include <stdlib.h>
00034 #include <assert.h>
00035 #include <sys/mman.h>
00036 #include <signal.h>
00037 #include <unistd.h>
00038 #include <sys/resource.h>
00039 
00040 #ifdef HAVE_VALGRIND_MEMCHECK_H
00041 #include <valgrind/memcheck.h>
00042 #else
00043 #define VALGRIND_MAKE_READABLE(x, y)
00044 #define RUNNING_ON_VALGRIND 0
00045 #endif
00046 
00047 #define TASK_DEBUG 0
00048 #if TASK_DEBUG
00049 # define Dprintf(fmt, args...) fprintf(stderr, fmt, ##args)
00050 #else
00051 # define Dprintf(fmt, args...)
00052 #endif
00053 
00054 int WvTask::taskcount, WvTask::numtasks, WvTask::numrunning;
00055 
00056 WvTaskMan *WvTaskMan::singleton;
00057 int WvTaskMan::links, WvTaskMan::magic_number;
00058 WvTaskList WvTaskMan::all_tasks, WvTaskMan::free_tasks;
00059 ucontext_t WvTaskMan::stackmaster_task, WvTaskMan::get_stack_return,
00060     WvTaskMan::toplevel;
00061 WvTask *WvTaskMan::current_task, *WvTaskMan::stack_target;
00062 char *WvTaskMan::stacktop;
00063 
00064 static int context_return;
00065 
00066 
00067 static bool use_shared_stack()
00068 {
00069     return RUNNING_ON_VALGRIND;
00070 }
00071 
00072 
00073 static void valgrind_fix(char *stacktop)
00074 {
00075 #ifdef HAVE_VALGRIND_MEMCHECK_H
00076     char val;
00077     //printf("valgrind fix: %p-%p\n", &val, stacktop);
00078     assert(stacktop > &val);
00079 #endif
00080     VALGRIND_MAKE_READABLE(&val, stacktop - &val);
00081 }
00082 
00083 
00084 WvTask::WvTask(WvTaskMan &_man, size_t _stacksize) : man(_man)
00085 {
00086     stacksize = _stacksize;
00087     running = recycled = false;
00088     func = NULL;
00089     userdata = NULL;
00090     
00091     tid = ++taskcount;
00092     numtasks++;
00093     magic_number = WVTASK_MAGIC;
00094     stack_magic = NULL;
00095     
00096     man.get_stack(*this, stacksize);
00097 
00098     man.all_tasks.append(this, false);
00099 }
00100 
00101 
00102 WvTask::~WvTask()
00103 {
00104     numtasks--;
00105     if (running)
00106         numrunning--;
00107     magic_number = 42;
00108 }
00109 
00110 
00111 void WvTask::start(WvStringParm _name, TaskFunc *_func, void *_userdata)
00112 {
00113     assert(!recycled);
00114     name = _name;
00115     func = _func;
00116     userdata = _userdata;
00117     running = true;
00118     numrunning++;
00119 }
00120 
00121 
00122 void WvTask::recycle()
00123 {
00124     assert(!running);
00125     
00126     if (!running && !recycled)
00127     {
00128         man.free_tasks.append(this, true);
00129         recycled = true;
00130     }
00131 }
00132 
00133 
00134 WvTaskMan *WvTaskMan::get()
00135 {
00136     if (!links)
00137         singleton = new WvTaskMan;
00138     links++;
00139     return singleton;
00140 }
00141 
00142 
00143 void WvTaskMan::unlink()
00144 {
00145     links--;
00146     if (!links)
00147     {
00148         delete singleton;
00149         singleton = NULL;
00150     }
00151 }
00152 
00153 
00154 static inline const char *Yes_No(bool val)
00155 {
00156     return val? "Yes": "No";
00157 }
00158 
00159 
00160 WvString WvTaskMan::debugger_tasks_run_cb(WvStringParm cmd, WvStringList &args,
00161         WvStreamsDebugger::ResultCallback result_cb, void *)
00162 {
00163     const char *format_str = "%5s%s%7s%s%8s%s%6s%s%s";
00164     WvStringList result;
00165     result.append(format_str, "--TID", "-", "Running", "-", "Recycled", "-", "-StkSz", "-", "Name-----");
00166     result_cb(cmd, result);
00167     WvTaskList::Iter i(all_tasks);
00168     for (i.rewind(); i.next(); )
00169     {
00170         result.zap();
00171         result.append(format_str, i->tid, " ",
00172                 Yes_No(i->running), " ",
00173                 Yes_No(i->recycled), " ",
00174                 i->stacksize, " ",
00175                 i->name);
00176         result_cb(cmd, result);
00177     }
00178     return WvString::null;
00179 }
00180 
00181 
00182 WvTaskMan::WvTaskMan()
00183 {
00184     static bool first = true;
00185     if (first)
00186     {
00187         first = false;
00188         WvStreamsDebugger::add_command("tasks", 0, debugger_tasks_run_cb, 0);
00189     }
00190 
00191     stack_target = NULL;
00192     current_task = NULL;
00193     magic_number = -WVTASK_MAGIC;
00194     
00195     stacktop = (char *)alloca(0);
00196     
00197     context_return = 0;
00198     assert(getcontext(&get_stack_return) == 0);
00199     if (context_return == 0)
00200     {
00201         // initial setup - start the stackmaster() task (never returns!)
00202         stackmaster();
00203     }
00204     // if we get here, stackmaster did a longjmp back to us.
00205 }
00206 
00207 
00208 WvTaskMan::~WvTaskMan()
00209 {    
00210     magic_number = -42;
00211     free_tasks.zap();
00212 }
00213 
00214 
00215 WvTask *WvTaskMan::start(WvStringParm name, 
00216                          WvTask::TaskFunc *func, void *userdata,
00217                          size_t stacksize)
00218 {
00219     WvTask *t;
00220     
00221     WvTaskList::Iter i(free_tasks);
00222     for (i.rewind(); i.next(); )
00223     {
00224         if (i().stacksize >= stacksize)
00225         {
00226             t = &i();
00227             i.set_autofree(false);
00228             i.unlink();
00229             t->recycled = false;
00230             t->start(name, func, userdata);
00231             return t;
00232         }
00233     }
00234     
00235     // if we get here, no matching task was found.
00236     t = new WvTask(*this, stacksize);
00237     t->start(name, func, userdata);
00238     return t;
00239 }
00240 
00241 
00242 int WvTaskMan::run(WvTask &task, int val)
00243 {
00244     assert(magic_number == -WVTASK_MAGIC);
00245     assert(task.magic_number == WVTASK_MAGIC);
00246     assert(!task.recycled);
00247     
00248     Dprintf("WvTaskMan: running task #%d with value %d (%s)\n",
00249             task.tid, val, (const char *)task.name);
00250     
00251     if (&task == current_task)
00252         return val; // that's easy!
00253         
00254     WvTask *old_task = current_task;
00255     current_task = &task;
00256     ucontext_t *state;
00257     
00258     if (!old_task)
00259         state = &toplevel; // top-level call (not in an actual task yet)
00260     else
00261         state = &old_task->mystate;
00262     
00263     context_return = 0;
00264     assert(getcontext(state) == 0);
00265     int newval = context_return;
00266     if (newval == 0)
00267     {
00268         // saved the state, now run the task.
00269         context_return = val;
00270         setcontext(&task.mystate);
00271         return -1;
00272     }
00273     else
00274     {
00275         // need to make state readable to see if we need to make more readable..
00276         VALGRIND_MAKE_READABLE(&state, sizeof(state));
00277         // someone did yield() (if toplevel) or run() on our old task; done.
00278         if (state != &toplevel)
00279             valgrind_fix(stacktop);
00280         current_task = old_task;
00281         return newval;
00282     }
00283 }
00284 
00285 
00286 int WvTaskMan::yield(int val)
00287 {
00288     if (!current_task)
00289         return 0; // weird...
00290     
00291     Dprintf("WvTaskMan: yielding from task #%d with value %d (%s)\n",
00292            current_task->tid, val, (const char *)current_task->name);
00293     
00294     assert(current_task->stack_magic);
00295     
00296     // if this fails, this task overflowed its stack.  Make it bigger!
00297     VALGRIND_MAKE_READABLE(current_task->stack_magic,
00298                            sizeof(current_task->stack_magic));
00299     assert(*current_task->stack_magic == WVTASK_MAGIC);
00300 
00301 #if TASK_DEBUG
00302     if (use_shared_stack())
00303     {
00304         size_t stackleft;
00305         char *stackbottom = (char *)(current_task->stack_magic + 1);
00306         for (stackleft = 0; stackleft < current_task->stacksize; stackleft++)
00307         {
00308             if (stackbottom[stackleft] != 0x42)
00309                 break;
00310         }
00311         Dprintf("WvTaskMan: remaining stack after #%d (%s): %ld/%ld\n",
00312                 current_task->tid, current_task->name.cstr(), (long)stackleft,
00313                 (long)current_task->stacksize);
00314     }
00315 #endif
00316                 
00317     context_return = 0;
00318     assert(getcontext(&current_task->mystate) == 0);
00319     int newval = context_return;
00320     if (newval == 0)
00321     {
00322         // saved the task state; now yield to the toplevel.
00323         context_return = val;
00324         setcontext(&toplevel);
00325         return -1;
00326     }
00327     else
00328     {
00329         // back via longjmp, because someone called run() again.  Let's go
00330         // back to our running task...
00331         valgrind_fix(stacktop);
00332         return newval;
00333     }
00334 }
00335 
00336 
00337 void WvTaskMan::get_stack(WvTask &task, size_t size)
00338 {
00339     context_return = 0;
00340     assert(getcontext(&get_stack_return) == 0);
00341     if (context_return == 0)
00342     {
00343         assert(magic_number == -WVTASK_MAGIC);
00344         assert(task.magic_number == WVTASK_MAGIC);
00345 
00346         if (!use_shared_stack())
00347         {
00348 #if defined(__linux__) && (defined(__386__) || defined(__i386) || defined(__i386__))
00349             static char *next_stack_addr = (char *)0xB0000000;
00350             static const size_t stack_shift = 0x00100000;
00351 
00352             next_stack_addr -= stack_shift;
00353 #else
00354             static char *next_stack_addr = NULL;
00355 #endif
00356         
00357             task.stack = mmap(next_stack_addr, task.stacksize,
00358                 PROT_READ | PROT_WRITE,
00359                 MAP_PRIVATE | MAP_ANONYMOUS,
00360                 -1, 0);
00361         }
00362         
00363         // initial setup
00364         stack_target = &task;
00365         context_return = size/1024 + (size%1024 > 0);
00366         setcontext(&stackmaster_task);
00367     }
00368     else
00369     {
00370         if (current_task)
00371             valgrind_fix(stacktop);
00372         assert(magic_number == -WVTASK_MAGIC);
00373         assert(task.magic_number == WVTASK_MAGIC);
00374         
00375         // back from stackmaster - the task is now set up.
00376         return;
00377     }
00378 }
00379 
00380 
00381 void WvTaskMan::stackmaster()
00382 {
00383     // leave lots of room on the "main" stack before doing our magic
00384     alloca(1024*1024);
00385     
00386     _stackmaster();
00387 }
00388 
00389 
00390 void WvTaskMan::_stackmaster()
00391 {
00392     int val;
00393     size_t total;
00394     
00395     Dprintf("stackmaster 1\n");
00396     
00397     // the main loop runs once from the constructor, and then once more
00398     // after each stack allocation.
00399     for (;;)
00400     {
00401         assert(magic_number == -WVTASK_MAGIC);
00402         
00403         context_return = 0;
00404         assert(getcontext(&stackmaster_task) == 0);
00405         val = context_return;
00406         if (val == 0)
00407         {
00408             assert(magic_number == -WVTASK_MAGIC);
00409             
00410             // just did setjmp; save stackmaster's current state (with
00411             // all current stack allocations) and go back to get_stack
00412             // (or the constructor, if that's what called us)
00413             context_return = 1;
00414             setcontext(&get_stack_return);
00415         }
00416         else
00417         {
00418             valgrind_fix(stacktop);
00419             assert(magic_number == -WVTASK_MAGIC);
00420             
00421             total = (val+1) * (size_t)1024;
00422             
00423             if (!use_shared_stack())
00424                 total = 1024; // enough to save the do_task stack frame
00425 
00426             // set up a stack frame for the new task.  This runs once
00427             // per get_stack.
00428             //alloc_stack_and_switch(total);
00429             do_task();
00430             
00431             assert(magic_number == -WVTASK_MAGIC);
00432 
00433             // allocate the stack area so we never use it again
00434             alloca(total);
00435 
00436             // a little sentinel so we can detect stack overflows
00437             stack_target->stack_magic = (int *)alloca(sizeof(int));
00438             *stack_target->stack_magic = WVTASK_MAGIC;
00439             
00440             // clear the stack to 0x42 so we can count unused stack
00441             // space later.
00442 #if TASK_DEBUG
00443             memset(stack_target->stack_magic + 1, 0x42, total - 1024);
00444 #endif
00445         }
00446     }
00447 }
00448 
00449 
00450 void WvTaskMan::call_func(WvTask *task)
00451 {
00452     Dprintf("WvTaskMan: calling task #%d (%s)\n",
00453             task->tid, (const char *)task->name);
00454     task->func(task->userdata);
00455     Dprintf("WvTaskMan: returning from task #%d (%s)\n",
00456             task->tid, (const char *)task->name);
00457     context_return = 1;
00458 }
00459 
00460 
00461 void WvTaskMan::do_task()
00462 {
00463     assert(magic_number == -WVTASK_MAGIC);
00464     WvTask *task = stack_target;
00465     assert(task->magic_number == WVTASK_MAGIC);
00466         
00467     // back here from longjmp; someone wants stack space.    
00468     context_return = 0;
00469     assert(getcontext(&task->mystate) == 0);
00470     if (context_return == 0)
00471     {
00472         // done the setjmp; that means the target task now has
00473         // a working jmp_buf all set up.  Leave space on the stack
00474         // for his data, then repeat the loop in _stackmaster (so we can
00475         // return to get_stack(), and allocate more stack for someone later)
00476         // 
00477         // Note that nothing on the allocated stack needs to be valid; when
00478         // they longjmp to task->mystate, they'll have a new stack pointer
00479         // and they'll already know what to do (in the 'else' clause, below)
00480         Dprintf("stackmaster 5\n");
00481         return;
00482     }
00483     else
00484     {
00485         // someone did a run() on the task, which
00486         // means they're ready to make it go.  Do it.
00487         valgrind_fix(stacktop);
00488         for (;;)
00489         {
00490             assert(magic_number == -WVTASK_MAGIC);
00491             assert(task);
00492             assert(task->magic_number == WVTASK_MAGIC);
00493             
00494             if (task->func && task->running)
00495             {
00496                 if (use_shared_stack())
00497                 {
00498                     // this is the task's main function.  It can call yield()
00499                     // to give up its timeslice if it wants.  Either way, it
00500                     // only returns to *us* if the function actually finishes.
00501                     task->func(task->userdata);
00502                 }
00503                 else
00504                 {
00505                     assert(getcontext(&task->func_call) == 0);
00506                     task->func_call.uc_stack.ss_size = task->stacksize;
00507                     task->func_call.uc_stack.ss_sp = task->stack;
00508                     task->func_call.uc_stack.ss_flags = 0;
00509                     task->func_call.uc_link = &task->func_return;
00510                     Dprintf("WvTaskMan: makecontext #%d (%s)\n",
00511                             task->tid, (const char *)task->name);
00512                     makecontext(&task->func_call,
00513                             (void (*)(void))call_func, 1, task);
00514 
00515                     context_return = 0;
00516                     assert(getcontext(&task->func_return) == 0);
00517                     if (context_return == 0)
00518                         setcontext(&task->func_call);
00519                 }
00520                 
00521                 // the task's function terminated.
00522                 task->name = "DEAD";
00523                 task->running = false;
00524                 task->numrunning--;
00525             }
00526             yield();
00527         }
00528     }
00529 }
00530 
00531 
00532 const void *WvTaskMan::current_top_of_stack()
00533 {
00534     extern const void *__libc_stack_end;
00535     if (use_shared_stack() || current_task == NULL)
00536         return __libc_stack_end;
00537     else
00538         return (const char *)current_task->stack + current_task->stacksize;
00539 }
00540 
00541 
00542 size_t WvTaskMan::current_stacksize_limit()
00543 {
00544     if (use_shared_stack() || current_task == NULL)
00545     {
00546         struct rlimit rl;
00547         if (getrlimit(RLIMIT_STACK, &rl) == 0)
00548             return size_t(rl.rlim_cur);
00549         else
00550             return 0;
00551     }
00552     else
00553         return size_t(current_task->stacksize);
00554 }
00555 
00556     

Generated on Fri Oct 5 18:20:29 2007 for WvStreams by  doxygen 1.5.3