minix/lib/libmthread/scheduler.c
2010-09-21 12:22:38 +00:00

197 lines
6.2 KiB
C

#include <minix/mthread.h>
#include "global.h"
#include "proto.h"
#define MAIN_CTX &(mainthread.m_context)
#define OLD_CTX &(threads[old_thread].m_context);
#define CURRENT_CTX &(threads[current_thread].m_context)
#define CURRENT_STATE threads[current_thread].m_state
PRIVATE int yield_all;
/*===========================================================================*
* mthread_getcontext *
*===========================================================================*/
PUBLIC int mthread_getcontext(ctx)
ucontext_t *ctx;
{
/* Retrieve this process' current state.*/
/* We're not interested in FPU state nor signals, so ignore them.
* Coincidentally, this significantly speeds up performance.
*/
ctx->uc_flags |= (UCF_IGNFPU | UCF_IGNSIGM);
return getcontext(ctx);
}
/*===========================================================================*
* mthread_schedule *
*===========================================================================*/
PUBLIC void mthread_schedule(void)
{
/* Pick a new thread to run and run it. In practice, this involves taking the
* first thread off the (FIFO) run queue and resuming that thread.
*/
int old_thread;
ucontext_t *new_ctx, *old_ctx;
mthread_init(); /* Make sure mthreads is initialized */
old_thread = current_thread;
if (mthread_queue_isempty(&run_queue)) {
/* No runnable threads. Let main thread run. */
/* We keep track whether we're running the program's 'main' thread or
* a spawned thread. In case we're already running the main thread and
* there are no runnable threads, we can't jump back to its context.
* Instead, we simply return.
*/
if (running_main_thread) return;
/* We're running the last runnable spawned thread. Return to main
* thread as there is no work left.
*/
running_main_thread = 1;
current_thread = NO_THREAD;
} else {
current_thread = mthread_queue_remove(&run_queue);
running_main_thread = 0; /* Running thread after swap */
}
if (current_thread == NO_THREAD)
new_ctx = MAIN_CTX;
else
new_ctx = CURRENT_CTX;
if (old_thread == NO_THREAD)
old_ctx = MAIN_CTX;
else
old_ctx = OLD_CTX;
if (swapcontext(old_ctx, new_ctx) == -1)
mthread_panic("Could not swap context");
}
/*===========================================================================*
* mthread_init_scheduler *
*===========================================================================*/
PUBLIC void mthread_init_scheduler(void)
{
/* Initialize the scheduler */
mthread_queue_init(&run_queue);
yield_all = 0;
}
/*===========================================================================*
* mthread_suspend *
*===========================================================================*/
PUBLIC void mthread_suspend(state)
mthread_state_t state;
{
/* Stop the current thread from running. There can be multiple reasons for
* this; the process tries to lock a locked mutex (i.e., has to wait for it to
* become unlocked), the process has to wait for a condition, the thread
* volunteered to let another thread to run (i.e., it called yield and remains
* runnable itself), or the thread is dead.
*/
int continue_thread = 0;
if (state == DEAD) mthread_panic("Shouldn't suspend with DEAD state");
threads[current_thread].m_state = state;
/* Save current thread's context */
if (mthread_getcontext(CURRENT_CTX) != 0)
mthread_panic("Couldn't save current thread's context");
/* We return execution here with setcontext/swapcontext, but also when we
* simply return from the getcontext call. If continue_thread is non-zero, we
* are continuing the execution of this thread after a call from setcontext
* or swapcontext.
*/
if(!continue_thread) {
continue_thread = 1;
mthread_schedule(); /* Let other thread run. */
}
}
/*===========================================================================*
* mthread_unsuspend *
*===========================================================================*/
PUBLIC void mthread_unsuspend(thread)
mthread_thread_t thread; /* Thread to make runnable */
{
/* Mark the state of a thread runnable and add it to the run queue */
if (!isokthreadid(thread)) mthread_panic("Invalid thread id\n");
threads[thread].m_state = RUNNABLE;
mthread_queue_add(&run_queue, thread);
}
/*===========================================================================*
* mthread_yield *
*===========================================================================*/
PUBLIC int mthread_yield(void)
{
/* Defer further execution of the current thread and let another thread run. */
mthread_init(); /* Make sure mthreads is initialized */
if (mthread_queue_isempty(&run_queue)) { /* No point in yielding. */
return(-1);
} else if (current_thread == NO_THREAD) {
/* Can't yield this thread, but still give other threads a chance to
* run.
*/
mthread_schedule();
return(-1);
}
mthread_queue_add(&run_queue, current_thread);
mthread_suspend(RUNNABLE); /* We're still runnable, but we're just kind
* enough to let someone else run.
*/
return(0);
}
/*===========================================================================*
* mthread_yield_all *
*===========================================================================*/
PUBLIC void mthread_yield_all(void)
{
/* Yield until there are no more runnable threads left. Two threads calling
* this function will lead to a deadlock.
*/
mthread_init(); /* Make sure mthreads is initialized */
if (yield_all) mthread_panic("Deadlock: two threads trying to yield_all");
yield_all = 1;
/* This works as follows. Thread A is running and threads B, C, and D are
* runnable. As A is running, it is NOT on the run_queue (see
* mthread_schedule). It calls mthread_yield and will be added to the run
* queue, allowing B to run. B runs and suspends eventually, possibly still
* in a runnable state. Then C and D run. Eventually A will run again (and is
* thus not on the list). If B, C, and D are dead, waiting for a condition,
* or waiting for a lock, they are not on the run queue either. At that
* point A is the only runnable thread left.
*/
while (!mthread_queue_isempty(&run_queue)) {
(void) mthread_yield();
}
/* Done yielding all threads. */
yield_all = 0;
}