From 12e167f672e122f3d5b80c60f03b7fcad0f823d3 Mon Sep 17 00:00:00 2001 From: Thomas Veerman Date: Tue, 21 Sep 2010 12:22:38 +0000 Subject: [PATCH] Add libmthread and test59 to test the implementation --- include/Makefile | 2 +- include/minix/mthread.h | 143 ++++++++ lib/Makefile | 2 +- lib/libmthread/Makefile | 16 + lib/libmthread/allocate.c | 500 +++++++++++++++++++++++++++ lib/libmthread/attribute.c | 354 +++++++++++++++++++ lib/libmthread/condition.c | 279 +++++++++++++++ lib/libmthread/global.h | 25 ++ lib/libmthread/misc.c | 91 +++++ lib/libmthread/mutex.c | 288 ++++++++++++++++ lib/libmthread/proto.h | 39 +++ lib/libmthread/queue.c | 105 ++++++ lib/libmthread/scheduler.c | 197 +++++++++++ test/Makefile | 4 +- test/run | 2 +- test/test59.c | 682 +++++++++++++++++++++++++++++++++++++ 16 files changed, 2725 insertions(+), 4 deletions(-) create mode 100644 include/minix/mthread.h create mode 100644 lib/libmthread/Makefile create mode 100644 lib/libmthread/allocate.c create mode 100644 lib/libmthread/attribute.c create mode 100644 lib/libmthread/condition.c create mode 100644 lib/libmthread/global.h create mode 100644 lib/libmthread/misc.c create mode 100644 lib/libmthread/mutex.c create mode 100644 lib/libmthread/proto.h create mode 100644 lib/libmthread/queue.c create mode 100644 lib/libmthread/scheduler.c create mode 100644 test/test59.c diff --git a/include/Makefile b/include/Makefile index 7756bf513..50554531a 100644 --- a/include/Makefile +++ b/include/Makefile @@ -19,7 +19,7 @@ INCS+= minix/a.out.h minix/bitmap.h minix/callnr.h minix/cdrom.h \ minix/acpi.h \ minix/drivers.h minix/drvlib.h minix/ds.h minix/endpoint.h \ minix/fslib.h minix/ioctl.h minix/ipc.h minix/ipcconst.h \ - minix/keymap.h minix/minlib.h minix/mq.h \ + minix/keymap.h minix/minlib.h minix/mq.h minix/mthread.h \ minix/netdriver.h minix/partition.h minix/paths.h \ minix/portio.h minix/priv.h minix/procfs.h minix/profile.h \ minix/queryparam.h \ diff --git a/include/minix/mthread.h b/include/minix/mthread.h new file mode 100644 index 000000000..2bd3e67f8 --- /dev/null +++ b/include/minix/mthread.h @@ -0,0 +1,143 @@ +#ifndef _MTHREAD_H +#define _MTHREAD_H +#define _SYSTEM + +#include /* MUST be first */ +#include /* MUST be second */ +#include +#include +#include +#include +#include +#include +#include +#include + +typedef int mthread_thread_t; +typedef int mthread_once_t; +typedef void * mthread_condattr_t; +typedef void * mthread_mutexattr_t; + +typedef struct { + mthread_thread_t head; + mthread_thread_t tail; +} mthread_queue_t; + +struct __mthread_mutex { + mthread_queue_t queue; /* Threads blocked on this mutex */ + mthread_thread_t owner; /* Thread that currently owns mutex */ + struct __mthread_mutex *prev; + struct __mthread_mutex *next; +}; +typedef struct __mthread_mutex *mthread_mutex_t; + +struct __mthread_cond { + struct __mthread_mutex *mutex; /* Associate mutex with condition */ + struct __mthread_cond *prev; + struct __mthread_cond *next; +}; +typedef struct __mthread_cond *mthread_cond_t; + +typedef enum { + CONDITION, DEAD, EXITING, FALLBACK_EXITING, MUTEX, RUNNABLE +} mthread_state_t; + +struct __mthread_attr { + size_t a_stacksize; + char *a_stackaddr; + int a_detachstate; + struct __mthread_attr *prev; + struct __mthread_attr *next; +}; +typedef struct __mthread_attr *mthread_attr_t; + +typedef struct { + mthread_thread_t m_next; /* Next thread to run */ + mthread_state_t m_state; /* Thread state */ + struct __mthread_attr m_attr; /* Thread attributes */ + struct __mthread_cond *m_cond; /* Condition variable that this thread + * might be blocking on */ + void *(*m_proc)(void *); /* Procedure to run */ + void *m_arg; /* Argument passed to procedure */ + void *m_result; /* Result after procedure returns */ + mthread_cond_t m_exited; /* Condition variable signaling this + * thread has ended */ + mthread_mutex_t m_exitm; /* Mutex to accompany exit condition */ + ucontext_t m_context; /* Thread machine context */ +} mthread_tcb_t; + +#define NO_THREAD -1 +#define MTHREAD_CREATE_JOINABLE 001 +#define MTHREAD_CREATE_DETACHED 002 +#define MTHREAD_ONCE_INIT 0 +#define MTHREAD_STACK_MIN MINSIGSTKSZ + +/* allocate.c */ +_PROTOTYPE( int mthread_create, (mthread_thread_t *thread, + mthread_attr_t *tattr, + void (*proc)(void *), void *arg) ); +_PROTOTYPE( int mthread_detach, (mthread_thread_t thread) ); +_PROTOTYPE( int mthread_equal, (mthread_thread_t l, mthread_thread_t r) ); +_PROTOTYPE( void mthread_exit, (void *value) ); +_PROTOTYPE( int mthread_join, (mthread_thread_t thread, void **value) ); +_PROTOTYPE( int mthread_once, (mthread_once_t *once, + void (*proc)(void)) ); +_PROTOTYPE( mthread_thread_t mthread_self, (void) ); + +/* attribute.c */ +_PROTOTYPE( int mthread_attr_destroy, (mthread_attr_t *tattr) ); +_PROTOTYPE( int mthread_attr_getdetachstate, (mthread_attr_t *tattr, + int *detachstate) ); +_PROTOTYPE( int mthread_attr_getstack, (mthread_attr_t *tattr, + void **stackaddr, + size_t *stacksize) ); +_PROTOTYPE( int mthread_attr_getstacksize, (mthread_attr_t *tattr, + size_t *stacksize) ); +_PROTOTYPE( int mthread_attr_init, (mthread_attr_t *tattr) ); +_PROTOTYPE( int mthread_attr_setdetachstate, (mthread_attr_t *tattr, + int detachstate) ); +_PROTOTYPE( int mthread_attr_setstack, (mthread_attr_t *tattr, + void *stackaddr, + size_t stacksize) ); +_PROTOTYPE( int mthread_attr_setstacksize, (mthread_attr_t *tattr, + size_t stacksize) ); + + +/* condition.c */ +_PROTOTYPE( int mthread_cond_broadcast, (mthread_cond_t *cond) ); +_PROTOTYPE( int mthread_cond_destroy, (mthread_cond_t *cond) ); +_PROTOTYPE( int mthread_cond_init, (mthread_cond_t *cond, + mthread_condattr_t *cattr) ); +_PROTOTYPE( int mthread_cond_signal, (mthread_cond_t *cond) ); +_PROTOTYPE( int mthread_cond_wait, (mthread_cond_t *cond, + mthread_mutex_t *mutex) ); + +/* misc.c */ +_PROTOTYPE( void mthread_stats, (void) ); +_PROTOTYPE( void mthread_verify_f, (char *f, int l) ); +#define mthread_verify() mthread_verify_f(__FILE__, __LINE__) + +/* mutex.c */ +_PROTOTYPE( int mthread_mutex_destroy, (mthread_mutex_t *mutex) ); +_PROTOTYPE( int mthread_mutex_init, (mthread_mutex_t *mutex, + mthread_mutexattr_t *mattr) ); +_PROTOTYPE( int mthread_mutex_lock, (mthread_mutex_t *mutex) ); +_PROTOTYPE( int mthread_mutex_trylock, (mthread_mutex_t *mutex) ); +_PROTOTYPE( int mthread_mutex_unlock, (mthread_mutex_t *mutex) ); + +/* schedule.c */ +_PROTOTYPE( void mthread_schedule, (void) ); +_PROTOTYPE( void mthread_suspend, (mthread_state_t state) ); +_PROTOTYPE( void mthread_unsuspend, (mthread_thread_t thread) ); +_PROTOTYPE( void mthread_init, (void) ); +_PROTOTYPE( int mthread_yield, (void) ); +_PROTOTYPE( void mthread_yield_all, (void) ); + +/* queue.c */ +_PROTOTYPE( void mthread_queue_init, (mthread_queue_t *queue) ); +_PROTOTYPE( void mthread_queue_add, (mthread_queue_t *queue, + mthread_thread_t thread) ); +_PROTOTYPE( mthread_thread_t mthread_queue_remove, (mthread_queue_t *queue)); +_PROTOTYPE( int mthread_queue_isempty, (mthread_queue_t *queue) ); + +#endif diff --git a/lib/Makefile b/lib/Makefile index 8ea7e3f4c..8f6e868c6 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -2,7 +2,7 @@ SUBDIR= csu libc libcurses libdriver libnetdriver libend libedit libm libsys \ libtimers libutil libbz2 libl libhgfs libz libfetch libarchive \ - libvtreefs libaudiodriver + libvtreefs libaudiodriver libmthread .if ${COMPILER_TYPE} == "ack" SUBDIR+= ack/libd ack/libe ack/libfp ack/liby diff --git a/lib/libmthread/Makefile b/lib/libmthread/Makefile new file mode 100644 index 000000000..b74280037 --- /dev/null +++ b/lib/libmthread/Makefile @@ -0,0 +1,16 @@ +# Makefile for libmthread + +CPPFLAGS+=-O -D_MINIX -D_POSIX_SOURCE -Wall + +LIB= mthread + +SRCS= \ + allocate.c \ + attribute.c \ + mutex.c \ + misc.c \ + queue.c \ + condition.c \ + scheduler.c + +.include diff --git a/lib/libmthread/allocate.c b/lib/libmthread/allocate.c new file mode 100644 index 000000000..2c769310e --- /dev/null +++ b/lib/libmthread/allocate.c @@ -0,0 +1,500 @@ +#define ALLOCATE +#include +#include +#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); +} + diff --git a/lib/libmthread/attribute.c b/lib/libmthread/attribute.c new file mode 100644 index 000000000..b69664078 --- /dev/null +++ b/lib/libmthread/attribute.c @@ -0,0 +1,354 @@ +#include +#include "proto.h" +#include "global.h" + +PRIVATE struct __mthread_attr *va_front, *va_rear; +FORWARD _PROTOTYPE( void mthread_attr_add, (mthread_attr_t *a) ); +FORWARD _PROTOTYPE( void mthread_attr_remove, (mthread_attr_t *a) ); +FORWARD _PROTOTYPE( int mthread_attr_valid, (mthread_attr_t *a) ); + +/*===========================================================================* + * mthread_init_valid_attributes * + *===========================================================================*/ +PUBLIC void mthread_init_valid_attributes(void) +{ +/* Initialize list of valid attributs */ + va_front = va_rear = NULL; +} + + +/*===========================================================================* + * mthread_attr_add * + *===========================================================================*/ +PRIVATE void mthread_attr_add(a) +mthread_attr_t *a; +{ +/* Add attribute to list of valid, initialized attributes */ + + if (va_front == NULL) { /* Empty list */ + va_front = *a; + (*a)->prev = NULL; + } else { + va_rear->next = *a; + (*a)->prev = va_rear; + } + + (*a)->next = NULL; + va_rear = *a; +} + + +/*===========================================================================* + * mthread_attr_destroy * + *===========================================================================*/ +PUBLIC int mthread_attr_destroy(attr) +mthread_attr_t *attr; +{ +/* Invalidate attribute and deallocate resources. */ + + mthread_init(); /* Make sure mthreads is initialized */ + + if (attr == NULL) { + errno = EINVAL; + return(-1); + } + + if (!mthread_attr_valid(attr)) { + errno = EINVAL; + return(-1); + } + + /* Valide attribute; invalidate it */ + mthread_attr_remove(attr); + free(*attr); + *attr = NULL; + + return(0); +} + + +/*===========================================================================* + * mthread_attr_init * + *===========================================================================*/ +PUBLIC int mthread_attr_init(attr) +mthread_attr_t *attr; /* Attribute */ +{ +/* Initialize the attribute to a known state. */ + struct __mthread_attr *a; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (attr == NULL) { + errno = EAGAIN; + return(-1); + } else if (mthread_attr_valid(attr)) { + errno = EBUSY; + return(-1); + } + + if ((a = malloc(sizeof(struct __mthread_attr))) == NULL) + return(-1); + + a->a_detachstate = MTHREAD_CREATE_JOINABLE; + a->a_stackaddr = NULL; + a->a_stacksize = (size_t) 0; + + *attr = (mthread_attr_t) a; + mthread_attr_add(attr); /* Validate attribute: attribute now in use */ + + return(0); +} + +/*===========================================================================* + * mthread_attr_getdetachstate * + *===========================================================================*/ +PUBLIC int mthread_attr_getdetachstate(attr, detachstate) +mthread_attr_t *attr; +int *detachstate; +{ +/* Get detachstate of a thread attribute */ + struct __mthread_attr *a; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (attr == NULL) { + errno = EINVAL; + return(-1); + } + + a = (struct __mthread_attr *) *attr; + if (!mthread_attr_valid(attr)) { + errno = EINVAL; + return(-1); + } + + *detachstate = a->a_detachstate; + + return(0); +} + + +/*===========================================================================* + * mthread_attr_setdetachstate * + *===========================================================================*/ +PUBLIC int mthread_attr_setdetachstate(attr, detachstate) +mthread_attr_t *attr; +int detachstate; +{ +/* Set detachstate of a thread attribute */ + struct __mthread_attr *a; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (attr == NULL) { + errno = EINVAL; + return(-1); + } + + a = (struct __mthread_attr *) *attr; + if (!mthread_attr_valid(attr)) { + errno = EINVAL; + return(-1); + } else if(detachstate != MTHREAD_CREATE_JOINABLE && + detachstate != MTHREAD_CREATE_DETACHED) { + errno = EINVAL; + return(-1); + } + + a->a_detachstate = detachstate; + + return(0); +} + + +/*===========================================================================* + * mthread_attr_getstack * + *===========================================================================*/ +PUBLIC int mthread_attr_getstack(attr, stackaddr, stacksize) +mthread_attr_t *attr; +void **stackaddr; +size_t *stacksize; +{ +/* Get stack attribute */ + struct __mthread_attr *a; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (attr == NULL) { + errno = EINVAL; + return(-1); + } + + a = (struct __mthread_attr *) *attr; + if (!mthread_attr_valid(attr)) { + errno = EINVAL; + return(-1); + } + + *stackaddr = a->a_stackaddr; + *stacksize = a->a_stacksize; + + return(0); +} + + +/*===========================================================================* + * mthread_attr_getstacksize * + *===========================================================================*/ +PUBLIC int mthread_attr_getstacksize(attr, stacksize) +mthread_attr_t *attr; +size_t *stacksize; +{ +/* Get stack size attribute */ + struct __mthread_attr *a; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (attr == NULL) { + errno = EINVAL; + return(-1); + } + + a = (struct __mthread_attr *) *attr; + if (!mthread_attr_valid(attr)) { + errno = EINVAL; + return(-1); + } + + *stacksize = a->a_stacksize; + + return(0); +} + + +/*===========================================================================* + * mthread_attr_setstack * + *===========================================================================*/ +PUBLIC int mthread_attr_setstack(attr, stackaddr, stacksize) +mthread_attr_t *attr; +void *stackaddr; +size_t stacksize; +{ +/* Set stack attribute */ + struct __mthread_attr *a; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (attr == NULL) { + errno = EINVAL; + return(-1); + } + + a = (struct __mthread_attr *) *attr; + if (!mthread_attr_valid(attr) || stacksize < MTHREAD_STACK_MIN) { + errno = EINVAL; + return(-1); + } + /* We don't care about address alignment (POSIX standard). The ucontext + * system calls will make sure that the provided stack will be aligned (at + * the cost of some memory if needed). + */ + + a->a_stackaddr = stackaddr; + a->a_stacksize = stacksize; + + return(0); +} + + +/*===========================================================================* + * mthread_attr_setstacksize * + *===========================================================================*/ +PUBLIC int mthread_attr_setstacksize(attr, stacksize) +mthread_attr_t *attr; +size_t stacksize; +{ +/* Set stack size attribute */ + struct __mthread_attr *a; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (attr == NULL) { + errno = EINVAL; + return(-1); + } + + a = (struct __mthread_attr *) *attr; + if (!mthread_attr_valid(attr) || stacksize < MTHREAD_STACK_MIN) { + errno = EINVAL; + return(-1); + } + + a->a_stacksize = stacksize; + + return(0); +} + + +/*===========================================================================* + * mthread_attr_remove * + *===========================================================================*/ +PRIVATE void mthread_attr_remove(a) +mthread_attr_t *a; +{ +/* Remove attribute from list of valid, initialized attributes */ + + if ((*a)->prev == NULL) + va_front = (*a)->next; + else + (*a)->prev->next = (*a)->next; + + if ((*a)->next == NULL) + va_rear = (*a)->prev; + else + (*a)->next->prev = (*a)->prev; +} + + +/*===========================================================================* + * mthread_attr_valid * + *===========================================================================*/ +PRIVATE int mthread_attr_valid(a) +mthread_attr_t *a; +{ +/* Check to see if attribute is on the list of valid attributes */ + struct __mthread_attr *loopitem; + + mthread_init(); /* Make sure mthreads is initialized */ + + loopitem = va_front; + + while (loopitem != NULL) { + if (loopitem == *a) + return(1); + + loopitem = loopitem->next; + } + + return(0); +} + + +/*===========================================================================* + * mthread_attr_verify * + *===========================================================================*/ +#ifdef MDEBUG +PUBLIC int mthread_attr_verify(void) +{ +/* Return true when no attributes are in use */ + struct __mthread_attr *loopitem; + + mthread_init(); /* Make sure mthreads is initialized */ + + loopitem = va_front; + + while (loopitem != NULL) { + loopitem = loopitem->next; + return(0); + } + + return(1); +} +#endif + + diff --git a/lib/libmthread/condition.c b/lib/libmthread/condition.c new file mode 100644 index 000000000..e86380fcf --- /dev/null +++ b/lib/libmthread/condition.c @@ -0,0 +1,279 @@ +#include +#include "proto.h" +#include "global.h" + +PRIVATE struct __mthread_cond *vc_front, *vc_rear; +FORWARD _PROTOTYPE( void mthread_cond_add, (mthread_cond_t *c) ); +FORWARD _PROTOTYPE( void mthread_cond_remove, (mthread_cond_t *c) ); +FORWARD _PROTOTYPE( int mthread_cond_valid, (mthread_cond_t *c) ); + + +/*===========================================================================* + * mthread_init_valid_conditions * + *===========================================================================*/ +PUBLIC void mthread_init_valid_conditions(void) +{ +/* Initialize condition variable list */ + vc_front = vc_rear = NULL; +} + + +/*===========================================================================* + * mthread_cond_add * + *===========================================================================*/ +PRIVATE void mthread_cond_add(c) +mthread_cond_t *c; +{ +/* Add condition to list of valid, initialized conditions */ + + if (vc_front == NULL) { /* Empty list */ + vc_front = *c; + (*c)->prev = NULL; + } else { + vc_rear->next = *c; + (*c)->prev = vc_rear; + } + + (*c)->next = NULL; + vc_rear = *c; +} + + +/*===========================================================================* + * mthread_cond_broadcast * + *===========================================================================*/ +PUBLIC int mthread_cond_broadcast(cond) +mthread_cond_t *cond; +{ +/* Signal all threads waiting for condition 'cond'. */ + int i; + + mthread_init(); /* Make sure mthreads is initialized */ + + if(cond == NULL) { + errno = EINVAL; + return(-1); + } + + if (!mthread_cond_valid(cond)) { + errno = EINVAL; + return(-1); + } + + for (i = 0; i < no_threads; i++) + if (threads[i].m_state == CONDITION && threads[i].m_cond == *cond) + mthread_unsuspend(i); + + return(0); +} + + +/*===========================================================================* + * mthread_cond_destroy * + *===========================================================================*/ +PUBLIC int mthread_cond_destroy(cond) +mthread_cond_t *cond; +{ +/* Destroy a condition variable. Make sure it's not in use */ + int i; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (cond == NULL) { + errno = EINVAL; + return(-1); + } + + if (!mthread_cond_valid(cond)) { + errno = EINVAL; + return(-1); + } + + /* Is another thread currently using this condition variable? */ + for (i = 0; i < no_threads; i++) { + if (threads[i].m_state == CONDITION && threads[i].m_cond == *cond) { + errno = EBUSY; + return(-1); + } + } + + /* Not in use; invalidate it. */ + mthread_cond_remove(cond); + free(*cond); + *cond = NULL; + + return(0); +} + + +/*===========================================================================* + * mthread_cond_init * + *===========================================================================*/ +PUBLIC int mthread_cond_init(cond, cattr) +mthread_cond_t *cond; +mthread_condattr_t *cattr; +{ +/* Initialize condition variable to a known state. cattr is ignored */ + struct __mthread_cond *c; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (cond == NULL) { + errno = EINVAL; + return(-1); + } else if (cattr != NULL) { + errno = ENOSYS; + return(-1); + } + + if (mthread_cond_valid(cond)) { + /* Already initialized */ + errno = EBUSY; + return(-1); + } + + if ((c = malloc(sizeof(struct __mthread_cond))) == NULL) + return(-1); + + c->mutex = NULL; + *cond = (mthread_cond_t) c; + mthread_cond_add(cond); + + return(0); +} + + +/*===========================================================================* + * mthread_cond_remove * + *===========================================================================*/ +PRIVATE void mthread_cond_remove(c) +mthread_cond_t *c; +{ +/* Remove condition from list of valid, initialized conditions */ + + if ((*c)->prev == NULL) + vc_front = (*c)->next; + else + (*c)->prev->next = (*c)->next; + + if ((*c)->next == NULL) + vc_rear = (*c)->prev; + else + (*c)->next->prev = (*c)->prev; + +} + + +/*===========================================================================* + * mthread_cond_signal * + *===========================================================================*/ +PUBLIC int mthread_cond_signal(cond) +mthread_cond_t *cond; +{ +/* Signal a thread that condition 'cond' was met. Just a single thread. */ + int i; + + mthread_init(); /* Make sure mthreads is initialized */ + + if(cond == NULL) { + errno = EINVAL; + return(-1); + } + + if (!mthread_cond_valid(cond)) { + errno = EINVAL; + return(-1); + } + + for (i = 0; i < no_threads; i++) { + if (threads[i].m_state == CONDITION && threads[i].m_cond == *cond) { + mthread_unsuspend(i); + break; + } + } + + return(0); +} + + +/*===========================================================================* + * mthread_cond_valid * + *===========================================================================*/ +PRIVATE int mthread_cond_valid(c) +mthread_cond_t *c; +{ +/* Check to see if cond is on the list of valid conditions */ + struct __mthread_cond *loopitem; + + loopitem = vc_front; + + while (loopitem != NULL) { + if (loopitem == *c) + return(1); + + loopitem = loopitem->next; + } + + return(0); +} + + +/*===========================================================================* + * mthread_cond_verify * + *===========================================================================*/ +#ifdef MDEBUG +PUBLIC int mthread_cond_verify(void) +{ +/* Return true in case no condition variables are in use. */ + + mthread_init(); /* Make sure mthreads is initialized */ + + return(vc_front == NULL); +} +#endif + + +/*===========================================================================* + * mthread_cond_wait * + *===========================================================================*/ +PUBLIC int mthread_cond_wait(cond, mutex) +mthread_cond_t *cond; +mthread_mutex_t *mutex; +{ +/* Wait for a condition to be signaled */ + struct __mthread_cond *c; + struct __mthread_mutex *m; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (cond == NULL || mutex == NULL) { + errno = EINVAL; + return(-1); + } + + c = (struct __mthread_cond *) *cond; + m = (struct __mthread_mutex *) *mutex; + + if (!mthread_cond_valid(cond) || !mthread_mutex_valid(mutex)) { + errno = EINVAL; + return(-1); + } + + c->mutex = m; /* Remember we're using this mutex in a cond_wait */ + if (mthread_mutex_unlock(mutex) != 0) /* Fails when we're not the owner */ + return(-1); + + threads[current_thread].m_cond = c; /* Register condition variable. */ + + mthread_suspend(CONDITION); + + /* When execution returns here, the condition was met. Lock mutex again. */ + c->mutex = NULL; /* Forget about this mutex */ + threads[current_thread].m_cond = NULL; /* ... and condition var */ + if (mthread_mutex_lock(mutex) != 0) + return(-1); + + return(0); +} + + diff --git a/lib/libmthread/global.h b/lib/libmthread/global.h new file mode 100644 index 000000000..0dc58716b --- /dev/null +++ b/lib/libmthread/global.h @@ -0,0 +1,25 @@ +/* EXTERN should be extern, except for the allocate file */ +#ifdef ALLOCATE +#undef EXTERN +#define EXTERN +#endif + +#include + +#define NO_THREADS 3 +#define MAX_THREAD_POOL 1000 +#define STACKSZ 4096 +#define isokthreadid(i) (i >= 0 && i < no_threads) + +EXTERN mthread_thread_t current_thread; +EXTERN int ret_code; +EXTERN mthread_queue_t free_threads; +EXTERN mthread_queue_t run_queue; /* FIFO of runnable threads */ +EXTERN mthread_tcb_t *scheduler; +EXTERN mthread_tcb_t *threads; +EXTERN mthread_tcb_t fallback; +EXTERN mthread_tcb_t mainthread; +EXTERN int no_threads; +EXTERN int used_threads; +EXTERN int running_main_thread; + diff --git a/lib/libmthread/misc.c b/lib/libmthread/misc.c new file mode 100644 index 000000000..230bd8bb8 --- /dev/null +++ b/lib/libmthread/misc.c @@ -0,0 +1,91 @@ +#include +#include +#include "proto.h" +#include "global.h" + +/*===========================================================================* + * mthread_debug_f * + *===========================================================================*/ +PUBLIC void mthread_debug_f(const char *file, int line, const char *msg) +{ + /* Print debug message */ +#ifdef MDEBUG + printf("MTH (%s:%d): %s\n", file, line, msg); +#endif +} + + +/*===========================================================================* + * mthread_panic_f * + *===========================================================================*/ +PUBLIC void mthread_panic_f(const char *file, int line, const char *msg) +{ + /* Print panic message to stdout and exit */ + printf("mthreads panic (%s:%d): ", file, line); + printf(msg); + printf("\n"); + exit(1); +} + + +/*===========================================================================* + * mthread_verify_f * + *===========================================================================*/ +#ifdef MDEBUG +PUBLIC void mthread_verify_f(char *file, int line) +{ + /* Verify library state. It is assumed this function is never called from + * a spawned thread, but from the 'main' thread. The library should be + * quiescent; no mutexes, conditions, or threads in use. All threads are to + * be in DEAD state. + */ + int i; + int threads_ok = 1, conditions_ok = 1, mutexes_ok = 1, attributes_ok = 1; + + for (i = 0; threads_ok && i < no_threads; i++) + if (threads[i].m_state != DEAD) threads_ok = 0; + + conditions_ok = mthread_cond_verify(); + mutexes_ok = mthread_mutex_verify(); + attributes_ok = mthread_attr_verify(); + + printf("(%s:%d) VERIFY ", file, line); + printf("| threads: %s |", (threads_ok ? "ok": "NOT ok")); + printf("| cond: %s |", (conditions_ok ? "ok": "NOT ok")); + printf("| mutex: %s |", (mutexes_ok ? "ok": "NOT ok")); + printf("| attr: %s |", (attributes_ok ? "ok": "NOT ok")); + printf("\n"); + + if(!threads_ok || !conditions_ok || !mutexes_ok) + mthread_panic("Library state corrupt\n"); +} +#else +PUBLIC void mthread_verify_f(char *f, int l) { ; } +#endif + + +/*===========================================================================* + * mthread_stats * + *===========================================================================*/ +PUBLIC void mthread_stats(void) +{ + int i, st_run, st_dead, st_cond, st_mutex, st_exit, st_fbexit;; + st_run = st_dead = st_cond = st_mutex = st_exit = st_fbexit = 0; + + for (i = 0; i < no_threads; i++) { + switch(threads[i].m_state) { + case RUNNABLE: st_run++; break; + case DEAD: st_dead++; break; + case MUTEX: st_mutex++; break; + case CONDITION: st_cond++; break; + case EXITING: st_exit++; break; + case FALLBACK_EXITING: st_fbexit++; break; + default: mthread_panic("Unknown state"); + } + } + + printf("Pool: %-5d In use: %-5d R: %-5d D: %-5d M: %-5d C: %-5d E: %-5d" + "F: %-5d\n", + no_threads, used_threads, st_run, st_dead, st_mutex, st_cond, + st_exit, st_fbexit); +} diff --git a/lib/libmthread/mutex.c b/lib/libmthread/mutex.c new file mode 100644 index 000000000..059744056 --- /dev/null +++ b/lib/libmthread/mutex.c @@ -0,0 +1,288 @@ +#include +#include "proto.h" +#include "global.h" + +PRIVATE struct __mthread_mutex *vm_front, *vm_rear; +FORWARD _PROTOTYPE( void mthread_mutex_add, (mthread_mutex_t *m) ); +FORWARD _PROTOTYPE( void mthread_mutex_remove, (mthread_mutex_t *m) ); + +/*===========================================================================* + * mthread_init_valid_mutexes * + *===========================================================================*/ +PUBLIC void mthread_init_valid_mutexes(void) +{ +/* Initialize list of valid mutexes */ + vm_front = vm_rear = NULL; +} + + +/*===========================================================================* + * mthread_mutex_add * + *===========================================================================*/ +PRIVATE void mthread_mutex_add(m) +mthread_mutex_t *m; +{ +/* Add mutex to list of valid, initialized mutexes */ + + if (vm_front == NULL) { /* Empty list */ + vm_front = *m; + (*m)->prev = NULL; + } else { + vm_rear->next = *m; + (*m)->prev = vm_rear; + } + + (*m)->next = NULL; + vm_rear = *m; +} + + +/*===========================================================================* + * mthread_mutex_destroy * + *===========================================================================*/ +PUBLIC int mthread_mutex_destroy(mutex) +mthread_mutex_t *mutex; +{ +/* Invalidate mutex and deallocate resources. */ + + int i; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (mutex == NULL) { + errno = EINVAL; + return(-1); + } + + if (!mthread_mutex_valid(mutex)) { + errno = EINVAL; + return(-1); + } else if ((*mutex)->owner != NO_THREAD) { + errno = EBUSY; + return(-1); + } + + /* Check if this mutex is not associated with a condition */ + for (i = 0; i < no_threads; i++) { + if (threads[i].m_state == CONDITION) { + if (threads[i].m_cond != NULL && + threads[i].m_cond->mutex == *mutex) { + errno = EBUSY; + return(-1); + } + } + } + + /* Not in use; invalidate it */ + mthread_mutex_remove(mutex); + free(*mutex); + *mutex = NULL; + + return(0); +} + + +/*===========================================================================* + * mthread_mutex_init * + *===========================================================================*/ +PUBLIC int mthread_mutex_init(mutex, mattr) +mthread_mutex_t *mutex; /* Mutex that is to be initialized */ +mthread_mutexattr_t *mattr; /* Mutex attribute */ +{ +/* Initialize the mutex to a known state. Attributes are not supported */ + + struct __mthread_mutex *m; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (mutex == NULL) { + errno = EAGAIN; + return(-1); + } else if (mattr != NULL) { + errno = ENOSYS; + return(-1); + } else if (mthread_mutex_valid(mutex)) { + errno = EBUSY; + return(-1); + } + + if ((m = malloc(sizeof(struct __mthread_mutex))) == NULL) + return(-1); + + mthread_queue_init( &(m->queue) ); + m->owner = NO_THREAD; + *mutex = (mthread_mutex_t) m; + mthread_mutex_add(mutex); /* Validate mutex; mutex now in use */ + + return(0); +} + +/*===========================================================================* + * mthread_mutex_lock * + *===========================================================================*/ +PUBLIC int mthread_mutex_lock(mutex) +mthread_mutex_t *mutex; /* Mutex that is to be locked */ +{ +/* Try to lock this mutex. If already locked, append the current thread to + * FIFO queue associated with this mutex and suspend the thread. */ + + struct __mthread_mutex *m; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (mutex == NULL) { + errno = EINVAL; + return(-1); + } + + m = (struct __mthread_mutex *) *mutex; + if (!mthread_mutex_valid(&m)) { + errno = EINVAL; + return(-1); + } else if (m->owner == NO_THREAD) { /* Not locked */ + m->owner = current_thread; + } else if (m->owner == current_thread) { + errno = EDEADLK; + return(-1); + } else { + mthread_queue_add( &(m->queue), current_thread); + mthread_suspend(MUTEX); + } + + /* When we get here we acquired the lock. */ + return(0); +} + + +/*===========================================================================* + * mthread_mutex_remove * + *===========================================================================*/ +PRIVATE void mthread_mutex_remove(m) +mthread_mutex_t *m; +{ +/* Remove mutex from list of valid, initialized mutexes */ + + if ((*m)->prev == NULL) + vm_front = (*m)->next; + else + (*m)->prev->next = (*m)->next; + + if ((*m)->next == NULL) + vm_rear = (*m)->prev; + else + (*m)->next->prev = (*m)->prev; +} + + +/*===========================================================================* + * mthread_mutex_trylock * + *===========================================================================*/ +PUBLIC int mthread_mutex_trylock(mutex) +mthread_mutex_t *mutex; /* Mutex that is to be locked */ +{ +/* Try to lock this mutex and return OK. If already locked, return error. */ + + struct __mthread_mutex *m; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (mutex == NULL) { + errno = EINVAL; + return(-1); + } + + m = (struct __mthread_mutex *) *mutex; + if (!mthread_mutex_valid(&m)) { + errno = EINVAL; + return(-1); + } else if (m->owner == NO_THREAD) { + m->owner = current_thread; + return(0); + } + + errno = EBUSY; + return(-1); +} + + +/*===========================================================================* + * mthread_mutex_unlock * + *===========================================================================*/ +PUBLIC int mthread_mutex_unlock(mutex) +mthread_mutex_t *mutex; /* Mutex that is to be unlocked */ +{ +/* Unlock a previously locked mutex. If there is a pending lock for this mutex + * by another thread, mark that thread runnable. */ + + struct __mthread_mutex *m; + + mthread_init(); /* Make sure mthreads is initialized */ + + if (mutex == NULL) { + errno = EINVAL; + return(-1); + } + + m = (struct __mthread_mutex *) *mutex; + if (!mthread_mutex_valid(&m)) { + errno = EINVAL; + return(-1); + } else if (m->owner != current_thread) { + errno = EPERM; + return(-1); /* Can't unlock a mutex locked by another thread. */ + } + + m->owner = mthread_queue_remove( &(m->queue) ); + if (m->owner != NO_THREAD) mthread_unsuspend(m->owner); + return(0); +} + + +/*===========================================================================* + * mthread_mutex_valid * + *===========================================================================*/ +PUBLIC int mthread_mutex_valid(m) +mthread_mutex_t *m; +{ +/* Check to see if mutex is on the list of valid mutexes */ + struct __mthread_mutex *loopitem; + + mthread_init(); /* Make sure mthreads is initialized */ + + loopitem = vm_front; + + while (loopitem != NULL) { + if (loopitem == *m) + return(1); + + loopitem = loopitem->next; + } + + return(0); +} + + +/*===========================================================================* + * mthread_mutex_verify * + *===========================================================================*/ +#ifdef MDEBUG +PUBLIC int mthread_mutex_verify(void) +{ + /* Return true when no mutexes are in use */ + int r = 1; + struct __mthread_mutex *loopitem; + + mthread_init(); /* Make sure mthreads is initialized */ + + loopitem = vm_front; + + while (loopitem != NULL) { + loopitem = loopitem->next; + r = 0; + } + + return(r); +} +#endif + + diff --git a/lib/libmthread/proto.h b/lib/libmthread/proto.h new file mode 100644 index 000000000..3d77a6590 --- /dev/null +++ b/lib/libmthread/proto.h @@ -0,0 +1,39 @@ +#ifndef __MTHREAD_PROTO_H__ +#define __MTHREAD_PROTO_H__ + +/* attribute.c */ +_PROTOTYPE( void mthread_init_valid_attributes, (void) ); +#ifdef MDEBUG +_PROTOTYPE( int mthread_attr_verify, (void) ); +#endif + +/* cond.c */ +_PROTOTYPE( void mthread_init_valid_conditions, (void) ); +#ifdef MDEBUG +_PROTOTYPE( int mthread_cond_verify, (void) ); +#endif + +/* misc.c */ +#define mthread_panic(m) mthread_panic_f(__FILE__, __LINE__, (m)) +_PROTOTYPE( void mthread_panic_f, (const char *file, int line, + const char *msg) ); +#define mthread_debug(m) mthread_debug_f(__FILE__, __LINE__, (m)) +_PROTOTYPE( void mthread_debug_f, (const char *file, int line, + const char *msg) ); + +/* mutex.c */ +_PROTOTYPE( void mthread_init_valid_mutexes, (void) ); +_PROTOTYPE( int mthread_mutex_valid, (mthread_mutex_t *mutex) ); +#ifdef MDEBUG +_PROTOTYPE( int mthread_mutex_verify, (void) ); +#endif + + +/* schedule.c */ +_PROTOTYPE( int mthread_getcontext, (ucontext_t *ctxt) ); +_PROTOTYPE( void mthread_init_scheduler, (void) ); + +/* queue.c */ +_PROTOTYPE( void mthread_dump_queue, (mthread_queue_t *queue) ); + +#endif diff --git a/lib/libmthread/queue.c b/lib/libmthread/queue.c new file mode 100644 index 000000000..98f175000 --- /dev/null +++ b/lib/libmthread/queue.c @@ -0,0 +1,105 @@ +#include +#include "global.h" + +/*===========================================================================* + * mthread_queue_add * + *===========================================================================*/ +PUBLIC void mthread_queue_add(queue, thread) +mthread_queue_t *queue; /* Queue we want thread to append to */ +mthread_thread_t thread; +{ +/* Append a thread to the tail of the queue. As a process can be present on + * only one queue at the same time, we can use the threads array's 'next' + * pointer to point to the next thread on the queue. + */ + + if (mthread_queue_isempty(queue)) { + queue->head = queue->tail = thread; + } else { + threads[queue->tail].m_next = thread; + queue->tail = thread; /* 'thread' is the new last in line */ + } +} + + +/*===========================================================================* + * mthread_queue_init * + *===========================================================================*/ +PUBLIC void mthread_queue_init(queue) +mthread_queue_t *queue; /* Queue that has to be initialized */ +{ +/* Initialize queue to a known state */ + + queue->head = queue->tail = NO_THREAD; +} + + +/*===========================================================================* + * mthread_queue_isempty * + *===========================================================================*/ +PUBLIC int mthread_queue_isempty(queue) +mthread_queue_t *queue; +{ + return(queue->head == NO_THREAD); +} + + +/*===========================================================================* + * mthread_dump_queue * + *===========================================================================*/ +PUBLIC void mthread_dump_queue(queue) +mthread_queue_t *queue; +{ + int threshold, count = 0; + mthread_thread_t t; + threshold = no_threads; +#ifdef MDEBUG + printf("Dumping queue: "); +#endif + if(queue->head != NO_THREAD) { + t = queue->head; +#ifdef MDEBUG + printf("%d ", t); +#endif + count++; + t = threads[t].m_next; + while (t != NO_THREAD) { +#ifdef MDEBUG + printf("%d ", t); +#endif + t = threads[t].m_next; + count++; + if (count > threshold) break; + } + } else { +#ifdef MDEBUG + printf("[empty]"); +#endif + } + +#ifdef MDEBUG + printf("\n"); +#endif +} + + +/*===========================================================================* + * mthread_queue_remove * + *===========================================================================*/ +PUBLIC mthread_thread_t mthread_queue_remove(queue) +mthread_queue_t *queue; /* Queue we want a thread from */ +{ +/* Get the first thread in this queue, if there is one. */ + mthread_thread_t thread = queue->head; + + if (thread != NO_THREAD) { /* i.e., this queue is not empty */ + if (queue->head == queue->tail) /* Queue holds only one thread */ + queue->head = queue->tail = NO_THREAD; /*So mark thread empty*/ + else + /* Second thread in line is the new first */ + queue->head = threads[thread].m_next; + } + + return(thread); +} + diff --git a/lib/libmthread/scheduler.c b/lib/libmthread/scheduler.c new file mode 100644 index 000000000..a1834be1e --- /dev/null +++ b/lib/libmthread/scheduler.c @@ -0,0 +1,197 @@ +#include +#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; +} + diff --git a/test/Makefile b/test/Makefile index 5abb5df3e..b42c29879 100644 --- a/test/Makefile +++ b/test/Makefile @@ -19,7 +19,7 @@ ROOTOBJ= test11 test33 test43 test44 test46 GCCOBJ= test45-gcc test49-gcc GCCFPUOBJ= test51-gcc test52-gcc -all: $(OBJ) $(BIGOBJ) $(GCCOBJ) $(GCCFPUOBJ) $(ROOTOBJ) test57 +all: $(OBJ) $(BIGOBJ) $(GCCOBJ) $(GCCFPUOBJ) $(ROOTOBJ) test57 test59 chmod 755 *.sh run $(OBJ): @@ -115,3 +115,5 @@ test56: test56.c test57: test57.c test57loop.S if which $(GCC) >/dev/null 2>&1; then $(GCC) $(CFLAGS-GCC) -o $@ test57.c test57loop.S; fi test58: test58.c +test59: test59.c + $(CC) $(CFLAGS) -o $@ $@.c -lmthread diff --git a/test/run b/test/run index 48af18ae5..332336eb7 100755 --- a/test/run +++ b/test/run @@ -14,7 +14,7 @@ badones= # list of tests that failed tests=" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 \ 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 \ 41 42 43 44 45 45-gcc 46 47 48 49 49-gcc 50 \ - 51 51-gcc 52 52-gcc 53 54 55 56 57 58\ + 51 51-gcc 52 52-gcc 53 54 55 56 57 58 59\ sh1.sh sh2.sh" tests_no=`expr 0` diff --git a/test/test59.c b/test/test59.c new file mode 100644 index 000000000..04b1ab524 --- /dev/null +++ b/test/test59.c @@ -0,0 +1,682 @@ +/* Test the mthreads library. When the library is compiled with -DMDEBUG, you + * have to compile this test with -DMDEBUG as well or it won't link. MDEBUG + * lets you check the internal integrity of the library. */ +#include +#include + +#define thread_t mthread_thread_t +#define mutex_t mthread_mutex_t +#define cond_t mthread_cond_t +#define once_t mthread_once_t +#define attr_t mthread_attr_t + +#define MAX_ERROR 5 +#include "common.c" + +PUBLIC int errct; +PRIVATE int count, condition_met; +PRIVATE int th_a, th_b, th_c, th_d, th_e, th_f, th_g, th_h; +PRIVATE mutex_t mu[2]; +PRIVATE cond_t condition; +PRIVATE mutex_t *count_mutex, *condition_mutex; +PRIVATE once_t once; +#define ROUNDS 14 +#define THRESH1 3 +#define THRESH2 8 +#define MEG 1024*1024 +#define MAGIC 0xb4a3f1c2 + +FORWARD _PROTOTYPE( void thread_a, (void *arg) ); +FORWARD _PROTOTYPE( void thread_b, (void *arg) ); +FORWARD _PROTOTYPE( void thread_c, (void *arg) ); +FORWARD _PROTOTYPE( void thread_d, (void *arg) ); +FORWARD _PROTOTYPE( void thread_e, (void) ); +FORWARD _PROTOTYPE( void thread_f, (void *arg) ); +FORWARD _PROTOTYPE( void thread_g, (void *arg) ); +FORWARD _PROTOTYPE( void thread_h, (void *arg) ); +FORWARD _PROTOTYPE( void test_scheduling, (void) ); +FORWARD _PROTOTYPE( void test_mutex, (void) ); +FORWARD _PROTOTYPE( void test_condition, (void) ); +FORWARD _PROTOTYPE( void test_attributes, (void) ); +FORWARD _PROTOTYPE( void err, (int subtest, int error) ); + +/*===========================================================================* + * thread_a * + *===========================================================================*/ +PRIVATE void thread_a(void *arg) { + th_a++; +} + + +/*===========================================================================* + * thread_b * + *===========================================================================*/ +PRIVATE void thread_b(void *arg) { + th_b++; + if (mthread_once(&once, thread_e) != 0) err(10, 1); +} + + +/*===========================================================================* + * thread_c * + *===========================================================================*/ +PRIVATE void thread_c(void *arg) { + th_c++; +} + + +/*===========================================================================* + * thread_d * + *===========================================================================*/ +PRIVATE void thread_d(void *arg) { + th_d++; + mthread_exit(NULL); /* Thread wants to stop running */ +} + + +/*===========================================================================* + * thread_e * + *===========================================================================*/ +PRIVATE void thread_e(void) { + th_e++; +} + + +/*===========================================================================* + * thread_f * + *===========================================================================*/ +PRIVATE void thread_f(void *arg) { + if (mthread_mutex_lock(condition_mutex) != 0) err(12, 1); + th_f++; + if (mthread_cond_signal(&condition) != 0) err(12, 2); + if (mthread_mutex_unlock(condition_mutex) != 0) err(12, 3); +} + + +/*===========================================================================* + * thread_g * + *===========================================================================*/ +PRIVATE void thread_g(void *arg) { + char bigarray[MTHREAD_STACK_MIN + 1]; + if (mthread_mutex_lock(condition_mutex) != 0) err(13, 1); + memset(bigarray, '\0', MTHREAD_STACK_MIN + 1); /* Actually allocate it */ + th_g++; + if (mthread_cond_signal(&condition) != 0) err(13, 2); + if (mthread_mutex_unlock(condition_mutex) != 0) err(13, 3); +} + + +/*===========================================================================* + * thread_h * + *===========================================================================*/ +PRIVATE void thread_h(void *arg) { + char bigarray[2 * MEG]; + int reply; + if (mthread_mutex_lock(condition_mutex) != 0) err(14, 1); + memset(bigarray, '\0', 2 * MEG); /* Actually allocate it */ + th_h++; + if (mthread_cond_signal(&condition) != 0) err(14, 2); + if (mthread_mutex_unlock(condition_mutex) != 0) err(14, 3); + reply = *((int *) arg); + mthread_exit((void *) reply); +} + + +/*===========================================================================* + * err * + *===========================================================================*/ +PRIVATE void err(int sub, int error) { + /* As we're running with multiple threads, they might all clobber the + * subtest variable. This wrapper prevents that from happening. */ + + subtest = sub; + e(error); +} + + +/*===========================================================================* + * test_scheduling * + *===========================================================================*/ +PRIVATE void test_scheduling(void) +{ + int i; + thread_t t[7]; + +#ifdef MDEBUG + mthread_verify(); +#endif + th_a = th_b = th_c = th_d = th_e = 0; + + if (mthread_create(&t[0], NULL, thread_a, NULL) != 0) err(1, 1); + if (mthread_create(&t[1], NULL, thread_a, NULL) != 0) err(1, 2); + if (mthread_create(&t[2], NULL, thread_a, NULL) != 0) err(1, 3); + if (mthread_create(&t[3], NULL, thread_d, NULL) != 0) err(1, 4); + if (mthread_once(&once, thread_e) != 0) err(1, 5); + mthread_schedule(); + if (mthread_create(&t[4], NULL, thread_c, NULL) != 0) err(1, 6); + mthread_schedule(); + if (mthread_create(&t[5], NULL, thread_b, NULL) != 0) err(1, 7); + if (mthread_create(&t[6], NULL, thread_a, NULL) != 0) err(1, 8); + mthread_schedule(); + mthread_schedule(); + if (mthread_once(&once, thread_e) != 0) err(1, 9); + if (mthread_once(&once, thread_e) != 0) err(1, 10); + + if (th_a != 4) err(1, 11); + if (th_b != 1) err(1, 12); + if (th_c != 1) err(1, 13); + if (th_d != 1) err(1, 14); + if (th_e != 1) err(1, 15); + + for (i = 0; i < (sizeof(t) / sizeof(thread_t)); i++) { + if (mthread_join(t[i], NULL) != 0) err(1, 16); + if (mthread_join(t[i], NULL) == 0) err(1, 17); /*Shouldn't work twice*/ + } + +#ifdef MDEBUG + mthread_verify(); +#endif + if (mthread_create(NULL, NULL, NULL, NULL) == 0) err(1, 18); + mthread_yield(); + +#ifdef MDEBUG + mthread_verify(); +#endif + if (mthread_create(&t[6], NULL, NULL, NULL) == 0) err(1, 19); + mthread_yield(); +#ifdef MDEBUG + mthread_verify(); +#endif + if (mthread_join(0xc0ffee, NULL) == 0) err(1, 20); + mthread_yield(); + mthread_yield(); + +#ifdef MDEBUG + mthread_verify(); +#endif +} + + +/*===========================================================================* + * mutex_a * + *===========================================================================*/ +PRIVATE void mutex_a(void *arg) +{ + mutex_t *mu = (mutex_t *) arg; + mutex_t mu2; + + if (mthread_mutex_lock(&mu[0]) != 0) err(3, 1); + + /* Trying to acquire lock again should fail with EDEADLK */ + if (mthread_mutex_lock(&mu[0]) != -1) err(3, 2); + if (errno != EDEADLK) err(3, 3); + + /* Try to acquire lock on uninitialized mutex; should fail with EINVAL */ + if (mthread_mutex_lock(&mu2) != -1) { + err(3, 4); + mthread_mutex_unlock(&mu2); + } + if (errno != EINVAL) err(3, 5); + errno = 0; + if (mthread_mutex_trylock(&mu2) != -1) { + err(3, 6); + mthread_mutex_unlock(&mu2); + } + if (errno != EINVAL) err(3, 7); + + if (mthread_mutex_trylock(&mu[1]) != 0) err(3, 8); + mthread_yield(); + + if (mthread_mutex_unlock(&mu[0]) != 0) err(3, 9); + mthread_yield(); + + if (mthread_mutex_unlock(&mu[1]) != 0) err(3, 10); + + /* Try with faulty memory locations */ + if (mthread_mutex_lock(NULL) == 0) err(3, 11); + if (mthread_mutex_trylock(NULL) == 0) err(3, 12); + if (mthread_mutex_unlock(NULL) == 0) err(3, 13); +} + + +/*===========================================================================* + * mutex_b * + *===========================================================================*/ +PRIVATE void mutex_b(void *arg) +{ + mutex_t *mu = (mutex_t *) arg; + + /* At this point mutex_a thread should have acquired a lock on mu[0]. We + * should not be able to unlock it on behalf of that thread. + */ + + if (mthread_mutex_unlock(&mu[0]) != -1) err(4, 1); + if (errno != EPERM) err(4, 2); + + /* Probing mu[0] to lock it should tell us it's locked */ + if (mthread_mutex_trylock(&mu[0]) == 0) err(4, 3); + if (errno != EBUSY) err(4, 4); + + if (mthread_mutex_lock(&mu[0]) != 0) err(4, 5); + if (mthread_mutex_lock(&mu[1]) != 0) err(4, 6); + mthread_yield(); + + if (mthread_mutex_unlock(&mu[0]) != 0) err(4, 7); + mthread_yield(); + + if (mthread_mutex_unlock(&mu[1]) != 0) err(4, 8); +} + + +/*===========================================================================* + * mutex_c * + *===========================================================================*/ +PRIVATE void mutex_c(void *arg) +{ + mutex_t *mu = (mutex_t *) arg; + + if (mthread_mutex_lock(&mu[1]) != 0) err(5, 1); + mthread_yield(); + + if (mthread_mutex_unlock(&mu[1]) != 0) err(5, 2); + if (mthread_mutex_lock(&mu[0]) != 0) err(5, 3); + mthread_yield(); + + if (mthread_mutex_unlock(&mu[0]) != 0) err(5, 4); +} + + +/*===========================================================================* + * test_mutex * + *===========================================================================*/ +PRIVATE void test_mutex(void) +{ + int i; + thread_t t[3]; +#ifdef MDEBUG + mthread_verify(); +#endif + if (mthread_mutex_init(&mu[0], NULL) != 0) err(2, 1); + if (mthread_mutex_init(&mu[1], NULL) != 0) err(2, 2); + + if (mthread_create(&t[0], NULL, mutex_a, (void *) mu) != 0) err(2, 3); + if (mthread_create(&t[1], NULL, mutex_b, (void *) mu) != 0) err(2, 4); + if (mthread_create(&t[2], NULL, mutex_c, (void *) mu) != 0) err(2, 5); + + mthread_yield_all(); /* Should result in a RUNNABLE mutex_a, and a blocked + * on mutex mutex_b and mutex_c. + */ + mthread_schedule(); /* Should schedule mutex_a to release the locks on its + * mutexes. Consequently allowing mutex_b and mutex_c + * to acquire locks on the mutexes and exit. + */ + + for (i = 0; i < (sizeof(t) / sizeof(thread_t)); i++) + if (mthread_join(t[i], NULL) != 0) err(2, 6); + + if (mthread_mutex_destroy(&mu[0]) != 0) err(2, 7); + if (mthread_mutex_destroy(&mu[1]) != 0) err(2, 8); + +#ifdef MDEBUG + mthread_verify(); +#endif +} + + +/*===========================================================================* + * cond_a * + *===========================================================================*/ +PRIVATE void cond_a(void *arg) +{ + cond_t c; + int did_count = 0; + while(1) { + if (mthread_mutex_lock(condition_mutex) != 0) err(6, 1); + while (count >= THRESH1 && count <= THRESH2) { + if (mthread_cond_wait(&condition, condition_mutex) != 0) + err(6, 2); + } + if (mthread_mutex_unlock(condition_mutex) != 0) err(6, 3); + + mthread_yield(); + + if (mthread_mutex_lock(count_mutex) != 0) err(6, 4); + count++; + did_count++; + if (mthread_mutex_unlock(count_mutex) != 0) err(6, 5); + + if (count >= ROUNDS) break; + } + if (!(did_count <= count - (THRESH2 - THRESH1 + 1))) err(6, 6); + + /* Try faulty addresses */ + if (mthread_mutex_lock(condition_mutex) != 0) err(6, 7); + /* Condition c is not initialized, so whatever we do with it should fail. */ + if (mthread_cond_wait(&c, condition_mutex) == 0) err(6, 8); + if (mthread_cond_wait(NULL, condition_mutex) == 0) err(6, 9); + if (mthread_cond_signal(&c) == 0) err(6, 10); + if (mthread_mutex_unlock(condition_mutex) != 0) err(6, 11); + + /* Try again with an unlocked mutex */ + if (mthread_cond_wait(&c, condition_mutex) == 0) err(6, 12); + if (mthread_cond_signal(&c) == 0) err(6, 13); + + /* And again with an unlocked mutex, but initialized c */ + if (mthread_cond_init(&c, NULL) != 0) err(6, 14); + if (mthread_cond_wait(&c, condition_mutex) == 0) err(6, 15); + if (mthread_cond_signal(&c) != 0) err(6, 16);/*c.f., 6.10 this should work!*/ + if (mthread_cond_destroy(&c) != 0) err(6, 17); +} + + +/*===========================================================================* + * cond_b * + *===========================================================================*/ +PRIVATE void cond_b(void *arg) +{ + int did_count = 0; + while(1) { + if (mthread_mutex_lock(condition_mutex) != 0) err(7, 1); + if (count < THRESH1 || count > THRESH2) + if (mthread_cond_signal(&condition) != 0) err(7, 2); + if (mthread_mutex_unlock(condition_mutex) != 0) err(7, 3); + + mthread_yield(); + + if (mthread_mutex_lock(count_mutex) != 0) err(7, 4); + count++; + did_count++; + if (mthread_mutex_unlock(count_mutex) != 0) err(7, 5); + + if (count >= ROUNDS) break; + } + + if (!(did_count >= count - (THRESH2 - THRESH1 + 1))) err(7, 6); + +} + +/*===========================================================================* + * cond_broadcast * + *===========================================================================*/ +PRIVATE void cond_broadcast(void *arg) +{ + int rounds = 0; + if (mthread_mutex_lock(condition_mutex) != 0) err(9, 1); + + while(!condition_met) + if (mthread_cond_wait(&condition, condition_mutex) != 0) err(9, 2); + + if (mthread_mutex_unlock(condition_mutex) != 0) err(9, 3); + + if (mthread_mutex_lock(count_mutex) != 0) err(9, 4); + count++; + if (mthread_mutex_unlock(count_mutex) != 0) err(9, 5); +} + +/*===========================================================================* + * test_condition * + *===========================================================================*/ +PRIVATE void test_condition(void) +{ +#define NTHREADS 10 + int i, r; + thread_t t[2], s[NTHREADS]; + count_mutex = &mu[0]; + condition_mutex = &mu[1]; + + /* Test simple condition variable behavior: Two threads increase a counter. + * At some point one thread waits for a condition and the other thread + * signals the condition. Consequently, one thread increased the counter a + * few times less than other thread. Although the difference is 'random', + * there is a guaranteed minimum difference that we can measure. + */ + +#ifdef MDEBUG + mthread_verify(); +#endif + + if (mthread_mutex_init(count_mutex, NULL) != 0) err(8, 1); + if (mthread_mutex_init(condition_mutex, NULL) != 0) err(8, 2); + if (mthread_cond_init(&condition, NULL) != 0) err(8, 3); + count = 0; + + if (mthread_create(&t[0], NULL, cond_a, NULL) != 0) err(8, 4); + if (mthread_create(&t[1], NULL, cond_b, NULL) != 0) err(8, 5); + + for (i = 0; i < (sizeof(t) / sizeof(thread_t)); i++) + if (mthread_join(t[i], NULL) != 0) err(8, 6); + + if (mthread_mutex_destroy(count_mutex) != 0) err(8, 7); + if (mthread_mutex_destroy(condition_mutex) != 0) err(8, 8); + if (mthread_cond_destroy(&condition) != 0) err(8, 9); + + /* Let's try to destroy it again. Should fails as it's uninitialized. */ + if (mthread_cond_destroy(&condition) == 0) err(8, 10); + +#ifdef MDEBUG + mthread_verify(); +#endif + + /* Test signal broadcasting: spawn N threads that will increase a counter + * after a condition has been signaled. The counter must equal N. */ + if (mthread_mutex_init(count_mutex, NULL) != 0) err(8, 11); + if (mthread_mutex_init(condition_mutex, NULL) != 0) err(8, 12); + if (mthread_cond_init(&condition, NULL) != 0) err(8, 13); + condition_met = count = 0; + + for (i = 0; i < NTHREADS; i++) + if (mthread_create(&s[i], NULL, cond_broadcast, NULL) != 0) err(8, 14); + + /* Allow other threads to block on the condition variable. If we don't yield, + * the threads will only start running when we call mthread_join below. In + * that case the while loop in cond_broadcast will never evaluate to true. + */ + mthread_yield(); + + if (mthread_mutex_lock(condition_mutex) != 0) err(8, 15); + condition_met = 1; + if (mthread_cond_broadcast(&condition) != 0) err(8, 16); + if (mthread_mutex_unlock(condition_mutex) != 0) err(8, 17); + + for (i = 0; i < (sizeof(s) / sizeof(thread_t)); i++) + if (mthread_join(s[i], NULL) != 0) err(8, 18); + + if (count != NTHREADS) err(8, 19); + if (mthread_mutex_destroy(count_mutex) != 0) err(8, 20); + if (mthread_mutex_destroy(condition_mutex) != 0) err(8, 21); + if (mthread_cond_destroy(&condition) != 0) err(8, 22); + + /* Again, destroying the condition variable twice shouldn't work */ + if (mthread_cond_destroy(&condition) == 0) err(8, 23); + +#ifdef MDEBUG + mthread_verify(); +#endif +} + +/*===========================================================================* + * test_attributes * + *===========================================================================*/ +PRIVATE void test_attributes(void) +{ + attr_t tattr; + thread_t tid; + int detachstate = -1, status = 0; + int i, no_ints, stack_untouched = 1; + void *stackaddr, *newstackaddr; + int *stackp; + size_t stacksize, newstacksize; + +#ifdef MDEBUG + mthread_verify(); +#endif + + /* Initialize thread attribute and try to read the default values */ + if (mthread_attr_init(&tattr) != 0) err(11, 1); + if (mthread_attr_getdetachstate(&tattr, &detachstate) != 0) err(11, 2); + if (detachstate != MTHREAD_CREATE_JOINABLE) err(11, 3); + if (mthread_attr_getstack(&tattr, &stackaddr, &stacksize) != 0) err(11, 4); + if (stackaddr != NULL) err(11, 5); + if (stacksize != (size_t) 0) err(11, 6); + + /* Modify the attribute ... */ + /* Try bogus detach state value */ + if (mthread_attr_setdetachstate(&tattr, 0xc0ffee) == 0) err(11, 7); + if (mthread_attr_setdetachstate(&tattr, MTHREAD_CREATE_DETACHED) != 0) + err(11, 8); + newstacksize = (size_t) MEG; + if ((newstackaddr = malloc(newstacksize)) == NULL) err(11, 9); + if (mthread_attr_setstack(&tattr, newstackaddr, newstacksize) != 0) + err(11, 10); + /* ... and read back the new values. */ + if (mthread_attr_getdetachstate(&tattr, &detachstate) != 0) err(11, 11); + if (detachstate != MTHREAD_CREATE_DETACHED) err(11, 12); + if (mthread_attr_getstack(&tattr, &stackaddr, &stacksize) != 0) err(11, 13); + if (stackaddr != newstackaddr) err(11, 14); + if (stacksize != newstacksize) err(11, 15); + + /* Freeing the stack. Note that this is only possible because it wasn't + * actually used yet by a thread. If it was, mthread would clean it up after + * usage and this free would do something undefined. */ + free(newstackaddr); + + /* Try to allocate too small a stack; it should fail and the attribute + * values should remain as is. + */ + newstacksize = MTHREAD_STACK_MIN - 1; + if ((newstackaddr = malloc(newstacksize)) == NULL) err(11, 16); + if (mthread_attr_setstack(&tattr, newstackaddr, newstacksize) == 0) + err(11, 17); + if (mthread_attr_getstack(&tattr, &stackaddr, &stacksize) != 0) err(11, 18); + if (stackaddr == newstackaddr) err(11, 19); + if (stacksize == newstacksize) err(11, 20); + /* Again, freeing because we can. Shouldn't do it if it was actually used. */ + free(newstackaddr); + + /* Tell attribute to let the system allocate a stack for the thread and only + * dictate how big that stack should be (2 megabyte, not actually allocated + * yet). + */ + if (mthread_attr_setstack(&tattr, NULL /* System allocated */, 2*MEG) != 0) + err(11, 21); + if (mthread_attr_getstack(&tattr, &stackaddr, &stacksize) != 0) err(11, 22); + if (stackaddr != NULL) err(11, 23); + if (stacksize != 2*MEG) err(11, 24); + + /* Use set/getstacksize to set and retrieve new stack sizes */ + stacksize = 0; + if (mthread_attr_getstacksize(&tattr, &stacksize) != 0) err(11, 25); + if (stacksize != 2*MEG) err(11, 26); + newstacksize = MEG; + if (mthread_attr_setstacksize(&tattr, newstacksize) != 0) err(11, 27); + if (mthread_attr_getstacksize(&tattr, &stacksize) != 0) err(11, 28); + if (stacksize != newstacksize) err(11, 29); + if (mthread_attr_destroy(&tattr) != 0) err(11, 30); + + /* Perform same tests, but also actually use them in a thread */ + if (mthread_attr_init(&tattr) != 0) err(11, 31); + if (mthread_attr_setdetachstate(&tattr, MTHREAD_CREATE_DETACHED) != 0) + err(11, 32); + condition_mutex = &mu[0]; + if (mthread_mutex_init(condition_mutex, NULL) != 0) err(11, 33); + if (mthread_cond_init(&condition, NULL) != 0) err(11, 34); + if (mthread_mutex_lock(condition_mutex) != 0) err(11, 35); + if (mthread_create(&tid, &tattr, thread_f, NULL) != 0) err(11, 36); + /* Wait for thread_f to finish */ + if (mthread_cond_wait(&condition, condition_mutex) != 0) err(11, 37); + if (mthread_mutex_unlock(condition_mutex) != 0) err(11, 38); + if (th_f != 1) err(11, 39); + /* Joining a detached thread should fail */ + if (mthread_join(tid, NULL) == 0) err(11, 40); + if (mthread_attr_destroy(&tattr) != 0) err(11, 41); + + /* Try telling the attribute how large the stack should be */ + if (mthread_attr_init(&tattr) != 0) err(11, 42); + if (mthread_attr_setstack(&tattr, NULL, 2 * MTHREAD_STACK_MIN) != 0) + err(11, 43); + if (mthread_mutex_lock(condition_mutex) != 0) err(11, 44); + if (mthread_create(&tid, &tattr, thread_g, NULL) != 0) err(11, 45); + /* Wait for thread_g to finish */ + if (mthread_cond_wait(&condition, condition_mutex) != 0) err(11, 46); + if (mthread_mutex_unlock(condition_mutex) != 0) err(11, 47); + if (th_g != 1) err(11, 48); + if (mthread_attr_setdetachstate(&tattr, MTHREAD_CREATE_DETACHED) != 0) + err(11, 49); /* Shouldn't affect the join below, as thread is already + * running as joinable. If this attribute should be + * modified after thread creation, use mthread_detach(). + */ + if (mthread_join(tid, NULL) != 0) err(11, 50); + if (mthread_attr_destroy(&tattr) != 0) err(11, 51); + + /* Try telling the attribute how large the stack should be and where it is + * located. + */ + if (mthread_attr_init(&tattr) != 0) err(11, 52); + stacksize = 3 * MEG; + /* Make sure this test is meaningful. We have to verify that we actually + * use a custom stack. So we're going to allocate an array on the stack in + * thread_h that should at least be bigger than the default stack size + * allocated by the system. + */ + if (2 * MEG <= MTHREAD_STACK_MIN) err(11, 53); + if ((stackaddr = malloc(stacksize)) == NULL) err(11, 54); + /* Fill stack with pattern. We assume that the beginning of the stack + * should be overwritten with something and that the end should remain + * untouched. The thread will zero-fill around two-thirds of the stack with + * zeroes, so we can check if that's true. + */ + stackp = stackaddr; + no_ints = stacksize / sizeof(int); + for (i = 0; i < no_ints ; i++) + stackp[i] = MAGIC; + if (mthread_attr_setstack(&tattr, stackaddr, stacksize) != 0) err(11, 55); + if (mthread_mutex_lock(condition_mutex) != 0) err(11, 56); + if (mthread_create(&tid, &tattr, thread_h, (void *) &stacksize) != 0) + err(11, 57); + /* Wait for thread h to finish */ + if (mthread_cond_wait(&condition, condition_mutex) != 0) err(11, 58); + if (th_h != 1) err(11, 59); + + /* Verify stack hypothesis; we assume a stack is used from the top and grows + * downwards. At this point the stack should still exist, because we haven't + * 'joined' yet. After joining, the stack is cleaned up and this test becomes + * useless. */ +#if (_MINIX_CHIP == _CHIP_INTEL) + if (stackp[0] != MAGIC) err(11, 60); /* End of the stack */ + for (i = no_ints - 1 - 16; i < no_ints; i++) + if (stackp[i] != MAGIC) stack_untouched = 0; + if (stack_untouched) err(11, 61); /* Beginning of the stack */ + if (stackp[no_ints / 2] != 0) err(11, 62);/*Zero half way through the stack*/ +#else +#error "Unsupported chip for this test" +#endif + + if (mthread_join(tid, (void *) &status) != 0) err(11, 63); + if (status != stacksize) err(11, 64); + if (mthread_attr_destroy(&tattr) != 0) err(11, 65); + if (mthread_mutex_destroy(condition_mutex) != 0) err(11, 66); + if (mthread_cond_destroy(&condition) != 0) err(11, 67); + +#ifdef MDEBUG + mthread_verify(); +#endif +} + +/*===========================================================================* + * main * + *===========================================================================*/ +int main(void) +{ + errct = 0; + th_a = th_b = th_c = th_d = th_e = th_f = th_g = th_h = 0; + once = MTHREAD_ONCE_INIT; + + start(59); + mthread_init(); + test_scheduling(); + test_mutex(); + test_condition(); + test_attributes(); + quit(); +} + -- 2.44.0