From a7072a5e1c06a1174624cbf6f53e37e1174a4bfc Mon Sep 17 00:00:00 2001 From: Thomas Veerman Date: Thu, 30 Sep 2010 13:44:13 +0000 Subject: [PATCH] Revamp the mthread library and update test59 Before, the 'main thread' of a process was never taken into account anywhere in the library, causing mutexes not to work properly (and consequently, neither did the condition variables). For example, if the 'main thread' (that is, the thread which is started at the beginning of a process; not a spawned thread by the library) would lock a mutex, it wasn't actually locked. --- include/minix/mthread.h | 44 ++------ lib/libmthread/allocate.c | 194 ++++++++++++++++++++------------- lib/libmthread/attribute.c | 2 +- lib/libmthread/condition.c | 68 ++++++++---- lib/libmthread/global.h | 34 ++++-- lib/libmthread/misc.c | 36 ++++--- lib/libmthread/mutex.c | 25 +++-- lib/libmthread/proto.h | 12 ++- lib/libmthread/queue.c | 59 ++++++---- lib/libmthread/scheduler.c | 63 ++++++----- test/test59.c | 216 +++++++++++++++++++++++-------------- 11 files changed, 466 insertions(+), 287 deletions(-) diff --git a/include/minix/mthread.h b/include/minix/mthread.h index e98cd8d21..7549515d4 100644 --- a/include/minix/mthread.h +++ b/include/minix/mthread.h @@ -17,14 +17,15 @@ typedef int mthread_once_t; typedef void * mthread_condattr_t; typedef void * mthread_mutexattr_t; +struct __mthread_tcb; typedef struct { - mthread_thread_t head; - mthread_thread_t tail; + struct __mthread_tcb *head; + struct __mthread_tcb *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 */ + mthread_queue_t queue; /* Queue of threads blocked on this mutex */ + mthread_thread_t owner; /* Thread ID that currently owns mutex */ struct __mthread_mutex *prev; struct __mthread_mutex *next; }; @@ -37,10 +38,6 @@ struct __mthread_cond { }; 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; @@ -50,22 +47,6 @@ struct __mthread_attr { }; 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 @@ -120,23 +101,18 @@ _PROTOTYPE( void mthread_verify_f, (char *f, int l) ); _PROTOTYPE( int mthread_mutex_destroy, (mthread_mutex_t *mutex) ); _PROTOTYPE( int mthread_mutex_init, (mthread_mutex_t *mutex, mthread_mutexattr_t *mattr) ); +#if 0 _PROTOTYPE( int mthread_mutex_lock, (mthread_mutex_t *mutex) ); +#endif +_PROTOTYPE( int mthread_mutex_lock_f, (mthread_mutex_t *mutex, + char *file, int line) ); +#define mthread_mutex_lock(x) mthread_mutex_lock_f(x, __FILE__, __LINE__) _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/libmthread/allocate.c b/lib/libmthread/allocate.c index 2c769310e..29bb188ca 100644 --- a/lib/libmthread/allocate.c +++ b/lib/libmthread/allocate.c @@ -87,22 +87,25 @@ 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 */ + mthread_tcb_t *tcb; + mthread_init(); /* Make sure libmthread is initialized */ if (!isokthreadid(detach)) { errno = ESRCH; return(-1); - } else if (threads[detach].m_state == DEAD) { + } + + tcb = mthread_find_tcb(detach); + if (tcb->m_state == MS_DEAD) { errno = ESRCH; return(-1); - } else if (threads[detach].m_attr.a_detachstate != MTHREAD_CREATE_DETACHED) { - if (threads[detach].m_state == EXITING) { + } else if (tcb->m_attr.a_detachstate != MTHREAD_CREATE_DETACHED) { + if (tcb->m_state == MS_EXITING) mthread_thread_stop(detach); - } else { - threads[detach].m_attr.a_detachstate = MTHREAD_CREATE_DETACHED; - } + else + tcb->m_attr.a_detachstate = MTHREAD_CREATE_DETACHED; } + return(0); } @@ -115,30 +118,30 @@ void *value; { /* Make a thread stop running and store the result value. */ int fallback_exit = 0; - mthread_thread_t stop; + mthread_tcb_t *tcb; - mthread_init(); /* Make sure mthreads is initialized */ + mthread_init(); /* Make sure libmthread is initialized */ - stop = current_thread; + tcb = mthread_find_tcb(current_thread); - if (threads[stop].m_state == EXITING) /* Already stopping, nothing to do. */ + if (tcb->m_state == MS_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) + if (tcb->m_state == MS_FALLBACK_EXITING) fallback_exit = 1; - threads[stop].m_result = value; - threads[stop].m_state = EXITING; + tcb->m_result = value; + tcb->m_state = MS_EXITING; - if (threads[stop].m_attr.a_detachstate == MTHREAD_CREATE_DETACHED) { - mthread_thread_stop(stop); + if (tcb->m_attr.a_detachstate == MTHREAD_CREATE_DETACHED) { + mthread_thread_stop(current_thread); } else { /* Joinable thread; notify possibly waiting thread */ - if (mthread_cond_signal(&(threads[stop].m_exited)) != 0) + if (mthread_cond_signal(&(tcb->m_exited)) != 0) mthread_panic("Couldn't signal exit"); /* The thread that's actually doing the join will eventually clean @@ -160,15 +163,18 @@ void *value; *===========================================================================*/ PRIVATE void mthread_fallback(void) { -/* The mthreads fallback thread. The idea is that every thread calls +/* The libmthread 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. */ + mthread_tcb_t *tcb; + + tcb = mthread_find_tcb(current_thread); - threads[current_thread].m_state = FALLBACK_EXITING; + tcb->m_state = MS_FALLBACK_EXITING; mthread_exit(NULL); /* Reconstruct fallback context for next invocation */ @@ -176,7 +182,25 @@ PRIVATE void mthread_fallback(void) /* Let another thread run */ mthread_schedule(); +} + +/*===========================================================================* + * mthread_find_tcb * + *===========================================================================*/ +PUBLIC mthread_tcb_t * mthread_find_tcb(thread) +mthread_thread_t thread; +{ + mthread_tcb_t *rt = NULL; + + if (!isokthreadid(thread)) mthread_panic("Invalid thread id"); + + if (thread == MAIN_THREAD) + rt = &mainthread; + else + rt = threads[thread]; + + return(rt); } @@ -186,23 +210,44 @@ PRIVATE void mthread_fallback(void) PRIVATE int mthread_increase_thread_pool(void) { /* Increase thread pool. No fancy algorithms, just double the size. */ - mthread_tcb_t *new_tcb; + 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 (old_no_threads == 0) + new_no_threads = NO_THREADS; + else + 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)); + /* Allocate space to store pointers to thread control blocks */ + if (old_no_threads == 0) /* No data yet: allocate space */ + new_tcb = calloc(new_no_threads, sizeof(mthread_tcb_t *)); + else /* Preserve existing data: reallocate space */ + new_tcb = realloc(threads, new_no_threads * sizeof(mthread_tcb_t *)); + if (new_tcb == NULL) { mthread_debug("Can't increase thread pool"); return(-1); } + /* Allocate space for thread control blocks itself */ + for (i = old_no_threads; i < new_no_threads; i++) { + new_tcb[i] = malloc(sizeof(mthread_tcb_t)); + if (new_tcb[i] == NULL) { + mthread_debug("Can't allocate space for tcb"); + return(-1); + } + memset(new_tcb[i], '\0', sizeof(mthread_tcb_t)); /* Clear entry */ + } + + /* We can breath again, let's tell the others about the good news */ threads = new_tcb; no_threads = new_no_threads; @@ -213,7 +258,8 @@ PRIVATE int mthread_increase_thread_pool(void) } #ifdef MDEBUG - printf("Increased thread pool from %d to %d threads\n", no_threads, new_no_threads); + printf("Increased thread pool from %d to %d threads\n", old_no_threads, + new_no_threads); #endif return(0); } @@ -230,7 +276,7 @@ PUBLIC void mthread_init(void) if (!initialized) { int i; - no_threads = NO_THREADS; + no_threads = 0; used_threads = 0; running_main_thread = 1;/* mthread_init can only be called from the * main thread. Calling it from a thread will @@ -239,34 +285,20 @@ PUBLIC void mthread_init(void) 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"); + current_thread = MAIN_THREAD; 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_sp = fallback_stack; 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"); + memset(fallback_stack, '\0', STACKSZ); makecontext(FALLBACK_CTX, (void (*) (void)) mthread_fallback, 0); initialized = 1; @@ -283,7 +315,9 @@ void **value; { /* Wait for a thread to stop running and copy the result. */ - mthread_init(); /* Make sure mthreads is initialized */ + mthread_tcb_t *tcb; + + mthread_init(); /* Make sure libmthread is initialized */ if (!isokthreadid(join)) { errno = ESRCH; @@ -291,21 +325,24 @@ void **value; } else if (join == current_thread) { errno = EDEADLK; return(-1); - } else if (threads[join].m_state == DEAD) { + } + + tcb = mthread_find_tcb(join); + if (tcb->m_state == MS_DEAD) { errno = ESRCH; return(-1); - } else if (threads[join].m_attr.a_detachstate == MTHREAD_CREATE_DETACHED) { + } else if (tcb->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) { + if (tcb->m_state != MS_EXITING) { mthread_cond_t *c; mthread_mutex_t *m; - c = &(threads[join].m_exited); - m = &(threads[join].m_exitm); + c = &(tcb->m_exited); + m = &(tcb->m_exitm); if (mthread_mutex_init(m, NULL) != 0) mthread_panic("Couldn't initialize mutex to join\n"); @@ -325,7 +362,7 @@ void **value; /* Thread has exited; copy results */ if(value != NULL) - *value = threads[join].m_result; + *value = tcb->m_result; /* Deallocate resources */ mthread_thread_stop(join); @@ -342,7 +379,7 @@ void (*proc)(void); { /* Run procedure proc just once */ - mthread_init(); /* Make sure mthreads is initialized */ + mthread_init(); /* Make sure libmthread is initialized */ if (once == NULL || proc == NULL) { errno = EINVAL; @@ -362,7 +399,7 @@ PUBLIC mthread_thread_t mthread_self(void) { /* Return the thread id of the thread calling this function. */ - mthread_init(); /* Make sure mthreads is initialized */ + mthread_init(); /* Make sure libmthread is initialized */ return(current_thread); } @@ -381,50 +418,52 @@ void *arg; * procedure with the given parameter. The thread is marked as runnable. */ -#define THIS_CTX (&(threads[thread].m_context)) +#define THIS_CTX (&(threads[thread]->m_context)) + mthread_tcb_t *tcb; 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; + tcb = mthread_find_tcb(thread); + tcb->m_next = NULL; + tcb->m_state = MS_DEAD; + tcb->m_proc = (void *(*)(void *)) proc; /* Yikes */ + tcb->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); + tcb->m_attr = *((struct __mthread_attr *) *tattr); else { - threads[thread].m_attr = default_attr; + tcb->m_attr = default_attr; } - if (mthread_cond_init(&(threads[thread].m_exited), NULL) != 0) + if (mthread_cond_init(&(tcb->m_exited), NULL) != 0) mthread_panic("Could not initialize thread"); /* First set the fallback thread, */ - THIS_CTX->uc_link = FALLBACK_CTX; + tcb->m_context.uc_link = FALLBACK_CTX; /* then construct this thread's context to run procedure proc. */ - if (getcontext(THIS_CTX) == -1) + if (getcontext(&(tcb->m_context)) == -1) mthread_panic("Failed to initialize context state"); - stacksize = threads[thread].m_attr.a_stacksize; - stackaddr = threads[thread].m_attr.a_stackaddr; + stacksize = tcb->m_attr.a_stacksize; + stackaddr = tcb->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) + tcb->m_context.uc_stack.ss_sp = malloc(stacksize); + if (tcb->m_context.uc_stack.ss_sp == NULL) mthread_panic("Failed to allocate stack to thread"); } else - THIS_CTX->uc_stack.ss_sp = stackaddr; + tcb->m_context.uc_stack.ss_sp = stackaddr; - THIS_CTX->uc_stack.ss_size = stacksize; - makecontext(THIS_CTX, mthread_trampoline, 0); + tcb->m_context.uc_stack.ss_size = stacksize; + makecontext(&(tcb->m_context), mthread_trampoline, 0); mthread_unsuspend(thread); /* Make thread runnable */ } @@ -441,10 +480,10 @@ mthread_thread_t thread; 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 = mthread_find_tcb(thread); + rt->m_tid = thread; + rt->m_next = NULL; + rt->m_state = MS_DEAD; rt->m_proc = NULL; rt->m_arg = NULL; rt->m_result = NULL; @@ -468,10 +507,10 @@ mthread_thread_t thread; if (!isokthreadid(thread)) mthread_panic("Invalid thread id"); - stop_thread = &(threads[thread]); + stop_thread = mthread_find_tcb(thread); - if (stop_thread->m_state == DEAD) { - /* Already DEAD, nothing to do */ + if (stop_thread->m_state == MS_DEAD) { + /* Already dead, nothing to do */ return; } @@ -492,9 +531,12 @@ PRIVATE void mthread_trampoline(void) { /* Execute the /current_thread's/ procedure. Store its result. */ + mthread_tcb_t *tcb; void *r; - r = (threads[current_thread].m_proc)(threads[current_thread].m_arg); + tcb = mthread_find_tcb(current_thread); + + r = (tcb->m_proc)(tcb->m_arg); mthread_exit(r); } diff --git a/lib/libmthread/attribute.c b/lib/libmthread/attribute.c index b69664078..dda0aaf3d 100644 --- a/lib/libmthread/attribute.c +++ b/lib/libmthread/attribute.c @@ -1,6 +1,6 @@ #include -#include "proto.h" #include "global.h" +#include "proto.h" PRIVATE struct __mthread_attr *va_front, *va_rear; FORWARD _PROTOTYPE( void mthread_attr_add, (mthread_attr_t *a) ); diff --git a/lib/libmthread/condition.c b/lib/libmthread/condition.c index e86380fcf..bb8a6e341 100644 --- a/lib/libmthread/condition.c +++ b/lib/libmthread/condition.c @@ -1,12 +1,12 @@ #include -#include "proto.h" #include "global.h" +#include "proto.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) ); - +#define MAIN_COND mainthread.m_cond /*===========================================================================* * mthread_init_valid_conditions * @@ -46,9 +46,10 @@ PUBLIC int mthread_cond_broadcast(cond) mthread_cond_t *cond; { /* Signal all threads waiting for condition 'cond'. */ - int i; + mthread_thread_t t; + mthread_tcb_t *tcb; - mthread_init(); /* Make sure mthreads is initialized */ + mthread_init(); /* Make sure libmthread is initialized */ if(cond == NULL) { errno = EINVAL; @@ -60,9 +61,15 @@ mthread_cond_t *cond; return(-1); } - for (i = 0; i < no_threads; i++) - if (threads[i].m_state == CONDITION && threads[i].m_cond == *cond) - mthread_unsuspend(i); + tcb = mthread_find_tcb(MAIN_THREAD); + if (tcb->m_state == MS_CONDITION && tcb->m_cond == *cond) + mthread_unsuspend(MAIN_THREAD); + + for (t = (mthread_thread_t) 0; t < no_threads; t++) { + tcb = mthread_find_tcb(t); + if (tcb->m_state == MS_CONDITION && tcb->m_cond == *cond) + mthread_unsuspend(t); + } return(0); } @@ -75,9 +82,10 @@ PUBLIC int mthread_cond_destroy(cond) mthread_cond_t *cond; { /* Destroy a condition variable. Make sure it's not in use */ - int i; + mthread_thread_t t; + mthread_tcb_t *tcb; - mthread_init(); /* Make sure mthreads is initialized */ + mthread_init(); /* Make sure libmthread is initialized */ if (cond == NULL) { errno = EINVAL; @@ -90,8 +98,15 @@ mthread_cond_t *cond; } /* 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) { + tcb = mthread_find_tcb(MAIN_THREAD); + if (tcb->m_state == MS_CONDITION && tcb->m_cond == *cond) { + errno = EBUSY; + return(-1); + } + + for (t = (mthread_thread_t) 0; t < no_threads; t++) { + tcb = mthread_find_tcb(t); + if(tcb->m_state == MS_CONDITION && tcb->m_cond == *cond){ errno = EBUSY; return(-1); } @@ -116,7 +131,7 @@ 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 */ + mthread_init(); /* Make sure libmthread is initialized */ if (cond == NULL) { errno = EINVAL; @@ -171,9 +186,10 @@ 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_thread_t t; + mthread_tcb_t *tcb; - mthread_init(); /* Make sure mthreads is initialized */ + mthread_init(); /* Make sure libmthread is initialized */ if(cond == NULL) { errno = EINVAL; @@ -185,9 +201,14 @@ mthread_cond_t *cond; return(-1); } - for (i = 0; i < no_threads; i++) { - if (threads[i].m_state == CONDITION && threads[i].m_cond == *cond) { - mthread_unsuspend(i); + tcb = mthread_find_tcb(MAIN_THREAD); + if (tcb->m_state == MS_CONDITION && tcb->m_cond == *cond) + mthread_unsuspend(MAIN_THREAD); + + for (t = (mthread_thread_t) 0; t < no_threads; t++) { + tcb = mthread_find_tcb(t); + if(tcb->m_state == MS_CONDITION && tcb->m_cond == *cond){ + mthread_unsuspend(t); break; } } @@ -226,7 +247,7 @@ PUBLIC int mthread_cond_verify(void) { /* Return true in case no condition variables are in use. */ - mthread_init(); /* Make sure mthreads is initialized */ + mthread_init(); /* Make sure libmthread is initialized */ return(vc_front == NULL); } @@ -241,10 +262,11 @@ mthread_cond_t *cond; mthread_mutex_t *mutex; { /* Wait for a condition to be signaled */ + mthread_tcb_t *tcb; struct __mthread_cond *c; struct __mthread_mutex *m; - mthread_init(); /* Make sure mthreads is initialized */ + mthread_init(); /* Make sure libmthread is initialized */ if (cond == NULL || mutex == NULL) { errno = EINVAL; @@ -263,13 +285,13 @@ mthread_mutex_t *mutex; 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); + tcb = mthread_find_tcb(current_thread); + tcb->m_cond = c; /* Register condition variable. */ + mthread_suspend(MS_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 */ + tcb->m_cond = NULL; /* ... and condition var */ if (mthread_mutex_lock(mutex) != 0) return(-1); diff --git a/lib/libmthread/global.h b/lib/libmthread/global.h index 0dc58716b..195622f50 100644 --- a/lib/libmthread/global.h +++ b/lib/libmthread/global.h @@ -6,20 +6,42 @@ #include -#define NO_THREADS 3 -#define MAX_THREAD_POOL 1000 +#define NO_THREADS 4 +#define MAX_THREAD_POOL 1024 #define STACKSZ 4096 -#define isokthreadid(i) (i >= 0 && i < no_threads) +#define MAIN_THREAD -1 +#define NO_THREAD -2 +#define isokthreadid(i) (i == MAIN_THREAD || (i >= 0 && i < no_threads)) + +typedef enum { + MS_CONDITION, MS_DEAD, MS_EXITING, MS_FALLBACK_EXITING, MS_MUTEX, MS_RUNNABLE +} mthread_state_t; + +struct __mthread_tcb { + mthread_thread_t m_tid; /* My own ID */ + 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 */ + struct __mthread_tcb *m_next; /* Next thread in linked list */ +}; +typedef struct __mthread_tcb mthread_tcb_t; 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 **threads; EXTERN mthread_tcb_t fallback; EXTERN mthread_tcb_t mainthread; EXTERN int no_threads; EXTERN int used_threads; EXTERN int running_main_thread; +EXTERN char fallback_stack[STACKSZ]; diff --git a/lib/libmthread/misc.c b/lib/libmthread/misc.c index 230bd8bb8..81301f31b 100644 --- a/lib/libmthread/misc.c +++ b/lib/libmthread/misc.c @@ -1,7 +1,7 @@ #include #include -#include "proto.h" #include "global.h" +#include "proto.h" /*===========================================================================* * mthread_debug_f * @@ -21,9 +21,11 @@ PUBLIC void mthread_debug_f(const char *file, int line, const char *msg) 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("mthread panic (%s:%d): ", file, line); printf(msg); printf("\n"); + fflush(stdout); /* Force debug print to screen */ + *((int *)0) = 1; /* Cause segfault to generate trace */ exit(1); } @@ -39,11 +41,14 @@ PUBLIC void mthread_verify_f(char *file, int line) * quiescent; no mutexes, conditions, or threads in use. All threads are to * be in DEAD state. */ - int i; + mthread_thread_t t; + mthread_tcb_t *tcb; 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; + for (t = (mthread_thread_t) 0; threads_ok && t < no_threads; t++) { + tcb = mthread_find_tcb(t); + if (tcb->m_state != MS_DEAD) threads_ok = 0; + } conditions_ok = mthread_cond_verify(); mutexes_ok = mthread_mutex_verify(); @@ -69,17 +74,20 @@ PUBLIC void mthread_verify_f(char *f, int l) { ; } *===========================================================================*/ PUBLIC void mthread_stats(void) { - int i, st_run, st_dead, st_cond, st_mutex, st_exit, st_fbexit;; + mthread_thread_t t; + mthread_tcb_t *tcb; + int 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; + for (t = (mthread_thread_t) 0; t < no_threads; t++) { + tcb = mthread_find_tcb(t); + switch(tcb->m_state) { + case MS_RUNNABLE: st_run++; break; + case MS_DEAD: st_dead++; break; + case MS_MUTEX: st_mutex++; break; + case MS_CONDITION: st_cond++; break; + case MS_EXITING: st_exit++; break; + case MS_FALLBACK_EXITING: st_fbexit++; break; default: mthread_panic("Unknown state"); } } diff --git a/lib/libmthread/mutex.c b/lib/libmthread/mutex.c index 059744056..06d1afab6 100644 --- a/lib/libmthread/mutex.c +++ b/lib/libmthread/mutex.c @@ -1,6 +1,6 @@ #include -#include "proto.h" #include "global.h" +#include "proto.h" PRIVATE struct __mthread_mutex *vm_front, *vm_rear; FORWARD _PROTOTYPE( void mthread_mutex_add, (mthread_mutex_t *m) ); @@ -45,7 +45,8 @@ mthread_mutex_t *mutex; { /* Invalidate mutex and deallocate resources. */ - int i; + mthread_thread_t t; + mthread_tcb_t *tcb; mthread_init(); /* Make sure mthreads is initialized */ @@ -58,15 +59,16 @@ mthread_mutex_t *mutex; errno = EINVAL; return(-1); } else if ((*mutex)->owner != NO_THREAD) { + printf("mutex owner is %d, so not destroying\n", (*mutex)->owner); 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) { + for (t = (mthread_thread_t) 0; t < no_threads; t++) { + tcb = mthread_find_tcb(t); + if (tcb->m_state == MS_CONDITION) { + if (tcb->m_cond != NULL && tcb->m_cond->mutex == *mutex) { errno = EBUSY; return(-1); } @@ -120,8 +122,10 @@ mthread_mutexattr_t *mattr; /* Mutex attribute */ /*===========================================================================* * mthread_mutex_lock * *===========================================================================*/ -PUBLIC int mthread_mutex_lock(mutex) +PUBLIC int mthread_mutex_lock_f(mutex, file, line) mthread_mutex_t *mutex; /* Mutex that is to be locked */ +char file[NAME_MAX + 1]; +int line; { /* Try to lock this mutex. If already locked, append the current thread to * FIFO queue associated with this mutex and suspend the thread. */ @@ -141,12 +145,16 @@ mthread_mutex_t *mutex; /* Mutex that is to be locked */ return(-1); } else if (m->owner == NO_THREAD) { /* Not locked */ m->owner = current_thread; + if (current_thread == MAIN_THREAD) + mthread_debug("MAIN_THREAD now mutex owner\n"); } else if (m->owner == current_thread) { errno = EDEADLK; return(-1); } else { mthread_queue_add( &(m->queue), current_thread); - mthread_suspend(MUTEX); + if (m->owner == MAIN_THREAD) + mthread_dump_queue(&(m->queue)); + mthread_suspend(MS_MUTEX); } /* When we get here we acquired the lock. */ @@ -277,6 +285,7 @@ PUBLIC int mthread_mutex_verify(void) loopitem = vm_front; while (loopitem != NULL) { + printf("mutex corruption: owner: %d\n", loopitem->owner); loopitem = loopitem->next; r = 0; } diff --git a/lib/libmthread/proto.h b/lib/libmthread/proto.h index 3d77a6590..7ef4aa27b 100644 --- a/lib/libmthread/proto.h +++ b/lib/libmthread/proto.h @@ -1,6 +1,9 @@ #ifndef __MTHREAD_PROTO_H__ #define __MTHREAD_PROTO_H__ +/* allocate.c */ +_PROTOTYPE( mthread_tcb_t * mthread_find_tcb, (mthread_thread_t thread) ); + /* attribute.c */ _PROTOTYPE( void mthread_init_valid_attributes, (void) ); #ifdef MDEBUG @@ -28,12 +31,19 @@ _PROTOTYPE( int mthread_mutex_valid, (mthread_mutex_t *mutex) ); _PROTOTYPE( int mthread_mutex_verify, (void) ); #endif - /* schedule.c */ _PROTOTYPE( int mthread_getcontext, (ucontext_t *ctxt) ); _PROTOTYPE( void mthread_init_scheduler, (void) ); +_PROTOTYPE( void mthread_schedule, (void) ); +_PROTOTYPE( void mthread_suspend, (mthread_state_t state) ); +_PROTOTYPE( void mthread_unsuspend, (mthread_thread_t thread) ); /* queue.c */ _PROTOTYPE( void mthread_dump_queue, (mthread_queue_t *queue) ); +_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/libmthread/queue.c b/lib/libmthread/queue.c index 98f175000..032e19f73 100644 --- a/lib/libmthread/queue.c +++ b/lib/libmthread/queue.c @@ -1,5 +1,6 @@ #include #include "global.h" +#include "proto.h" /*===========================================================================* * mthread_queue_add * @@ -12,12 +13,18 @@ mthread_thread_t thread; * 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. */ + mthread_tcb_t *last; + + if (!isokthreadid(thread)) + mthread_panic("Can't append invalid thread ID to a queue"); + + last = mthread_find_tcb(thread); 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 */ + queue->head = queue->tail = last; + } else { + queue->tail->m_next = last; + queue->tail = last; /* 'last' is the new last in line */ } } @@ -30,7 +37,7 @@ mthread_queue_t *queue; /* Queue that has to be initialized */ { /* Initialize queue to a known state */ - queue->head = queue->tail = NO_THREAD; + queue->head = queue->tail = NULL; } @@ -40,7 +47,7 @@ mthread_queue_t *queue; /* Queue that has to be initialized */ PUBLIC int mthread_queue_isempty(queue) mthread_queue_t *queue; { - return(queue->head == NO_THREAD); + return(queue->head == NULL); } @@ -51,23 +58,28 @@ PUBLIC void mthread_dump_queue(queue) mthread_queue_t *queue; { int threshold, count = 0; - mthread_thread_t t; + mthread_tcb_t *t; + mthread_thread_t tid; threshold = no_threads; #ifdef MDEBUG printf("Dumping queue: "); #endif - if(queue->head != NO_THREAD) { + if(queue->head != NULL) { t = queue->head; + if (t == &mainthread) tid = MAIN_THREAD; + else tid = t->m_tid; #ifdef MDEBUG - printf("%d ", t); + printf("%d ", tid); #endif count++; - t = threads[t].m_next; - while (t != NO_THREAD) { + t = t->m_next; + while (t != NULL) { + if (t == &mainthread) tid = MAIN_THREAD; + else tid = t->m_tid; #ifdef MDEBUG - printf("%d ", t); + printf("%d ", tid); #endif - t = threads[t].m_next; + t = t->m_next; count++; if (count > threshold) break; } @@ -90,14 +102,25 @@ 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; + mthread_thread_t thread; + mthread_tcb_t *tcb; + + /* Calculate thread id from queue head */ + if (queue->head == NULL) thread = NO_THREAD; + else if (queue->head == &mainthread) thread = MAIN_THREAD; + else thread = (queue->head->m_tid); 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 + tcb = queue->head; + if (queue->head == queue->tail) { + /* Queue holds only one thread */ + queue->head = queue->tail = NULL; /* So mark thread empty */ + } else { /* Second thread in line is the new first */ - queue->head = threads[thread].m_next; + queue->head = queue->head->m_next; + } + + tcb->m_next = NULL; /* This thread is no longer part of a queue */ } return(thread); diff --git a/lib/libmthread/scheduler.c b/lib/libmthread/scheduler.c index a1834be1e..96e91beea 100644 --- a/lib/libmthread/scheduler.c +++ b/lib/libmthread/scheduler.c @@ -3,9 +3,10 @@ #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 +#define MAIN_STATE mainthread.m_state +#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; /*===========================================================================* @@ -33,10 +34,11 @@ PUBLIC void mthread_schedule(void) * first thread off the (FIFO) run queue and resuming that thread. */ - int old_thread; + mthread_thread_t old_thread; + mthread_tcb_t *new_tcb, *old_tcb; ucontext_t *new_ctx, *old_ctx; - mthread_init(); /* Make sure mthreads is initialized */ + mthread_init(); /* Make sure libmthread is initialized */ old_thread = current_thread; @@ -54,24 +56,23 @@ PUBLIC void mthread_schedule(void) * thread as there is no work left. */ running_main_thread = 1; - current_thread = NO_THREAD; + current_thread = MAIN_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; + /* Find thread entries in tcb... */ + new_tcb = mthread_find_tcb(current_thread); + old_tcb = mthread_find_tcb(old_thread); - if (old_thread == NO_THREAD) - old_ctx = MAIN_CTX; - else - old_ctx = OLD_CTX; + /* ...and subsequently their contexts */ + new_ctx = &(new_tcb->m_context); + old_ctx = &(old_tcb->m_context); - if (swapcontext(old_ctx, new_ctx) == -1) - mthread_panic("Could not swap context"); + if (swapcontext(old_ctx, new_ctx) == -1) + mthread_panic("Could not swap context"); + } @@ -101,15 +102,18 @@ mthread_state_t state; */ int continue_thread = 0; + mthread_tcb_t *tcb; + ucontext_t *ctx; - if (state == DEAD) mthread_panic("Shouldn't suspend with DEAD state"); + if (state == MS_DEAD) mthread_panic("Shouldn't suspend with MS_DEAD state"); + tcb = mthread_find_tcb(current_thread); + tcb->m_state = state; + ctx = &(tcb->m_context); - threads[current_thread].m_state = state; - /* Save current thread's context */ - if (mthread_getcontext(CURRENT_CTX) != 0) + if (mthread_getcontext(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 @@ -130,9 +134,12 @@ 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 */ + mthread_tcb_t *tcb; - if (!isokthreadid(thread)) mthread_panic("Invalid thread id\n"); - threads[thread].m_state = RUNNABLE; + if (!isokthreadid(thread)) mthread_panic("Invalid thread id"); + + tcb = mthread_find_tcb(thread); + tcb->m_state = MS_RUNNABLE; mthread_queue_add(&run_queue, thread); } @@ -144,7 +151,7 @@ PUBLIC int mthread_yield(void) { /* Defer further execution of the current thread and let another thread run. */ - mthread_init(); /* Make sure mthreads is initialized */ + mthread_init(); /* Make sure libmthread is initialized */ if (mthread_queue_isempty(&run_queue)) { /* No point in yielding. */ return(-1); @@ -157,9 +164,9 @@ PUBLIC int mthread_yield(void) } 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. - */ + mthread_suspend(MS_RUNNABLE); /* We're still runnable, but we're just kind + * enough to let someone else run. + */ return(0); } @@ -173,7 +180,7 @@ PUBLIC void mthread_yield_all(void) * this function will lead to a deadlock. */ - mthread_init(); /* Make sure mthreads is initialized */ + mthread_init(); /* Make sure libmthread is initialized */ if (yield_all) mthread_panic("Deadlock: two threads trying to yield_all"); yield_all = 1; diff --git a/test/test59.c b/test/test59.c index 04b1ab524..1b01638e3 100644 --- a/test/test59.c +++ b/test/test59.c @@ -16,10 +16,19 @@ 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 int mutex_a_step, mutex_b_step, mutex_c_step; +PRIVATE mutex_t mu[3]; PRIVATE cond_t condition; PRIVATE mutex_t *count_mutex, *condition_mutex; PRIVATE once_t once; +#define VERIFY_MUTEX(a,b,c,esub,eno) do { \ + if (mutex_a_step != a) { \ + printf("Expected %d %d %d, got: %d %d %d\n", \ + a, b, c, mutex_a_step, mutex_b_step, mutex_c_step); \ + err(esub, eno); \ + } else if (mutex_b_step != b) err(esub, eno); \ + else if (mutex_c_step != c) err(esub, eno); \ + } while(0) #define ROUNDS 14 #define THRESH1 3 #define THRESH2 8 @@ -152,13 +161,15 @@ PRIVATE void test_scheduling(void) 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(); + + mthread_yield(); + if (mthread_create(&t[4], NULL, thread_c, NULL) != 0) err(1, 6); - mthread_schedule(); + mthread_yield(); 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(); + mthread_yield(); + mthread_yield(); if (mthread_once(&once, thread_e) != 0) err(1, 9); if (mthread_once(&once, thread_e) != 0) err(1, 10); @@ -205,7 +216,8 @@ 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); + VERIFY_MUTEX(0, 0, 0, 3, 1); + if (mthread_mutex_lock(&mu[0]) != 0) err(3, 2); /* Trying to acquire lock again should fail with EDEADLK */ if (mthread_mutex_lock(&mu[0]) != -1) err(3, 2); @@ -225,17 +237,31 @@ PRIVATE void mutex_a(void *arg) if (errno != EINVAL) err(3, 7); if (mthread_mutex_trylock(&mu[1]) != 0) err(3, 8); + mutex_a_step = 1; mthread_yield(); - - if (mthread_mutex_unlock(&mu[0]) != 0) err(3, 9); + VERIFY_MUTEX(1, 0, 0, 3, 9); + errno = 0; + if (mthread_mutex_trylock(&mu[2]) != -1) err(3, 10); + if (errno != EBUSY) err(3, 11); + if (mthread_mutex_lock(&mu[2]) != 0) err(3, 12); /* Transfer control to main + * loop. + */ + VERIFY_MUTEX(1, 0, 0, 3, 13); + + if (mthread_mutex_unlock(&mu[0]) != 0) err(3, 14); + mutex_a_step = 2; mthread_yield(); - if (mthread_mutex_unlock(&mu[1]) != 0) err(3, 10); + VERIFY_MUTEX(2, 1, 0, 3, 15); + if (mthread_mutex_unlock(&mu[1]) != 0) err(3, 16); + mutex_a_step = 3; /* 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); + if (mthread_mutex_lock(NULL) == 0) err(3, 17); + if (mthread_mutex_trylock(NULL) == 0) err(3, 18); + if (mthread_mutex_unlock(NULL) == 0) err(3, 19); + + if (mthread_mutex_unlock(&mu[2]) != 0) err(3, 20); } @@ -250,21 +276,29 @@ PRIVATE void mutex_b(void *arg) * 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); + VERIFY_MUTEX(1, 0, 0, 4, 1); + if (mthread_mutex_unlock(&mu[0]) != -1) err(4, 2); + if (errno != EPERM) err(4, 3); /* 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_trylock(&mu[0]) == 0) err(4, 4); + if (errno != EBUSY) err(4, 5); if (mthread_mutex_lock(&mu[0]) != 0) err(4, 5); + mutex_b_step = 1; + VERIFY_MUTEX(2, 1, 0, 4, 6); if (mthread_mutex_lock(&mu[1]) != 0) err(4, 6); + mutex_b_step = 2; + VERIFY_MUTEX(3, 2, 2, 4, 7); mthread_yield(); + VERIFY_MUTEX(3, 2, 2, 4, 8); if (mthread_mutex_unlock(&mu[0]) != 0) err(4, 7); + mutex_b_step = 3; mthread_yield(); if (mthread_mutex_unlock(&mu[1]) != 0) err(4, 8); + mutex_b_step = 4; } @@ -275,14 +309,23 @@ PRIVATE void mutex_c(void *arg) { mutex_t *mu = (mutex_t *) arg; - if (mthread_mutex_lock(&mu[1]) != 0) err(5, 1); + VERIFY_MUTEX(1, 0, 0, 5, 1); + if (mthread_mutex_lock(&mu[1]) != 0) err(5, 2); + mutex_c_step = 1; + VERIFY_MUTEX(3, 1, 1, 5, 3); mthread_yield(); + VERIFY_MUTEX(3, 1, 1, 5, 4); - if (mthread_mutex_unlock(&mu[1]) != 0) err(5, 2); - if (mthread_mutex_lock(&mu[0]) != 0) err(5, 3); + if (mthread_mutex_unlock(&mu[1]) != 0) err(5, 5); + mutex_c_step = 2; + if (mthread_mutex_lock(&mu[0]) != 0) err(5, 6); + mutex_c_step = 3; + VERIFY_MUTEX(3, 3, 3, 5, 7); mthread_yield(); + VERIFY_MUTEX(3, 4, 3, 5, 8); - if (mthread_mutex_unlock(&mu[0]) != 0) err(5, 4); + if (mthread_mutex_unlock(&mu[0]) != 0) err(5, 9); + mutex_c_step = 4; } @@ -298,24 +341,33 @@ PRIVATE void test_mutex(void) #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_mutex_init(&mu[2], NULL) != 0) err(2, 3); 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); + if (mthread_mutex_lock(&mu[2]) != 0) err(2, 6); + 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 + + VERIFY_MUTEX(1, 0, 0, 2, 7); /* err(2, 7) */ + if (mthread_mutex_unlock(&mu[2]) != 0) err(2, 8); + + mthread_yield(); /* Should schedule mutex_a to release the lock on the + * mu[0] mutex. Consequently allowing mutex_b and mutex_c * to acquire locks on the mutexes and exit. */ + VERIFY_MUTEX(2, 0, 0, 2, 9); for (i = 0; i < (sizeof(t) / sizeof(thread_t)); i++) - if (mthread_join(t[i], NULL) != 0) err(2, 6); + if (mthread_join(t[i], NULL) != 0) err(2, 10); - if (mthread_mutex_destroy(&mu[0]) != 0) err(2, 7); - if (mthread_mutex_destroy(&mu[1]) != 0) err(2, 8); + if (mthread_mutex_destroy(&mu[0]) != 0) err(2, 11); + if (mthread_mutex_destroy(&mu[1]) != 0) err(2, 12); + if (mthread_mutex_destroy(&mu[2]) != 0) err(2, 13); #ifdef MDEBUG mthread_verify(); @@ -534,7 +586,7 @@ PRIVATE void test_attributes(void) if (mthread_attr_getstack(&tattr, &stackaddr, &stacksize) != 0) err(11, 13); if (stackaddr != newstackaddr) err(11, 14); if (stacksize != newstacksize) err(11, 15); - + if (mthread_attr_destroy(&tattr) != 0) err(11, 16); /* 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. */ @@ -544,12 +596,17 @@ PRIVATE void test_attributes(void) * values should remain as is. */ newstacksize = MTHREAD_STACK_MIN - 1; - if ((newstackaddr = malloc(newstacksize)) == NULL) err(11, 16); + stackaddr = NULL; + stacksize = 0; + if (mthread_attr_init(&tattr) != 0) err(11, 17); + if ((newstackaddr = malloc(newstacksize)) == NULL) err(11, 18); 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); + err(11, 19); + if (errno != EINVAL) err(11, 20); + if (mthread_attr_getstack(&tattr, &stackaddr, &stacksize) != 0) err(11, 21); + if (stackaddr == newstackaddr) err(11, 22); + if (stacksize == newstacksize) err(11, 23); + if (mthread_attr_destroy(&tattr) != 0) err(11, 24); /* Again, freeing because we can. Shouldn't do it if it was actually used. */ free(newstackaddr); @@ -557,69 +614,70 @@ PRIVATE void test_attributes(void) * dictate how big that stack should be (2 megabyte, not actually allocated * yet). */ + if (mthread_attr_init(&tattr) != 0) err(11, 25); 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); + err(11, 26); + if (mthread_attr_getstack(&tattr, &stackaddr, &stacksize) != 0) err(11, 27); + if (stackaddr != NULL) err(11, 28); + if (stacksize != 2*MEG) err(11, 29); /* 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); + if (mthread_attr_getstacksize(&tattr, &stacksize) != 0) err(11, 30); + if (stacksize != 2*MEG) err(11, 31); 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); + if (mthread_attr_setstacksize(&tattr, newstacksize) != 0) err(11, 32); + if (mthread_attr_getstacksize(&tattr, &stacksize) != 0) err(11, 33); + if (stacksize != newstacksize) err(11, 34); + if (mthread_attr_destroy(&tattr) != 0) err(11, 35); /* Perform same tests, but also actually use them in a thread */ - if (mthread_attr_init(&tattr) != 0) err(11, 31); + if (mthread_attr_init(&tattr) != 0) err(11, 36); if (mthread_attr_setdetachstate(&tattr, MTHREAD_CREATE_DETACHED) != 0) - err(11, 32); + err(11, 37); 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); + if (mthread_mutex_init(condition_mutex, NULL) != 0) err(11, 38); + if (mthread_cond_init(&condition, NULL) != 0) err(11, 39); + if (mthread_mutex_lock(condition_mutex) != 0) err(11, 40); + if (mthread_create(&tid, &tattr, thread_f, NULL) != 0) err(11, 41); /* 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); + if (mthread_cond_wait(&condition, condition_mutex) != 0) err(11, 42); + if (mthread_mutex_unlock(condition_mutex) != 0) err(11, 43); + if (th_f != 1) err(11, 44); /* Joining a detached thread should fail */ - if (mthread_join(tid, NULL) == 0) err(11, 40); - if (mthread_attr_destroy(&tattr) != 0) err(11, 41); + if (mthread_join(tid, NULL) == 0) err(11, 45); + if (mthread_attr_destroy(&tattr) != 0) err(11, 46); /* Try telling the attribute how large the stack should be */ - if (mthread_attr_init(&tattr) != 0) err(11, 42); + if (mthread_attr_init(&tattr) != 0) err(11, 47); 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); + err(11, 48); + if (mthread_mutex_lock(condition_mutex) != 0) err(11, 49); + if (mthread_create(&tid, &tattr, thread_g, NULL) != 0) err(11, 50); /* 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_cond_wait(&condition, condition_mutex) != 0) err(11, 51); + if (mthread_mutex_unlock(condition_mutex) != 0) err(11, 52); + if (th_g != 1) err(11, 53); if (mthread_attr_setdetachstate(&tattr, MTHREAD_CREATE_DETACHED) != 0) - err(11, 49); /* Shouldn't affect the join below, as thread is already + err(11, 54); /* 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); + if (mthread_join(tid, NULL) != 0) err(11, 55); + if (mthread_attr_destroy(&tattr) != 0) err(11, 56); /* Try telling the attribute how large the stack should be and where it is * located. */ - if (mthread_attr_init(&tattr) != 0) err(11, 52); + if (mthread_attr_init(&tattr) != 0) err(11, 57); 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); + if (2 * MEG <= MTHREAD_STACK_MIN) err(11, 58); + if ((stackaddr = malloc(stacksize)) == NULL) err(11, 59); /* 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 @@ -629,33 +687,34 @@ PRIVATE void test_attributes(void) 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_attr_setstack(&tattr, stackaddr, stacksize) != 0) err(11, 60); + if (mthread_mutex_lock(condition_mutex) != 0) err(11, 61); if (mthread_create(&tid, &tattr, thread_h, (void *) &stacksize) != 0) - err(11, 57); + err(11, 62); /* Wait for thread h to finish */ - if (mthread_cond_wait(&condition, condition_mutex) != 0) err(11, 58); - if (th_h != 1) err(11, 59); + if (mthread_cond_wait(&condition, condition_mutex) != 0) err(11, 63); + if (th_h != 1) err(11, 64); + if (mthread_mutex_unlock(condition_mutex) != 0) err(11, 65); /* 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 */ + if (stackp[0] != MAGIC) err(11, 66); /* 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*/ + if (stack_untouched) err(11, 67); /* Beginning of the stack */ + if (stackp[no_ints / 2] != 0) err(11, 68);/*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); + if (mthread_join(tid, (void *) &status) != 0) err(11, 69); + if (status != stacksize) err(11, 70); + if (mthread_attr_destroy(&tattr) != 0) err(11, 71); + if (mthread_mutex_destroy(condition_mutex) != 0) err(11, 72); + if (mthread_cond_destroy(&condition) != 0) err(11, 73); #ifdef MDEBUG mthread_verify(); @@ -669,6 +728,7 @@ int main(void) { errct = 0; th_a = th_b = th_c = th_d = th_e = th_f = th_g = th_h = 0; + mutex_a_step = mutex_b_step = mutex_c_step = 0; once = MTHREAD_ONCE_INIT; start(59); -- 2.44.0