Add libmthread and test59 to test the implementation
This commit is contained in:
@@ -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
|
||||
|
||||
16
lib/libmthread/Makefile
Normal file
16
lib/libmthread/Makefile
Normal file
@@ -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>
|
||||
500
lib/libmthread/allocate.c
Normal file
500
lib/libmthread/allocate.c
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
354
lib/libmthread/attribute.c
Normal file
354
lib/libmthread/attribute.c
Normal file
@@ -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
|
||||
|
||||
|
||||
279
lib/libmthread/condition.c
Normal file
279
lib/libmthread/condition.c
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
25
lib/libmthread/global.h
Normal file
25
lib/libmthread/global.h
Normal file
@@ -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;
|
||||
|
||||
91
lib/libmthread/misc.c
Normal file
91
lib/libmthread/misc.c
Normal file
@@ -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);
|
||||
}
|
||||
288
lib/libmthread/mutex.c
Normal file
288
lib/libmthread/mutex.c
Normal file
@@ -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
|
||||
|
||||
|
||||
39
lib/libmthread/proto.h
Normal file
39
lib/libmthread/proto.h
Normal file
@@ -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
|
||||
105
lib/libmthread/queue.c
Normal file
105
lib/libmthread/queue.c
Normal file
@@ -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);
|
||||
}
|
||||
|
||||
197
lib/libmthread/scheduler.c
Normal file
197
lib/libmthread/scheduler.c
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user