/* * Return nonzero if there are no threads sleeping on the channel. * This is meant to be used only for diagnostic purposes. */ bool wchan_isempty(struct wchan* wc, struct spinlock* lk) { bool ret; assert(spinlock_held(lk)); ret = threadlist_isempty(&wc->wc_threads); return ret; }
/* * Return nonzero if there are no threads sleeping on the channel. * This is meant to be used only for diagnostic purposes. */ bool wchan_isempty(struct wchan *wc, struct spinlock *lk) { bool ret; KASSERT(spinlock_do_i_hold(lk)); ret = threadlist_isempty(&wc->wc_threads); return ret; }
void threadlist_cleanup(struct threadlist *tl) { DEBUGASSERT(tl != NULL); DEBUGASSERT(tl->tl_head.tln_next == &tl->tl_tail); DEBUGASSERT(tl->tl_head.tln_prev == NULL); DEBUGASSERT(tl->tl_tail.tln_next == NULL); DEBUGASSERT(tl->tl_tail.tln_prev == &tl->tl_head); DEBUGASSERT(tl->tl_head.tln_self == NULL); DEBUGASSERT(tl->tl_tail.tln_self == NULL); KASSERT(threadlist_isempty(tl)); KASSERT(tl->tl_count == 0); /* nothing (else) to do */ }
/* * Thread migration. * * This is also called periodically from hardclock(). If the current * CPU is busy and other CPUs are idle, or less busy, it should move * threads across to those other other CPUs. * * Migrating threads isn't free because of cache affinity; a thread's * working cache set will end up having to be moved to the other CPU, * which is fairly slow. The tradeoff between this performance loss * and the performance loss due to underutilization of some CPUs is * something that needs to be tuned and probably is workload-specific. * * For here and now, because we know we're running on System/161 and * System/161 does not (yet) model such cache effects, we'll be very * aggressive. */ void thread_consider_migration(void) { unsigned my_count, total_count, one_share, to_send; unsigned i, numcpus; struct cpu *c; struct threadlist victims; struct thread *t; my_count = total_count = 0; numcpus = cpuarray_num(&allcpus); for (i=0; i<numcpus; i++) { c = cpuarray_get(&allcpus, i); spinlock_acquire(&c->c_runqueue_lock); total_count += c->c_runqueue.tl_count; if (c == curcpu->c_self) { my_count = c->c_runqueue.tl_count; } spinlock_release(&c->c_runqueue_lock); } one_share = DIVROUNDUP(total_count, numcpus); if (my_count < one_share) { return; } to_send = my_count - one_share; threadlist_init(&victims); spinlock_acquire(&curcpu->c_runqueue_lock); for (i=0; i<to_send; i++) { t = threadlist_remtail(&curcpu->c_runqueue); threadlist_addhead(&victims, t); } spinlock_release(&curcpu->c_runqueue_lock); for (i=0; i < numcpus && to_send > 0; i++) { c = cpuarray_get(&allcpus, i); if (c == curcpu->c_self) { continue; } spinlock_acquire(&c->c_runqueue_lock); while (c->c_runqueue.tl_count < one_share && to_send > 0) { t = threadlist_remhead(&victims); /* * Ordinarily, curthread will not appear on * the run queue. However, it can under the * following circumstances: * - it went to sleep; * - the processor became idle, so it * remained curthread; * - it was reawakened, so it was put on the * run queue; * - and the processor hasn't fully unidled * yet, so all these things are still true. * * If the timer interrupt happens at (almost) * exactly the proper moment, we can come here * while things are in this state and see * curthread. However, *migrating* curthread * can cause bad things to happen (Exercise: * Why? And what?) so shuffle it to the end of * the list and decrement to_send in order to * skip it. Then it goes back on our own run * queue below. */ if (t == curthread) { threadlist_addtail(&victims, t); to_send--; continue; } t->t_cpu = c; threadlist_addtail(&c->c_runqueue, t); DEBUG(DB_THREADS, "Migrated thread %s: cpu %u -> %u", t->t_name, curcpu->c_number, c->c_number); to_send--; if (c->c_isidle) { /* * Other processor is idle; send * interrupt to make sure it unidles. */ ipi_send(c, IPI_UNIDLE); } } spinlock_release(&c->c_runqueue_lock); } /* * Because the code above isn't atomic, the thread counts may have * changed while we were working and we may end up with leftovers. * Don't panic; just put them back on our own run queue. */ if (!threadlist_isempty(&victims)) { spinlock_acquire(&curcpu->c_runqueue_lock); while ((t = threadlist_remhead(&victims)) != NULL) { threadlist_addtail(&curcpu->c_runqueue, t); } spinlock_release(&curcpu->c_runqueue_lock); } KASSERT(threadlist_isempty(&victims)); threadlist_cleanup(&victims); }
/* * High level, machine-independent context switch code. * * The current thread is queued appropriately and its state is changed * to NEWSTATE; another thread to run is selected and switched to. * * If NEWSTATE is S_SLEEP, the thread is queued on the wait channel * WC, protected by the spinlock LK. Otherwise WC and Lk should be * NULL. */ static void thread_switch(threadstate_t newstate, struct wchan *wc, struct spinlock *lk) { struct thread *cur, *next; int spl; DEBUGASSERT(curcpu->c_curthread == curthread); DEBUGASSERT(curthread->t_cpu == curcpu->c_self); /* Explicitly disable interrupts on this processor */ spl = splhigh(); cur = curthread; /* * If we're idle, return without doing anything. This happens * when the timer interrupt interrupts the idle loop. */ if (curcpu->c_isidle) { splx(spl); return; } /* Check the stack guard band. */ thread_checkstack(cur); /* Lock the run queue. */ spinlock_acquire(&curcpu->c_runqueue_lock); /* Micro-optimization: if nothing to do, just return */ if (newstate == S_READY && threadlist_isempty(&curcpu->c_runqueue)) { spinlock_release(&curcpu->c_runqueue_lock); splx(spl); return; } /* Put the thread in the right place. */ switch (newstate) { case S_RUN: panic("Illegal S_RUN in thread_switch\n"); case S_READY: thread_make_runnable(cur, true /*have lock*/); break; case S_SLEEP: cur->t_wchan_name = wc->wc_name; /* * Add the thread to the list in the wait channel, and * unlock same. To avoid a race with someone else * calling wchan_wake*, we must keep the wchan's * associated spinlock locked from the point the * caller of wchan_sleep locked it until the thread is * on the list. */ threadlist_addtail(&wc->wc_threads, cur); spinlock_release(lk); break; case S_ZOMBIE: cur->t_wchan_name = "ZOMBIE"; threadlist_addtail(&curcpu->c_zombies, cur); break; } cur->t_state = newstate; /* * Get the next thread. While there isn't one, call md_idle(). * curcpu->c_isidle must be true when md_idle is * called. Unlock the runqueue while idling too, to make sure * things can be added to it. * * Note that we don't need to unlock the runqueue atomically * with idling; becoming unidle requires receiving an * interrupt (either a hardware interrupt or an interprocessor * interrupt from another cpu posting a wakeup) and idling * *is* atomic with respect to re-enabling interrupts. * * Note that c_isidle becomes true briefly even if we don't go * idle. However, because one is supposed to hold the runqueue * lock to look at it, this should not be visible or matter. */ /* The current cpu is now idle. */ curcpu->c_isidle = true; do { next = threadlist_remhead(&curcpu->c_runqueue); if (next == NULL) { spinlock_release(&curcpu->c_runqueue_lock); cpu_idle(); spinlock_acquire(&curcpu->c_runqueue_lock); } } while (next == NULL); curcpu->c_isidle = false; /* * Note that curcpu->c_curthread may be the same variable as * curthread and it may not be, depending on how curthread and * curcpu are defined by the MD code. We'll assign both and * assume the compiler will optimize one away if they're the * same. */ curcpu->c_curthread = next; curthread = next; /* do the switch (in assembler in switch.S) */ switchframe_switch(&cur->t_context, &next->t_context); /* * When we get to this point we are either running in the next * thread, or have come back to the same thread again, * depending on how you look at it. That is, * switchframe_switch returns immediately in another thread * context, which in general will be executing here with a * different stack and different values in the local * variables. (Although new threads go to thread_startup * instead.) But, later on when the processor, or some * processor, comes back to the previous thread, it's also * executing here with the *same* value in the local * variables. * * The upshot, however, is as follows: * * - The thread now currently running is "cur", not "next", * because when we return from switchrame_switch on the * same stack, we're back to the thread that * switchframe_switch call switched away from, which is * "cur". * * - "cur" is _not_ the thread that just *called* * switchframe_switch. * * - If newstate is S_ZOMB we never get back here in that * context at all. * * - If the thread just chosen to run ("next") was a new * thread, we don't get to this code again until * *another* context switch happens, because when new * threads return from switchframe_switch they teleport * to thread_startup. * * - At this point the thread whose stack we're now on may * have been migrated to another cpu since it last ran. * * The above is inherently confusing and will probably take a * while to get used to. * * However, the important part is that code placed here, after * the call to switchframe_switch, does not necessarily run on * every context switch. Thus any such code must be either * skippable on some switches or also called from * thread_startup. */ /* Clear the wait channel and set the thread state. */ cur->t_wchan_name = NULL; cur->t_state = S_RUN; /* Unlock the run queue. */ spinlock_release(&curcpu->c_runqueue_lock); /* Activate our address space in the MMU. */ as_activate(); /* Clean up dead threads. */ exorcise(); /* Turn interrupts back on. */ splx(spl); }