| /* |
| * Thread scheduler. |
| */ |
| #include "threadimpl.h" |
| |
| static Thread *runthread(Proc*); |
| static void schedexit(Proc*); |
| |
| /* |
| * Main scheduling loop. |
| */ |
| void |
| _threadscheduler(void *arg) |
| { |
| Proc *p; |
| Thread *t; |
| |
| p = arg; |
| |
| _threadlinkmain(); |
| _threadsetproc(p); |
| |
| for(;;){ |
| /* |
| * Clean up zombie children. |
| */ |
| |
| /* |
| * Find next thread to run. |
| */ |
| _threaddebug(DBGSCHED, "runthread"); |
| t = runthread(p); |
| if(t == nil) |
| schedexit(p); |
| |
| /* |
| * If it's ready, run it (might instead be marked to die). |
| */ |
| lock(&p->lock); |
| if(t->state == Ready){ |
| _threaddebug(DBGSCHED, "running %d.%d", p->id, t->id); |
| t->state = Running; |
| t->nextstate = Ready; |
| p->thread = t; |
| unlock(&p->lock); |
| _swaplabel(&p->context, &t->context); |
| lock(&p->lock); |
| p->thread = nil; |
| } |
| |
| /* |
| * If thread needs to die, kill it. |
| * t->proc == p may not be true if we're |
| * trying to jump into the exec proc (see exec-unix.c). |
| */ |
| if(t->moribund){ |
| _threaddebug(DBGSCHED, "moribund %d.%d", p->id, t->id); |
| if(t->moribund != 1) |
| print("moribund broke %p %d\n", &t->moribund, t->moribund); |
| assert(t->moribund == 1); |
| t->state = Dead; |
| _procdelthread(p, t); |
| unlock(&p->lock); |
| _threadfree(t); |
| t = nil; |
| continue; |
| } |
| |
| /* |
| * If the thread has asked to move to another proc, |
| * let it go (only to be used in *very* special situations). |
| if(t->nextproc != p) |
| _procdelthread(p, t); |
| */ |
| |
| unlock(&p->lock); |
| |
| /* |
| * If the thread has asked to move to another proc, |
| * add it to the new proc. |
| */ |
| if(t->nextproc != p){ |
| // lock(&t->nextproc->lock); |
| // _procaddthread(t->nextproc, t); |
| // unlock(&t->nextproc->lock); |
| t->proc = t->nextproc; |
| } |
| |
| /* |
| * If there is a request to run a function on the |
| * scheduling stack, do so. |
| */ |
| if(p->schedfn){ |
| _threaddebug(DBGSCHED, "schedfn"); |
| p->schedfn(p); |
| p->schedfn = nil; |
| _threaddebug(DBGSCHED, "schedfn ended"); |
| } |
| |
| /* |
| * Move the thread along. |
| */ |
| t->state = t->nextstate; |
| _threaddebug(DBGSCHED, "moveon %d.%d", t->proc->id, t->id); |
| if(t->state == Ready) |
| _threadready(t); |
| } |
| } |
| |
| /* |
| * Called by thread to give up control of processor to scheduler. |
| */ |
| int |
| _sched(void) |
| { |
| Proc *p; |
| Thread *t; |
| |
| p = _threadgetproc(); |
| t = p->thread; |
| assert(t != nil); |
| _swaplabel(&t->context, &p->context); |
| return p->nsched++; |
| } |
| |
| /* |
| * Called by thread to yield the processor to other threads. |
| * Returns number of other threads run between call and return. |
| */ |
| int |
| yield(void) |
| { |
| Proc *p; |
| int nsched; |
| |
| p = _threadgetproc(); |
| nsched = p->nsched; |
| return _sched() - nsched; |
| } |
| |
| /* |
| * Choose the next thread to run. |
| */ |
| static Thread* |
| runthread(Proc *p) |
| { |
| Thread *t; |
| Tqueue *q; |
| |
| /* |
| * No threads left? |
| */ |
| if(p->nthreads==0 || (p->nthreads==1 && p->idle)) |
| return nil; |
| |
| _threadschednote(); |
| lock(&p->readylock); |
| q = &p->ready; |
| if(q->head == nil){ |
| /* |
| * Is this a single-process program with an idle thread? |
| */ |
| if(p->idle){ |
| /* |
| * The idle thread had better be ready! |
| */ |
| if(p->idle->state != Ready) |
| sysfatal("all threads are asleep"); |
| |
| /* |
| * Run the idle thread. |
| */ |
| unlock(&p->readylock); |
| _threaddebug(DBGSCHED, "running idle thread", p->nthreads); |
| return p->idle; |
| } |
| |
| /* |
| * Wait until one of our threads is readied (by another proc!). |
| */ |
| q->asleep = 1; |
| p->rend.l = &p->readylock; |
| while(q->asleep){ |
| _procsleep(&p->rend); |
| _threadschednote(); |
| } |
| |
| /* |
| * Maybe we were awakened to exit? |
| */ |
| if(_threadexitsallstatus){ |
| _threaddebug(DBGSCHED, "time to exit"); |
| _exits(_threadexitsallstatus); |
| } |
| assert(q->head != nil); |
| } |
| |
| t = q->head; |
| q->head = t->next; |
| unlock(&p->readylock); |
| |
| return t; |
| } |
| |
| /* |
| * Add a newly-ready thread to its proc's run queue. |
| */ |
| void |
| _threadready(Thread *t) |
| { |
| Tqueue *q; |
| |
| /* |
| * The idle thread does not go on the run queue. |
| */ |
| if(t == t->proc->idle){ |
| _threaddebug(DBGSCHED, "idle thread is ready"); |
| return; |
| } |
| |
| assert(t->state == Ready); |
| _threaddebug(DBGSCHED, "readying %d.%d", t->proc->id, t->id); |
| |
| /* |
| * Add thread to run queue. |
| */ |
| q = &t->proc->ready; |
| lock(&t->proc->readylock); |
| |
| t->next = nil; |
| if(q->head == nil) |
| q->head = t; |
| else |
| q->tail->next = t; |
| q->tail = t; |
| |
| /* |
| * Wake proc scheduler if it is sleeping. |
| */ |
| if(q->asleep){ |
| assert(q->asleep == 1); |
| q->asleep = 0; |
| _procwakeup(&t->proc->rend); |
| } |
| unlock(&t->proc->readylock); |
| } |
| |
| /* |
| * Mark the given thread as the idle thread. |
| * Since the idle thread was just created, it is sitting |
| * somewhere on the ready queue. |
| */ |
| void |
| _threadsetidle(int id) |
| { |
| Tqueue *q; |
| Thread *t, **l, *last; |
| Proc *p; |
| |
| p = _threadgetproc(); |
| |
| lock(&p->readylock); |
| |
| /* |
| * Find thread on ready queue. |
| */ |
| q = &p->ready; |
| for(l=&q->head, last=nil; (t=*l) != nil; l=&t->next, last=t) |
| if(t->id == id) |
| break; |
| assert(t != nil); |
| |
| /* |
| * Remove it from ready queue. |
| */ |
| *l = t->next; |
| if(t == q->head) |
| q->head = t->next; |
| if(t->next == nil) |
| q->tail = last; |
| |
| /* |
| * Set as idle thread. |
| */ |
| p->idle = t; |
| _threaddebug(DBGSCHED, "p->idle is %d\n", t->id); |
| unlock(&p->readylock); |
| } |
| |
| /* |
| * Mark proc as internal so that if all but internal procs exit, we exit. |
| */ |
| void |
| _threadinternalproc(void) |
| { |
| Proc *p; |
| |
| p = _threadgetproc(); |
| if(p->internal) |
| return; |
| lock(&_threadpq.lock); |
| if(p->internal == 0){ |
| p->internal = 1; |
| --_threadnprocs; |
| } |
| unlock(&_threadpq.lock); |
| } |
| |
| static void |
| schedexit(Proc *p) |
| { |
| char ex[ERRMAX]; |
| int n; |
| Proc **l; |
| |
| _threaddebug(DBGSCHED, "exiting proc %d", p->id); |
| lock(&_threadpq.lock); |
| for(l=&_threadpq.head; *l; l=&(*l)->next){ |
| if(*l == p){ |
| *l = p->next; |
| if(*l == nil) |
| _threadpq.tail = l; |
| break; |
| } |
| } |
| if(p->internal) |
| n = _threadnprocs; |
| else |
| n = --_threadnprocs; |
| unlock(&_threadpq.lock); |
| |
| strncpy(ex, p->exitstr, sizeof ex); |
| ex[sizeof ex-1] = '\0'; |
| free(p); |
| if(n == 0){ |
| _threaddebug(DBGSCHED, "procexit; no more procs"); |
| _kthreadexitallproc(ex); |
| }else{ |
| _threaddebug(DBGSCHED, "procexit"); |
| _kthreadexitproc(ex); |
| } |
| } |
| |