501 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			501 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| #define ALLOCATE
 | |
| #include <errno.h>
 | |
| #include <minix/mthread.h>
 | |
| #include "global.h"
 | |
| #include "proto.h"
 | |
| 
 | |
| #define FALLBACK_CTX (&(fallback.m_context))
 | |
| 
 | |
| FORWARD _PROTOTYPE( void mthread_fallback, (void)			);
 | |
| FORWARD _PROTOTYPE( int mthread_increase_thread_pool, (void)			);
 | |
| FORWARD _PROTOTYPE( void mthread_thread_init, (mthread_thread_t thread,
 | |
| 					       mthread_attr_t *tattr,
 | |
| 					       void (*proc)(void *),
 | |
| 					       void *arg)		);
 | |
| 
 | |
| FORWARD _PROTOTYPE( void mthread_thread_reset, (mthread_thread_t thread));
 | |
| FORWARD _PROTOTYPE( void mthread_thread_stop, (mthread_thread_t thread));
 | |
| FORWARD _PROTOTYPE( void mthread_trampoline, (void)			);
 | |
| 
 | |
| PRIVATE int initialized = 0;
 | |
| 
 | |
| PRIVATE struct __mthread_attr default_attr = {	MTHREAD_STACK_MIN,
 | |
| 						NULL,
 | |
| 						MTHREAD_CREATE_JOINABLE,
 | |
| 						NULL, NULL };
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_equal				     *
 | |
|  *===========================================================================*/
 | |
| PUBLIC int mthread_equal(l, r)
 | |
| mthread_thread_t l;
 | |
| mthread_thread_t r;
 | |
