]> Zhao Yanbai Git Server - minix.git/commitdiff
Add libmthread and test59 to test the implementation
authorThomas Veerman <thomas@minix3.org>
Tue, 21 Sep 2010 12:22:38 +0000 (12:22 +0000)
committerThomas Veerman <thomas@minix3.org>
Tue, 21 Sep 2010 12:22:38 +0000 (12:22 +0000)
16 files changed:
include/Makefile
include/minix/mthread.h [new file with mode: 0644]
lib/Makefile
lib/libmthread/Makefile [new file with mode: 0644]
lib/libmthread/allocate.c [new file with mode: 0644]
lib/libmthread/attribute.c [new file with mode: 0644]
lib/libmthread/condition.c [new file with mode: 0644]
lib/libmthread/global.h [new file with mode: 0644]
lib/libmthread/misc.c [new file with mode: 0644]
lib/libmthread/mutex.c [new file with mode: 0644]
lib/libmthread/proto.h [new file with mode: 0644]
lib/libmthread/queue.c [new file with mode: 0644]
lib/libmthread/scheduler.c [new file with mode: 0644]
test/Makefile
test/run
test/test59.c [new file with mode: 0644]

index 7756bf5137da8a20cca70092c6d36e0a8ba58501..50554531ac0c3469788f368e104850808db4f334 100644 (file)
@@ -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 (file)
index 0000000..2bd3e67
--- /dev/null
@@ -0,0 +1,143 @@
+#ifndef _MTHREAD_H
+#define _MTHREAD_H
+#define _SYSTEM
+
+#include <minix/config.h>      /* MUST be first */
+#include <ansi.h>              /* MUST be second */
+#include <minix/const.h>
+#include <sys/types.h>
+#include <stdio.h>
+#include <ucontext.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <alloca.h>
+#include <limits.h>
+
+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
index 8ea7e3f4c9d25edb486377c2f2a9fb27a1c7a264..8f6e868c65d1d13085c6050f83dcc4dc38926d63 100644 (file)
@@ -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 (file)
index 0000000..b742800
--- /dev/null
@@ -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 <bsd.lib.mk>
diff --git a/lib/libmthread/allocate.c b/lib/libmthread/allocate.c
new file mode 100644 (file)
index 0000000..2c76931
--- /dev/null
@@ -0,0 +1,500 @@
+#define ALLOCATE
+#include <errno.h>
+#include <minix/mthread.h>
+#include "global.h"
+#include "proto.h"
+
+#define FALLBACK_CTX (&(fallback.m_context))
+
+FORWARD _PROTOTYPE( void mthread_fallback, (void)                      );
+FORWARD _PROTOTYPE( int mthread_increase_thread_pool, (void)                   );
+FORWARD _PROTOTYPE( void mthread_thread_init, (mthread_thread_t thread,
+                                              mthread_attr_t *tattr,
+                                              void (*proc)(void *),
+                                              void *arg)               );
+
+FORWARD _PROTOTYPE( void mthread_thread_reset, (mthread_thread_t thread));
+FORWARD _PROTOTYPE( void mthread_thread_stop, (mthread_thread_t thread));
+FORWARD _PROTOTYPE( void mthread_trampoline, (void)                    );
+
+PRIVATE int initialized = 0;
+
+PRIVATE struct __mthread_attr default_attr = { MTHREAD_STACK_MIN,
+                                               NULL,
+                                               MTHREAD_CREATE_JOINABLE,
+                                               NULL, NULL };
+
+/*===========================================================================*
+ *                             mthread_equal                                *
+ *===========================================================================*/
+PUBLIC int mthread_equal(l, r)
+mthread_thread_t l;
+mthread_thread_t r;
+{
+/* Compare two thread ids */
+  mthread_init();      /* Make sure mthreads is initialized */
+
+  return(l == r);
+}
+
+
+/*===========================================================================*
+ *                             mthread_create                               *
+ *===========================================================================*/
+PUBLIC int mthread_create(threadid, tattr, proc, arg)
+mthread_thread_t *threadid;
+mthread_attr_t *tattr;
+void (*proc)(void *);
+void *arg;
+{
+/* Register procedure proc for execution in a thread. */
+  mthread_thread_t thread;
+
+  mthread_init();      /* Make sure mthreads is initialized */
+
+  if (proc == NULL) {
+       errno = EINVAL;
+       return(-1);
+  }
+
+  if (!mthread_queue_isempty(&free_threads)) {
+       thread = mthread_queue_remove(&free_threads);
+       mthread_thread_init(thread, tattr, proc, arg);
+       used_threads++;
+       if(threadid != NULL) 
+               *threadid = (mthread_thread_t) thread;
+#ifdef MDEBUG
+       printf("Inited thread %d\n", thread);
+#endif
+       return(0);
+  } else  {
+       if (mthread_increase_thread_pool() == -1) {
+               errno = EAGAIN;
+               return(-1);
+       }
+
+       return mthread_create(threadid, tattr, proc, arg);
+  }
+}
+
+
+/*===========================================================================*
+ *                             mthread_detach                               *
+ *===========================================================================*/
+PUBLIC int mthread_detach(detach)
+mthread_thread_t detach;
+{
+/* Mark a thread as detached. Consequently, upon exit, resources allocated for
+ * this thread are automatically freed.
+ */
+
+  mthread_init();      /* Make sure mthreads is initialized */
+
+  if (!isokthreadid(detach)) {
+       errno = ESRCH;
+       return(-1);
+  } else if (threads[detach].m_state == DEAD) {
+       errno = ESRCH;
+       return(-1);
+  } else if (threads[detach].m_attr.a_detachstate != MTHREAD_CREATE_DETACHED) {
+       if (threads[detach].m_state == EXITING) {
+               mthread_thread_stop(detach);
+       } else {
+               threads[detach].m_attr.a_detachstate = MTHREAD_CREATE_DETACHED;
+       }
+  }
+  return(0);
+}
+
+
+/*===========================================================================*
+ *                             mthread_exit                                 *
+ *===========================================================================*/
+PUBLIC void mthread_exit(value)
+void *value;
+{
+/* Make a thread stop running and store the result value. */
+  int fallback_exit = 0;
+  mthread_thread_t stop;
+
+  mthread_init();      /* Make sure mthreads is initialized */
+
+  stop = current_thread;
+
+  if (threads[stop].m_state == EXITING) /* Already stopping, nothing to do. */
+       return;
+
+  /* When we're called from the fallback thread, the fallback thread 
+   * will invoke the scheduler. However, if the thread itself called 
+   * mthread_exit, _we_ will have to wake up the scheduler.
+   */
+  if (threads[stop].m_state == FALLBACK_EXITING)
+       fallback_exit = 1;
+
+  threads[stop].m_result = value;
+  threads[stop].m_state = EXITING;
+
+  if (threads[stop].m_attr.a_detachstate == MTHREAD_CREATE_DETACHED) {
+       mthread_thread_stop(stop);
+  } else {
+       /* Joinable thread; notify possibly waiting thread */
+       if (mthread_cond_signal(&(threads[stop].m_exited)) != 0) 
+               mthread_panic("Couldn't signal exit");
+
+       /* The thread that's actually doing the join will eventually clean
+        * up this thread (i.e., call mthread_thread_stop).
+        */
+  }
+
+  /* The fallback thread does a mthread_schedule. If we're not running from
+   * that thread, we have to do it ourselves.
+   */
+  if (!fallback_exit) 
+       mthread_schedule();
+
+}
+
+
+/*===========================================================================*
+ *                             mthread_fallback                             *
+ *===========================================================================*/
+PRIVATE void mthread_fallback(void)
+{
+/* The mthreads fallback thread. The idea is that every thread calls 
+ * mthread_exit(...) to stop running when it has nothing to do anymore. 
+ * However, in case a thread forgets to do that, the whole process  exit()s and
+ * that might be a bit problematic. Therefore, all threads will run this
+ * fallback thread when they exit, giving the scheduler a chance to fix the
+ * situation.
+ */
+
+  threads[current_thread].m_state = FALLBACK_EXITING;
+  mthread_exit(NULL);
+
+  /* Reconstruct fallback context for next invocation */
+  makecontext(FALLBACK_CTX, (void (*) (void)) mthread_fallback, 0);
+
+  /* Let another thread run */
+  mthread_schedule();
+
+}
+
+
+/*===========================================================================*
+ *                     mthread_increase_thread_pool                         *
+ *===========================================================================*/
+PRIVATE int mthread_increase_thread_pool(void)
+{
+/* Increase thread pool. No fancy algorithms, just double the size. */
+  mthread_tcb_t *new_tcb;
+  int new_no_threads, old_no_threads, i;
+
+  old_no_threads = no_threads;
+  new_no_threads = 2 * old_no_threads;
+
+  if (new_no_threads >= MAX_THREAD_POOL) {
+       mthread_debug("Reached max number of threads");
+       return(-1);
+  }
+
+  new_tcb = realloc(threads, new_no_threads * sizeof(mthread_tcb_t));
+  if (new_tcb == NULL) {
+       mthread_debug("Can't increase thread pool");
+       return(-1);
+  }
+
+  threads = new_tcb; 
+  no_threads = new_no_threads;
+
+  /* Add newly available threads to free_threads */
+  for (i = old_no_threads; i < new_no_threads; i++) {
+       mthread_queue_add(&free_threads, i);
+       mthread_thread_reset(i);
+  }
+
+#ifdef MDEBUG
+  printf("Increased thread pool from %d to %d threads\n", no_threads, new_no_threads);
+#endif
+  return(0);
+}
+
+
+/*===========================================================================*
+ *                             mthread_init                                 *
+ *===========================================================================*/
+PUBLIC void mthread_init(void)
+{
+/* Initialize thread system; allocate thread structures and start creating
+ * threads.
+ */
+
+  if (!initialized) {
+       int i;
+       no_threads = NO_THREADS;
+       used_threads = 0;
+       running_main_thread = 1;/* mthread_init can only be called from the
+                                * main thread. Calling it from a thread will
+                                * not enter this clause.
+                                */
+
+       if (getcontext(&(mainthread.m_context)) == -1)
+               mthread_panic("Couldn't save state for main thread");
+       current_thread = NO_THREAD;
+
+       /* Allocate a bunch of thread control blocks */
+       threads = malloc(no_threads * sizeof(mthread_tcb_t));
+       if (threads == NULL)
+               mthread_panic("No memory, can't initialize threads");
+
+       mthread_init_valid_mutexes();
+       mthread_init_valid_conditions();
+       mthread_init_valid_attributes();
+       mthread_init_scheduler();
+
+       /* Put initial threads on the free threads queue */
+       mthread_queue_init(&free_threads);
+       for (i = 0; i < no_threads; i++) {
+               mthread_queue_add(&free_threads, i);
+               mthread_thread_reset(i);
+       }
+
+       /* Initialize the fallback thread */
+       if (getcontext(FALLBACK_CTX) == -1)
+               mthread_panic("Could not initialize fallback thread");
+       FALLBACK_CTX->uc_link = &(mainthread.m_context);
+       FALLBACK_CTX->uc_stack.ss_sp = malloc(STACKSZ);
+       FALLBACK_CTX->uc_stack.ss_size = STACKSZ;
+       if (FALLBACK_CTX->uc_stack.ss_sp == NULL)
+               mthread_panic("Could not allocate stack space to fallback "
+                             "thread");
+       makecontext(FALLBACK_CTX, (void (*) (void)) mthread_fallback, 0);
+
+       initialized = 1;
+  }
+}
+
+
+/*===========================================================================*
+ *                             mthread_join                                 *
+ *===========================================================================*/
+PUBLIC int mthread_join(join, value)
+mthread_thread_t join;
+void **value;
+{
+/* Wait for a thread to stop running and copy the result. */
+
+  mthread_init();      /* Make sure mthreads is initialized */
+
+  if (!isokthreadid(join)) {
+       errno = ESRCH;
+       return(-1);
+  } else if (join == current_thread) {
+       errno = EDEADLK;
+       return(-1);
+  } else if (threads[join].m_state == DEAD) {
+       errno = ESRCH;
+       return(-1);
+  } else if (threads[join].m_attr.a_detachstate == MTHREAD_CREATE_DETACHED) {
+       errno = EINVAL;
+       return(-1);
+  }
+
+  /* When the thread hasn't exited yet, we have to wait for that to happen */
+  if (threads[join].m_state != EXITING) {
+       mthread_cond_t *c;
+       mthread_mutex_t *m;
+
+       c = &(threads[join].m_exited);
+       m = &(threads[join].m_exitm);
+
+       if (mthread_mutex_init(m, NULL) != 0)
+               mthread_panic("Couldn't initialize mutex to join\n");
+
+       if (mthread_mutex_lock(m) != 0)
+               mthread_panic("Couldn't lock mutex to join\n");
+
+       if (mthread_cond_wait(c, m) != 0) 
+               mthread_panic("Couldn't wait for join condition\n");
+               
+       if (mthread_mutex_unlock(m) != 0)
+               mthread_panic("Couldn't unlock mutex to join\n");
+
+       if (mthread_mutex_destroy(m) != 0)
+               mthread_panic("Couldn't destroy mutex to join\n");
+  }
+
+  /* Thread has exited; copy results */
+  if(value != NULL)
+       *value = threads[join].m_result;
+
+  /* Deallocate resources */
+  mthread_thread_stop(join);
+  return(0);
+}
+
+
+/*===========================================================================*
+ *                             mthread_once                                 *
+ *===========================================================================*/
+PUBLIC int mthread_once(once, proc)
+mthread_once_t *once;
+void (*proc)(void);
+{
+/* Run procedure proc just once */
+
+  mthread_init();      /* Make sure mthreads is initialized */
+
+  if (once == NULL || proc == NULL) {
+       errno = EINVAL;
+       return(-1);
+  } 
+
+  if (*once != 1) proc();
+  *once = 1;
+  return(0);
+}
+
+
+/*===========================================================================*
+ *                             mthread_self                                 *
+ *===========================================================================*/
+PUBLIC mthread_thread_t mthread_self(void)
+{
+/* Return the thread id of the thread calling this function. */
+
+  mthread_init();      /* Make sure mthreads is initialized */
+
+  return(current_thread);
+}
+
+
+/*===========================================================================*
+ *                             mthread_thread_init                          *
+ *===========================================================================*/
+PRIVATE void mthread_thread_init(thread, tattr, proc, arg)
+mthread_thread_t thread;
+mthread_attr_t *tattr;
+void (*proc)(void *);
+void *arg;
+{
+/* Initialize a thread so that it, when unsuspended, will run the given
+ * procedure with the given parameter. The thread is marked as runnable.
+ */
+
+#define THIS_CTX (&(threads[thread].m_context))
+  size_t stacksize;
+  char *stackaddr;
+
+  threads[thread].m_next = NO_THREAD;
+  threads[thread].m_state = DEAD;
+  threads[thread].m_proc = (void *(*)(void *)) proc; /* Yikes */
+  threads[thread].m_arg = arg;
+  /* Threads use a copy of the provided attributes. This way, if another
+   * thread modifies the attributes (such as detach state), already running
+   * threads are not affected.
+   */
+  if (tattr != NULL)
+       threads[thread].m_attr = *((struct __mthread_attr *) *tattr);
+  else {
+       threads[thread].m_attr = default_attr;
+  }
+
+  if (mthread_cond_init(&(threads[thread].m_exited), NULL) != 0)
+       mthread_panic("Could not initialize thread");
+
+  /* First set the fallback thread, */
+  THIS_CTX->uc_link = FALLBACK_CTX;
+
+  /* then construct this thread's context to run procedure proc. */
+  if (getcontext(THIS_CTX) == -1)
+       mthread_panic("Failed to initialize context state");
+
+  stacksize = threads[thread].m_attr.a_stacksize;
+  stackaddr = threads[thread].m_attr.a_stackaddr;
+
+  if (stacksize == (size_t) 0)
+       stacksize = (size_t) MTHREAD_STACK_MIN;
+
+  if (stackaddr == NULL) {
+       /* Allocate stack space */
+       THIS_CTX->uc_stack.ss_sp = malloc(stacksize);
+       if (THIS_CTX->uc_stack.ss_sp == NULL)
+               mthread_panic("Failed to allocate stack to thread");
+  } else
+       THIS_CTX->uc_stack.ss_sp = stackaddr;
+
+  THIS_CTX->uc_stack.ss_size = stacksize;
+  makecontext(THIS_CTX, mthread_trampoline, 0);
+
+  mthread_unsuspend(thread); /* Make thread runnable */
+}
+
+
+/*===========================================================================*
+ *                             mthread_thread_reset                         *
+ *===========================================================================*/
+PRIVATE void mthread_thread_reset(thread)
+mthread_thread_t thread;
+{
+/* Reset the thread to its default values. Free the allocated stack space. */
+
+  mthread_tcb_t *rt;
+  if (!isokthreadid(thread)) mthread_panic("Invalid thread id"); 
+
+  rt = &(threads[thread]);
+
+  rt->m_next = NO_THREAD;
+  rt->m_state = DEAD;
+  rt->m_proc = NULL;
+  rt->m_arg = NULL;
+  rt->m_result = NULL;
+  rt->m_cond = NULL;
+  if (rt->m_context.uc_stack.ss_sp)
+       free(rt->m_context.uc_stack.ss_sp); /* Free allocated stack */
+  rt->m_context.uc_stack.ss_sp = NULL;
+  rt->m_context.uc_stack.ss_size = 0;
+  rt->m_context.uc_link = NULL;
+}
+
+
+/*===========================================================================*
+ *                             mthread_thread_stop                          *
+ *===========================================================================*/
+PRIVATE void mthread_thread_stop(thread)
+mthread_thread_t thread;
+{
+/* Stop thread from running. Deallocate resources. */
+  mthread_tcb_t *stop_thread;
+
+  if (!isokthreadid(thread)) mthread_panic("Invalid thread id"); 
+
+  stop_thread = &(threads[thread]);
+
+  if (stop_thread->m_state == DEAD) {
+       /* Already DEAD, nothing to do */
+       return;
+  }
+
+  mthread_thread_reset(thread);
+  if (mthread_cond_destroy(&(stop_thread->m_exited)) != 0)
+       mthread_panic("Could not destroy condition at thread deallocation\n");
+
+  used_threads--;
+  mthread_queue_add(&free_threads, thread);
+}
+
+
+/*===========================================================================*
+ *                             mthread_trampoline                           *
+ *===========================================================================*/
+PRIVATE void mthread_trampoline(void)
+{
+/* Execute the /current_thread's/ procedure. Store its result. */
+
+  void *r;
+
+  r = (threads[current_thread].m_proc)(threads[current_thread].m_arg);
+  mthread_exit(r); 
+}
+
diff --git a/lib/libmthread/attribute.c b/lib/libmthread/attribute.c
new file mode 100644 (file)
index 0000000..b696640
--- /dev/null
@@ -0,0 +1,354 @@
+#include <minix/mthread.h>
+#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 (file)
index 0000000..e86380f
--- /dev/null
@@ -0,0 +1,279 @@
+#include <minix/mthread.h>
+#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 (file)
index 0000000..0dc5871
--- /dev/null
@@ -0,0 +1,25 @@
+/* EXTERN should be extern, except for the allocate file */
+#ifdef ALLOCATE
+#undef EXTERN
+#define EXTERN
+#endif
+
+#include <assert.h>
+
+#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 (file)
index 0000000..230bd8b
--- /dev/null
@@ -0,0 +1,91 @@
+#include <minix/mthread.h>
+#include <stdio.h>
+#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 (file)
index 0000000..0597440
--- /dev/null
@@ -0,0 +1,288 @@
+#include <minix/mthread.h>
+#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 (file)
index 0000000..3d77a65
--- /dev/null
@@ -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 (file)
index 0000000..98f1750
--- /dev/null
@@ -0,0 +1,105 @@
+#include <minix/mthread.h>
+#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 (file)
index 0000000..a1834be
--- /dev/null
@@ -0,0 +1,197 @@
+#include <minix/mthread.h>
+#include "global.h"
+#include "proto.h"
+
+#define MAIN_CTX       &(mainthread.m_context)
+#define OLD_CTX                &(threads[old_thread].m_context);
+#define CURRENT_CTX    &(threads[current_thread].m_context)
+#define CURRENT_STATE  threads[current_thread].m_state
+PRIVATE int yield_all;
+
+/*===========================================================================*
+ *                             mthread_getcontext                           *
+ *===========================================================================*/
+PUBLIC int mthread_getcontext(ctx)
+ucontext_t *ctx;
+{
+/* Retrieve this process' current state.*/
+
+  /* We're not interested in FPU state nor signals, so ignore them. 
+   * Coincidentally, this significantly speeds up performance.
+   */
+  ctx->uc_flags |= (UCF_IGNFPU | UCF_IGNSIGM);
+  return getcontext(ctx);
+}
+
+
+/*===========================================================================*
+ *                             mthread_schedule                             *
+ *===========================================================================*/
+PUBLIC void mthread_schedule(void)
+{
+/* Pick a new thread to run and run it. In practice, this involves taking the 
+ * first thread off the (FIFO) run queue and resuming that thread. 
+ */
+
+  int old_thread;
+  ucontext_t *new_ctx, *old_ctx;
+
+  mthread_init();      /* Make sure mthreads is initialized */
+
+  old_thread = current_thread;
+
+  if (mthread_queue_isempty(&run_queue)) {
+       /* No runnable threads. Let main thread run. */
+
+       /* We keep track whether we're running the program's 'main' thread or
+        * a spawned thread. In case we're already running the main thread and
+        * there are no runnable threads, we can't jump back to its context. 
+        * Instead, we simply return.
+        */
+       if (running_main_thread) return;
+
+       /* We're running the last runnable spawned thread. Return to main
+        * thread as there is no work left.
+        */
+       running_main_thread = 1;
+       current_thread = NO_THREAD;
+  } else {
+       current_thread = mthread_queue_remove(&run_queue);
+       running_main_thread = 0;        /* Running thread after swap */
+  }
+
+  if (current_thread == NO_THREAD) 
+       new_ctx = MAIN_CTX;
+  else
+       new_ctx = CURRENT_CTX;
+
+  if (old_thread == NO_THREAD) 
+       old_ctx = MAIN_CTX;
+  else
+       old_ctx = OLD_CTX;
+
+  if (swapcontext(old_ctx, new_ctx) == -1) 
+       mthread_panic("Could not swap context");
+}
+
+
+/*===========================================================================*
+ *                             mthread_init_scheduler                       *
+ *===========================================================================*/
+PUBLIC void mthread_init_scheduler(void)
+{
+/* Initialize the scheduler */
+  mthread_queue_init(&run_queue);
+  yield_all = 0;
+
+}
+
+
+/*===========================================================================*
+ *                             mthread_suspend                              *
+ *===========================================================================*/
+PUBLIC void mthread_suspend(state)
+mthread_state_t state;
+{
+/* Stop the current thread from running. There can be multiple reasons for
+ * this; the process tries to lock a locked mutex (i.e., has to wait for it to
+ * become unlocked), the process has to wait for a condition, the thread
+ * volunteered to let another thread to run (i.e., it called yield and remains
+ * runnable itself), or the thread is dead.
+ */
+
+  int continue_thread = 0;
+
+  if (state == DEAD) mthread_panic("Shouldn't suspend with DEAD state");
+
+  threads[current_thread].m_state = state;
+  
+  /* Save current thread's context */
+  if (mthread_getcontext(CURRENT_CTX) != 0)
+       mthread_panic("Couldn't save current thread's context");
+
+  /* We return execution here with setcontext/swapcontext, but also when we
+   * simply return from the getcontext call. If continue_thread is non-zero, we
+   * are continuing the execution of this thread after a call from setcontext 
+   * or swapcontext.
+   */
+
+  if(!continue_thread) {
+       continue_thread = 1;
+       mthread_schedule(); /* Let other thread run. */
+  }
+}
+
+
+/*===========================================================================*
+ *                             mthread_unsuspend                            *
+ *===========================================================================*/
+PUBLIC void mthread_unsuspend(thread)
+mthread_thread_t thread; /* Thread to make runnable */
+{
+/* Mark the state of a thread runnable and add it to the run queue */
+
+  if (!isokthreadid(thread)) mthread_panic("Invalid thread id\n");
+  threads[thread].m_state = RUNNABLE;
+  mthread_queue_add(&run_queue, thread);
+}
+
+
+/*===========================================================================*
+ *                             mthread_yield                                *
+ *===========================================================================*/
+PUBLIC int mthread_yield(void)
+{
+/* Defer further execution of the current thread and let another thread run. */
+
+  mthread_init();      /* Make sure mthreads is initialized */
+
+  if (mthread_queue_isempty(&run_queue)) {     /* No point in yielding. */
+       return(-1);
+  } else if (current_thread == NO_THREAD) {
+       /* Can't yield this thread, but still give other threads a chance to
+        * run.
+        */
+       mthread_schedule();
+       return(-1);
+  }
+
+  mthread_queue_add(&run_queue, current_thread);
+  mthread_suspend(RUNNABLE); /* We're still runnable, but we're just kind
+                             * enough to let someone else run.
+                             */
+  return(0);
+}
+
+
+/*===========================================================================*
+ *                             mthread_yield_all                            *
+ *===========================================================================*/
+PUBLIC void mthread_yield_all(void)
+{
+/* Yield until there are no more runnable threads left. Two threads calling
+ * this function will lead to a deadlock.
+ */
+
+  mthread_init();      /* Make sure mthreads is initialized */
+
+  if (yield_all) mthread_panic("Deadlock: two threads trying to yield_all");
+  yield_all = 1;
+
+  /* This works as follows. Thread A is running and threads B, C, and D are
+   * runnable. As A is running, it is NOT on the run_queue (see
+   * mthread_schedule). It calls mthread_yield and will be added to the run
+   * queue, allowing B to run. B runs and suspends eventually, possibly still
+   * in a runnable state. Then C and D run. Eventually A will run again (and is
+   * thus not on the list). If B, C, and D are dead, waiting for a condition,
+   * or waiting for a lock, they are not on the run queue either. At that
+   * point A is the only runnable thread left.
+   */
+  while (!mthread_queue_isempty(&run_queue)) {
+       (void) mthread_yield();
+  }
+
+  /* Done yielding all threads. */
+  yield_all = 0;
+}
+
index 5abb5df3e06eccb3f638e9cdeb0f70c66d6662ed..b42c298792cc4ab57eb0e5e9fa7e76b610233844 100644 (file)
@@ -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 
index 48af18ae5fe69c59b3a8ee6e008588ca9e4150d2..332336eb7a31e49661b18ab31a9e472c60bf3253 100755 (executable)
--- 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 (file)
index 0000000..04b1ab5
--- /dev/null
@@ -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 <stdio.h>
+#include <minix/mthread.h>
+
+#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();
+}
+