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