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

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);
}