| {
 | |
| /* Compare two thread ids */
 | |
|   mthread_init();	/* Make sure mthreads is initialized */
 | |
| 
 | |
|   return(l == r);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_create				     *
 | |
|  *===========================================================================*/
 | |
| PUBLIC int mthread_create(threadid, tattr, proc, arg)
 | |
| mthread_thread_t *threadid;
 | |
| mthread_attr_t *tattr;
 | |
| void (*proc)(void *);
 | |
| void *arg;
 | |
| {
 | |
| /* Register procedure proc for execution in a thread. */
 | |
|   mthread_thread_t thread;
 | |
| 
 | |
|   mthread_init();	/* Make sure mthreads is initialized */
 | |
| 
 | |
|   if (proc == NULL) {
 | |
|   	errno = EINVAL;
 | |
|   	return(-1);
 | |
|   }
 | |
| 
 | |
|   if (!mthread_queue_isempty(&free_threads)) {
 | |
|   	thread = mthread_queue_remove(&free_threads);
 | |
|   	mthread_thread_init(thread, tattr, proc, arg);
 | |
|  	used_threads++;
 | |
|  	if(threadid != NULL) 
 | |
|  		*threadid = (mthread_thread_t) thread;
 | |
| #ifdef MDEBUG
 | |
|  	printf("Inited thread %d\n", thread);
 | |
| #endif
 | |
|  	return(0);
 | |
|   } else  {
 | |
|   	if (mthread_increase_thread_pool() == -1) {
 | |
|   		errno = EAGAIN;
 | |
|   		return(-1);
 | |
|   	}
 | |
| 
 | |
|   	return mthread_create(threadid, tattr, proc, arg);
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_detach				     *
 | |
|  *===========================================================================*/
 | |
| PUBLIC int mthread_detach(detach)
 | |
| mthread_thread_t detach;
 | |
| {
 | |
| /* Mark a thread as detached. Consequently, upon exit, resources allocated for
 | |
|  * this thread are automatically freed.
 | |
|  */
 | |
| 
 | |
|   mthread_init();	/* Make sure mthreads is initialized */
 | |
| 
 | |
|   if (!isokthreadid(detach)) {
 | |
|   	errno = ESRCH;
 | |
|   	return(-1);
 | |
|   } else if (threads[detach].m_state == DEAD) {
 | |
|   	errno = ESRCH;
 | |
|   	return(-1);
 | |
|   } else if (threads[detach].m_attr.a_detachstate != MTHREAD_CREATE_DETACHED) {
 | |
|   	if (threads[detach].m_state == EXITING) {
 | |
|   		mthread_thread_stop(detach);
 | |
|   	} else {
 | |
| 		threads[detach].m_attr.a_detachstate = MTHREAD_CREATE_DETACHED;
 | |
| 	}
 | |
|   }
 | |
|   return(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_exit				     *
 | |
|  *===========================================================================*/
 | |
| PUBLIC void mthread_exit(value)
 | |
| void *value;
 | |
| {
 | |
| /* Make a thread stop running and store the result value. */
 | |
|   int fallback_exit = 0;
 | |
|   mthread_thread_t stop;
 | |
| 
 | |
|   mthread_init();	/* Make sure mthreads is initialized */
 | |
| 
 | |
|   stop = current_thread;
 | |
| 
 | |
|   if (threads[stop].m_state == EXITING) /* Already stopping, nothing to do. */
 | |
|   	return;
 | |
| 
 | |
|   /* When we're called from the fallback thread, the fallback thread 
 | |
|    * will invoke the scheduler. However, if the thread itself called 
 | |
|    * mthread_exit, _we_ will have to wake up the scheduler.
 | |
|    */
 | |
|   if (threads[stop].m_state == FALLBACK_EXITING)
 | |
|   	fallback_exit = 1;
 | |
| 
 | |
|   threads[stop].m_result = value;
 | |
|   threads[stop].m_state = EXITING;
 | |
| 
 | |
|   if (threads[stop].m_attr.a_detachstate == MTHREAD_CREATE_DETACHED) {
 | |
| 	mthread_thread_stop(stop);
 | |
|   } else {
 | |
|   	/* Joinable thread; notify possibly waiting thread */
 | |
| 	if (mthread_cond_signal(&(threads[stop].m_exited)) != 0) 
 | |
| 		mthread_panic("Couldn't signal exit");
 | |
| 
 | |
| 	/* The thread that's actually doing the join will eventually clean
 | |
| 	 * up this thread (i.e., call mthread_thread_stop).
 | |
| 	 */
 | |
|   }
 | |
| 
 | |
|   /* The fallback thread does a mthread_schedule. If we're not running from
 | |
|    * that thread, we have to do it ourselves.
 | |
|    */
 | |
|   if (!fallback_exit) 
 | |
| 	mthread_schedule();
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_fallback			     *
 | |
|  *===========================================================================*/
 | |
| PRIVATE void mthread_fallback(void)
 | |
| {
 | |
| /* The mthreads fallback thread. The idea is that every thread calls 
 | |
|  * mthread_exit(...) to stop running when it has nothing to do anymore. 
 | |
|  * However, in case a thread forgets to do that, the whole process  exit()s and
 | |
|  * that might be a bit problematic. Therefore, all threads will run this
 | |
|  * fallback thread when they exit, giving the scheduler a chance to fix the
 | |
|  * situation.
 | |
|  */
 | |
| 
 | |
|   threads[current_thread].m_state = FALLBACK_EXITING;
 | |
|   mthread_exit(NULL);
 | |
| 
 | |
|   /* Reconstruct fallback context for next invocation */
 | |
|   makecontext(FALLBACK_CTX, (void (*) (void)) mthread_fallback, 0);
 | |
| 
 | |
|   /* Let another thread run */
 | |
|   mthread_schedule();
 | |
| 
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *			mthread_increase_thread_pool			     *
 | |
|  *===========================================================================*/
 | |
| PRIVATE int mthread_increase_thread_pool(void)
 | |
| {
 | |
| /* Increase thread pool. No fancy algorithms, just double the size. */
 | |
|   mthread_tcb_t *new_tcb;
 | |
|   int new_no_threads, old_no_threads, i;
 | |
| 
 | |
|   old_no_threads = no_threads;
 | |
|   new_no_threads = 2 * old_no_threads;
 | |
| 
 | |
|   if (new_no_threads >= MAX_THREAD_POOL) {
 | |
|   	mthread_debug("Reached max number of threads");
 | |
|   	return(-1);
 | |
|   }
 | |
| 
 | |
|   new_tcb = realloc(threads, new_no_threads * sizeof(mthread_tcb_t));
 | |
|   if (new_tcb == NULL) {
 | |
|   	mthread_debug("Can't increase thread pool");
 | |
|   	return(-1);
 | |
|   }
 | |
| 
 | |
|   threads = new_tcb; 
 | |
|   no_threads = new_no_threads;
 | |
| 
 | |
|   /* Add newly available threads to free_threads */
 | |
|   for (i = old_no_threads; i < new_no_threads; i++) {
 | |
| 	mthread_queue_add(&free_threads, i);
 | |
| 	mthread_thread_reset(i);
 | |
|   }
 | |
| 
 | |
| #ifdef MDEBUG
 | |
|   printf("Increased thread pool from %d to %d threads\n", no_threads, new_no_threads);
 | |
| #endif
 | |
|   return(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_init				     *
 | |
|  *===========================================================================*/
 | |
| PUBLIC void mthread_init(void)
 | |
| {
 | |
| /* Initialize thread system; allocate thread structures and start creating
 | |
|  * threads.
 | |
|  */
 | |
| 
 | |
|   if (!initialized) {
 | |
|   	int i;
 | |
|   	no_threads = NO_THREADS;
 | |
|   	used_threads = 0;
 | |
|   	running_main_thread = 1;/* mthread_init can only be called from the
 | |
|   				 * main thread. Calling it from a thread will
 | |
|   				 * not enter this clause.
 | |
|   				 */
 | |
| 
 | |
|   	if (getcontext(&(mainthread.m_context)) == -1)
 | |
|   		mthread_panic("Couldn't save state for main thread");
 | |
|   	current_thread = NO_THREAD;
 | |
| 
 | |
| 	/* Allocate a bunch of thread control blocks */
 | |
| 	threads = malloc(no_threads * sizeof(mthread_tcb_t));
 | |
| 	if (threads == NULL)
 | |
| 		mthread_panic("No memory, can't initialize threads");
 | |
| 
 | |
| 	mthread_init_valid_mutexes();
 | |
| 	mthread_init_valid_conditions();
 | |
| 	mthread_init_valid_attributes();
 | |
| 	mthread_init_scheduler();
 | |
| 
 | |
| 	/* Put initial threads on the free threads queue */
 | |
| 	mthread_queue_init(&free_threads);
 | |
| 	for (i = 0; i < no_threads; i++) {
 | |
| 		mthread_queue_add(&free_threads, i);
 | |
| 		mthread_thread_reset(i);
 | |
| 	}
 | |
| 
 | |
| 	/* Initialize the fallback thread */
 | |
| 	if (getcontext(FALLBACK_CTX) == -1)
 | |
| 		mthread_panic("Could not initialize fallback thread");
 | |
| 	FALLBACK_CTX->uc_link = &(mainthread.m_context);
 | |
| 	FALLBACK_CTX->uc_stack.ss_sp = malloc(STACKSZ);
 | |
| 	FALLBACK_CTX->uc_stack.ss_size = STACKSZ;
 | |
| 	if (FALLBACK_CTX->uc_stack.ss_sp == NULL)
 | |
| 		mthread_panic("Could not allocate stack space to fallback "
 | |
| 			      "thread");
 | |
|   	makecontext(FALLBACK_CTX, (void (*) (void)) mthread_fallback, 0);
 | |
| 
 | |
| 	initialized = 1;
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_join				     *
 | |
|  *===========================================================================*/
 | |
| PUBLIC int mthread_join(join, value)
 | |
| mthread_thread_t join;
 | |
| void **value;
 | |
| {
 | |
| /* Wait for a thread to stop running and copy the result. */
 | |
| 
 | |
|   mthread_init();	/* Make sure mthreads is initialized */
 | |
| 
 | |
|   if (!isokthreadid(join)) {
 | |
|   	errno = ESRCH;
 | |
|   	return(-1);
 | |
|   } else if (join == current_thread) {
 | |
| 	errno = EDEADLK;
 | |
| 	return(-1);
 | |
|   } else if (threads[join].m_state == DEAD) {
 | |
|   	errno = ESRCH;
 | |
|   	return(-1);
 | |
|   } else if (threads[join].m_attr.a_detachstate == MTHREAD_CREATE_DETACHED) {
 | |
| 	errno = EINVAL;
 | |
| 	return(-1);
 | |
|   }
 | |
| 
 | |
|   /* When the thread hasn't exited yet, we have to wait for that to happen */
 | |
|   if (threads[join].m_state != EXITING) {
 | |
|   	mthread_cond_t *c;
 | |
|   	mthread_mutex_t *m;
 | |
| 
 | |
|   	c = &(threads[join].m_exited);
 | |
|   	m = &(threads[join].m_exitm);
 | |
| 
 | |
|   	if (mthread_mutex_init(m, NULL) != 0)
 | |
| 		mthread_panic("Couldn't initialize mutex to join\n");
 | |
| 
 | |
| 	if (mthread_mutex_lock(m) != 0)
 | |
| 		mthread_panic("Couldn't lock mutex to join\n");
 | |
| 
 | |
| 	if (mthread_cond_wait(c, m) != 0) 
 | |
| 		mthread_panic("Couldn't wait for join condition\n");
 | |
| 		
 | |
| 	if (mthread_mutex_unlock(m) != 0)
 | |
| 		mthread_panic("Couldn't unlock mutex to join\n");
 | |
| 
 | |
| 	if (mthread_mutex_destroy(m) != 0)
 | |
| 		mthread_panic("Couldn't destroy mutex to join\n");
 | |
|   }
 | |
| 
 | |
|   /* Thread has exited; copy results */
 | |
|   if(value != NULL)
 | |
| 	*value = threads[join].m_result;
 | |
| 
 | |
|   /* Deallocate resources */
 | |
|   mthread_thread_stop(join);
 | |
|   return(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_once				     *
 | |
|  *===========================================================================*/
 | |
| PUBLIC int mthread_once(once, proc)
 | |
| mthread_once_t *once;
 | |
| void (*proc)(void);
 | |
| {
 | |
| /* Run procedure proc just once */
 | |
| 
 | |
|   mthread_init();	/* Make sure mthreads is initialized */
 | |
| 
 | |
|   if (once == NULL || proc == NULL) {
 | |
|   	errno = EINVAL;
 | |
|   	return(-1);
 | |
|   } 
 | |
| 
 | |
|   if (*once != 1) proc();
 | |
|   *once = 1;
 | |
|   return(0);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_self				     *
 | |
|  *===========================================================================*/
 | |
| PUBLIC mthread_thread_t mthread_self(void)
 | |
| {
 | |
| /* Return the thread id of the thread calling this function. */
 | |
| 
 | |
|   mthread_init();	/* Make sure mthreads is initialized */
 | |
| 
 | |
|   return(current_thread);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_thread_init			     *
 | |
|  *===========================================================================*/
 | |
| PRIVATE void mthread_thread_init(thread, tattr, proc, arg)
 | |
| mthread_thread_t thread;
 | |
| mthread_attr_t *tattr;
 | |
| void (*proc)(void *);
 | |
| void *arg;
 | |
| {
 | |
| /* Initialize a thread so that it, when unsuspended, will run the given
 | |
|  * procedure with the given parameter. The thread is marked as runnable.
 | |
|  */
 | |
| 
 | |
| #define THIS_CTX (&(threads[thread].m_context))
 | |
|   size_t stacksize;
 | |
|   char *stackaddr;
 | |
| 
 | |
|   threads[thread].m_next = NO_THREAD;
 | |
|   threads[thread].m_state = DEAD;
 | |
|   threads[thread].m_proc = (void *(*)(void *)) proc; /* Yikes */
 | |
|   threads[thread].m_arg = arg;
 | |
|   /* Threads use a copy of the provided attributes. This way, if another
 | |
|    * thread modifies the attributes (such as detach state), already running
 | |
|    * threads are not affected.
 | |
|    */
 | |
|   if (tattr != NULL)
 | |
|   	threads[thread].m_attr = *((struct __mthread_attr *) *tattr);
 | |
|   else {
 | |
|   	threads[thread].m_attr = default_attr;
 | |
|   }
 | |
| 
 | |
|   if (mthread_cond_init(&(threads[thread].m_exited), NULL) != 0)
 | |
|   	mthread_panic("Could not initialize thread");
 | |
| 
 | |
|   /* First set the fallback thread, */
 | |
|   THIS_CTX->uc_link = FALLBACK_CTX;
 | |
| 
 | |
|   /* then construct this thread's context to run procedure proc. */
 | |
|   if (getcontext(THIS_CTX) == -1)
 | |
|   	mthread_panic("Failed to initialize context state");
 | |
| 
 | |
|   stacksize = threads[thread].m_attr.a_stacksize;
 | |
|   stackaddr = threads[thread].m_attr.a_stackaddr;
 | |
| 
 | |
|   if (stacksize == (size_t) 0)
 | |
| 	stacksize = (size_t) MTHREAD_STACK_MIN;
 | |
| 
 | |
|   if (stackaddr == NULL) {
 | |
| 	/* Allocate stack space */
 | |
|   	THIS_CTX->uc_stack.ss_sp = malloc(stacksize);
 | |
| 	if (THIS_CTX->uc_stack.ss_sp == NULL)
 | |
|   		mthread_panic("Failed to allocate stack to thread");
 | |
|   } else
 | |
|   	THIS_CTX->uc_stack.ss_sp = stackaddr;
 | |
| 
 | |
|   THIS_CTX->uc_stack.ss_size = stacksize;
 | |
|   makecontext(THIS_CTX, mthread_trampoline, 0);
 | |
| 
 | |
|   mthread_unsuspend(thread); /* Make thread runnable */
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_thread_reset			     *
 | |
|  *===========================================================================*/
 | |
| PRIVATE void mthread_thread_reset(thread)
 | |
| mthread_thread_t thread;
 | |
| {
 | |
| /* Reset the thread to its default values. Free the allocated stack space. */
 | |
| 
 | |
|   mthread_tcb_t *rt;
 | |
|   if (!isokthreadid(thread)) mthread_panic("Invalid thread id"); 
 | |
| 
 | |
|   rt = &(threads[thread]);
 | |
| 
 | |
|   rt->m_next = NO_THREAD;
 | |
|   rt->m_state = DEAD;
 | |
|   rt->m_proc = NULL;
 | |
|   rt->m_arg = NULL;
 | |
|   rt->m_result = NULL;
 | |
|   rt->m_cond = NULL;
 | |
|   if (rt->m_context.uc_stack.ss_sp)
 | |
|   	free(rt->m_context.uc_stack.ss_sp); /* Free allocated stack */
 | |
|   rt->m_context.uc_stack.ss_sp = NULL;
 | |
|   rt->m_context.uc_stack.ss_size = 0;
 | |
|   rt->m_context.uc_link = NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_thread_stop			     *
 | |
|  *===========================================================================*/
 | |
| PRIVATE void mthread_thread_stop(thread)
 | |
| mthread_thread_t thread;
 | |
| {
 | |
| /* Stop thread from running. Deallocate resources. */
 | |
|   mthread_tcb_t *stop_thread;
 | |
| 
 | |
|   if (!isokthreadid(thread)) mthread_panic("Invalid thread id"); 
 | |
| 
 | |
|   stop_thread = &(threads[thread]);
 | |
| 
 | |
|   if (stop_thread->m_state == DEAD) {
 | |
|   	/* Already DEAD, nothing to do */
 | |
|   	return;
 | |
|   }
 | |
| 
 | |
|   mthread_thread_reset(thread);
 | |
|  
 | |
|   if (mthread_cond_destroy(&(stop_thread->m_exited)) != 0)
 | |
|   	mthread_panic("Could not destroy condition at thread deallocation\n");
 | |
| 
 | |
|   used_threads--;
 | |
|   mthread_queue_add(&free_threads, thread);
 | |
| }
 | |
| 
 | |
| 
 | |
| /*===========================================================================*
 | |
|  *				mthread_trampoline			     *
 | |
|  *===========================================================================*/
 | |
| PRIVATE void mthread_trampoline(void)
 | |
| {
 | |
| /* Execute the /current_thread's/ procedure. Store its result. */
 | |
| 
 | |
|   void *r;
 | |
| 
 | |
|   r = (threads[current_thread].m_proc)(threads[current_thread].m_arg);
 | |
|   mthread_exit(r); 
 | |
| }
 | |
| 
 | 
