/* Copyright (c) 2005 Russ Cox, MIT; see COPYRIGHT */ #include "taskimpl.h" #include #include #ifdef USE_VALGRIND #include #endif int taskdebuglevel; int taskcount; int tasknswitch; int taskexitval; Task *taskrunning; Context taskschedcontext; Tasklist taskrunqueue; Task **alltask; int nalltask; static char *argv0; static void contextswitch(Context *from, Context *to); static void taskdebug(char *fmt, ...) { va_list arg; char buf[128]; Task *t; char *p; static int fd = -1; if(taskdebuglevel == 0) return; va_start(arg, fmt); vfprint(1, fmt, arg); va_end(arg); if(fd < 0){ p = strrchr(argv0, '/'); if(p) p++; else p = argv0; snprint(buf, sizeof buf, "/tmp/%s.tlog", p); if((fd = open(buf, O_CREAT|O_WRONLY, 0666)) < 0) if((fd = open("/dev/null", O_WRONLY)) < 0) abort(); } va_start(arg, fmt); vsnprint(buf, sizeof buf, fmt, arg); va_end(arg); t = taskrunning; if(t) fprint(fd, "%d.%d: %s\n", getpid(), t->id, buf); else fprint(fd, "%d._: %s\n", getpid(), buf); } static void taskstart(uint y, uint x) { Task *t; ulong z; z = x<<16; /* hide undefined 32-bit shift from 32-bit compilers */ z <<= 16; z |= y; t = (Task*)z; //print("taskstart %p\n", t); t->startfn(t->startarg); //print("taskexits %p\n", t); taskexit(0); //print("not reacehd\n"); } static int taskidgen; static Task* taskalloc(void (*fn)(void*), void *arg, uint stack) { Task *t; sigset_t zero; uint x, y; ulong z; /* allocate the task and stack together */ t = malloc(sizeof *t+stack); if(t == nil){ fprint(2, "taskalloc malloc: %r\n"); abort(); } memset(t, 0, sizeof *t); t->stk = (uchar*)(t+1); t->stksize = stack; t->id = ++taskidgen; t->startfn = fn; t->startarg = arg; /* do a reasonable initialization */ memset(&t->context.uc, 0, sizeof t->context.uc); sigemptyset(&zero); sigprocmask(SIG_BLOCK, &zero, &t->context.uc.uc_sigmask); /* must initialize with current context */ if(getcontext(&t->context.uc) < 0){ fprint(2, "getcontext: %r\n"); abort(); } #ifdef USE_VALGRIND t->vid = VALGRIND_STACK_REGISTER(t->stk+8, t->stk+8 + t->stksize-64); #endif /* call makecontext to do the real work. */ /* leave a few words open on both ends */ t->context.uc.uc_stack.ss_sp = t->stk+8; t->context.uc.uc_stack.ss_size = t->stksize-64; #if defined(__sun__) && !defined(__MAKECONTEXT_V2_SOURCE) /* sigh */ #warning "doing sun thing" /* can avoid this with __MAKECONTEXT_V2_SOURCE but only on SunOS 5.9 */ t->context.uc.uc_stack.ss_sp = (char*)t->context.uc.uc_stack.ss_sp +t->context.uc.uc_stack.ss_size; #endif /* * All this magic is because you have to pass makecontext a * function that takes some number of word-sized variables, * and on 64-bit machines pointers are bigger than words. */ //print("make %p\n", t); z = (ulong)t; y = z; z >>= 16; /* hide undefined 32-bit shift from 32-bit compilers */ x = z>>16; makecontext(&t->context.uc, (void(*)())taskstart, 2, y, x); return t; } int taskcreate(void (*fn)(void*), void *arg, uint stack) { int id; Task *t; t = taskalloc(fn, arg, stack); taskcount++; id = t->id; if(nalltask%64 == 0){ alltask = realloc(alltask, (nalltask+64)*sizeof(alltask[0])); if(alltask == nil){ fprint(2, "out of memory\n"); abort(); } } t->alltaskslot = nalltask; alltask[nalltask++] = t; taskready(t); return id; } void tasksystem(void) { if(!taskrunning->system){ taskrunning->system = 1; --taskcount; } } void taskswitch(void) { needstack(0); contextswitch(&taskrunning->context, &taskschedcontext); } void taskready(Task *t) { t->ready = 1; addtask(&taskrunqueue, t); } int taskyield(void) { int n; n = tasknswitch; taskready(taskrunning); taskstate("yield"); taskswitch(); return tasknswitch - n - 1; } int anyready(void) { return taskrunqueue.head != nil; } void taskexitall(int val) { exit(val); } void taskexit(int val) { taskexitval = val; taskrunning->exiting = 1; taskswitch(); #ifdef USE_VALGRIND VALGRIND_STACK_DEREGISTER(taskrunning->vid); #endif } static void contextswitch(Context *from, Context *to) { if(swapcontext(&from->uc, &to->uc) < 0){ fprint(2, "swapcontext failed: %r\n"); assert(0); } } static void taskscheduler(void) { int i; Task *t; taskdebug("scheduler enter"); for(;;){ if(taskcount == 0) exit(taskexitval); t = taskrunqueue.head; if(t == nil){ fprint(2, "no runnable tasks! %d tasks stalled\n", taskcount); exit(1); } deltask(&taskrunqueue, t); t->ready = 0; taskrunning = t; tasknswitch++; taskdebug("run %d (%s)", t->id, t->name); contextswitch(&taskschedcontext, &t->context); //print("back in scheduler\n"); taskrunning = nil; if(t->exiting){ if(!t->system) taskcount--; i = t->alltaskslot; alltask[i] = alltask[--nalltask]; alltask[i]->alltaskslot = i; free(t); } } } void** taskdata(void) { return &taskrunning->udata; } /* * debugging */ void taskname(char *fmt, ...) { va_list arg; Task *t; if(taskdebuglevel == 0) return; t = taskrunning; va_start(arg, fmt); vsnprint(t->name, sizeof t->name, fmt, arg); va_end(arg); } char* taskgetname(void) { return taskrunning->name; } void taskstate(char *fmt, ...) { va_list arg; Task *t; if(taskdebuglevel == 0) return; t = taskrunning; va_start(arg, fmt); vsnprint(t->state, sizeof t->name, fmt, arg); va_end(arg); } char* taskgetstate(void) { return taskrunning->state; } void needstack(int n) { Task *t; t = taskrunning; if((char*)&t <= (char*)t->stk || (char*)&t - (char*)t->stk < 256+n){ fprint(2, "task stack overflow: &t=%p tstk=%p n=%d\n", &t, t->stk, 256+n); abort(); } } static void taskinfo(int s) { int i; Task *t; char *extra; USED(s); fprint(2, "task list:\n"); for(i=0; iready) extra = " (ready)"; else extra = ""; fprint(2, "%6d%c %-20s %s%s\n", t->id, t->system ? 's' : ' ', t->name, t->state, extra); } } /* * startup */ static int taskargc; static char **taskargv; int mainstacksize; static void taskmainstart(void *v) { USED(v); taskname("taskmain"); taskmain(taskargc, taskargv); } int main(int argc, char **argv) { struct sigaction sa, osa; memset(&sa, 0, sizeof sa); sa.sa_handler = taskinfo; sa.sa_flags = SA_RESTART; sigaction(SIGQUIT, &sa, &osa); #ifdef SIGINFO sigaction(SIGINFO, &sa, &osa); #endif argv0 = argv[0]; taskargc = argc; taskargv = argv; if(mainstacksize == 0) mainstacksize = 256*1024; taskcreate(taskmainstart, nil, mainstacksize); taskscheduler(); fprint(2, "taskscheduler returned in main!\n"); abort(); return 0; } /* * hooray for linked lists */ void addtask(Tasklist *l, Task *t) { if(l->tail){ l->tail->next = t; t->prev = l->tail; }else{ l->head = t; t->prev = nil; } l->tail = t; t->next = nil; } void deltask(Tasklist *l, Task *t) { if(t->prev) t->prev->next = t->next; else l->head = t->next; if(t->next) t->next->prev = t->prev; else l->tail = t->prev; } unsigned int taskid(void) { return taskrunning->id; }