198 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			198 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;
 | |
| }
 | |
| 
 | 
