You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
487 lines
13 KiB
C
487 lines
13 KiB
C
/****
|
|
* file threads.c
|
|
*
|
|
A partial implementation of a user-level thread package
|
|
Written by Douglas Jones, Feb 17, 1998
|
|
Updated with minor bug fix in thread_exit, Feb 19, 1998
|
|
Updated so free called on original stack, Feb 19, 2002
|
|
Updated with defines for _longjmp, Aug 13, 2002
|
|
|
|
Copyright Douglas Jones, 1998.
|
|
|
|
Permission is hereby granted to make and modify
|
|
personal copies of this code for noncommercial or
|
|
educational use.
|
|
*
|
|
All other rights are reserved! *
|
|
****/
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <setjmp.h>
|
|
|
|
/* just in case this isn't compiled on a UNIX system
|
|
we back off to use the standard setjmp and longjmp.
|
|
UNIX systems have _setjmp that just does control
|
|
transfers, and then setjmp (no underscore) that also
|
|
saves the signal masks. Saving those makes sense
|
|
for exception handlers, but not for coroutines.
|
|
*/
|
|
#ifndef _setjmp
|
|
#define _setjmp setjmp
|
|
#endif
|
|
|
|
#ifndef _longjmp
|
|
#define _longjmp longjmp
|
|
#endif
|
|
|
|
/****
|
|
* PART I: -- Reverse Engineer the jump buffer *
|
|
* This is the key to user-level context switching *
|
|
****/
|
|
/**** *
|
|
* tools used by studyit to understand jump buffer structure *
|
|
* ****/
|
|
static int * low_bound; /* below probe on stack */
|
|
static int * probe_local; /* local to probe on stack */
|
|
static int * high_bound; /* above probe on stack */
|
|
static int * prior_local; /* value of probe_local from earlier call */
|
|
|
|
static jmp_buf probe_env; /* saved environment of probe */
|
|
static jmp_buf probe_sameAR; /* second environment saved by same call */
|
|
static jmp_buf probe_samePC; /* environment saved on previous call */
|
|
|
|
static jmp_buf * ref_probe = &probe_samePC; /* switches between probes */
|
|
|
|
/****
|
|
* reports from studyit giving results of study
|
|
* -- our focus here is on learning how to make
|
|
a jump buffer that points to newly allocated *
|
|
activation record on the heap *
|
|
****/
|
|
static int stack_direction; /* +1 if stack grows up, -1 if down */
|
|
long int local_offset; /* lowest location in AR from local vars */
|
|
jmp_buf probe_record; /* nonzero entries mean fields that change */
|
|
|
|
/****
|
|
* code to explore the activation record *
|
|
* and jump buffer structure *
|
|
****/
|
|
void boundhigh()
|
|
/* purpose: set high_bound to a local variable beyond probe on stack */
|
|
{
|
|
int c;
|
|
high_bound = &c;
|
|
}
|
|
|
|
void probe()
|
|
/* purpose: probe the activation record and jump buffer structure */
|
|
{
|
|
int c;
|
|
prior_local = probe_local;
|
|
probe_local = &c;
|
|
_setjmp( *ref_probe );
|
|
ref_probe = &probe_env;
|
|
_setjmp( probe_sameAR );
|
|
(void) boundhigh();
|
|
}
|
|
|
|
void boundlow()
|
|
/* purpose: set low_bound to a local variable before probe on stack */
|
|
{
|
|
int c;
|
|
low_bound = &c;
|
|
(void) probe();
|
|
}
|
|
|
|
void fill()
|
|
/* purpose: put some padding on the stack and then study the result */
|
|
{
|
|
(void) boundlow();
|
|
}
|
|
|
|
static void studyit()
|
|
/* purpose: study the result of probes */
|
|
{
|
|
if (sizeof(long int) != sizeof(long int *)) {
|
|
fprintf( stderr, "Pointer size is wrong\n" );
|
|
}
|
|
|
|
if (((long int)high_bound) > ((long int)low_bound)) {
|
|
/* stack grows up */
|
|
stack_direction = 1;
|
|
} else {
|
|
/* stack grows down */
|
|
stack_direction = -1;
|
|
}
|
|
|
|
/* study the jump buffer */
|
|
{
|
|
int i;
|
|
long int * p;
|
|
long int * sameAR;
|
|
long int * samePC;
|
|
long int * probe_rec;
|
|
long int prior_diff = (long int)probe_local
|
|
- (long int)prior_local;
|
|
long int min_frame = (long int)probe_local;
|
|
|
|
/* following line views jump buffer as array of long int */
|
|
p = (long int *)probe_env;
|
|
sameAR = (long int *)probe_sameAR;
|
|
samePC = (long int *)probe_samePC;
|
|
probe_rec = (long int *)probe_record;
|
|
|
|
for (i = 0; i < sizeof(jmp_buf); i += sizeof(long int) ) {
|
|
*probe_rec = 0;
|
|
if (*p != *samePC) {
|
|
if (*p != *sameAR) {
|
|
fprintf( stderr, "No Thread Launch\n" );
|
|
exit(-1);
|
|
}
|
|
if ((*p - *samePC) == prior_diff) {
|
|
/* record frame dependency */
|
|
*probe_rec = 1;
|
|
if (stack_direction == 1) { /* up */
|
|
if (min_frame > *p) {
|
|
min_frame = *p;
|
|
}
|
|
} else { /* grows down */
|
|
if (min_frame < *p) {
|
|
min_frame = *p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
p++, sameAR++, samePC++, probe_rec++;
|
|
}
|
|
|
|
if (stack_direction == 1) { /* grows up */
|
|
local_offset
|
|
= (long int)probe_local - min_frame;
|
|
} else { /* stack grows down */
|
|
local_offset
|
|
= min_frame - (long int)probe_local;
|
|
}
|
|
}
|
|
}
|
|
|
|
void thread_startup_report()
|
|
/* reports on the results of the study done by above code */
|
|
{
|
|
int i;
|
|
long int * jb;
|
|
printf( "Thread startup study results\n" );
|
|
if (stack_direction == 1) {
|
|
printf( " Stack grows up\n" );
|
|
} else {
|
|
printf( " Stack grows down\n" );
|
|
}
|
|
printf( " Local variable offset from AR base = %1d\n", local_offset );
|
|
printf( " Jump buffer fields subject to modification:\n" );
|
|
jb = (long int *)probe_record;
|
|
for (i = 0; i < sizeof(jmp_buf); i += sizeof(long int) ) {
|
|
if (*jb != 0) {
|
|
printf( " %1d\n", i );
|
|
}
|
|
jb++;
|
|
}
|
|
printf( " Jump buffer size: %1d\n", sizeof(jmp_buf) );
|
|
}
|
|
|
|
/****
|
|
* PART II: -- The Thread Manager *
|
|
* Based on the results reported by Part I *
|
|
****/
|
|
|
|
/**** *
|
|
* thread data types *
|
|
* ****/
|
|
|
|
struct thread {
|
|
struct thread * next; /* used to link threads into queues */
|
|
int size; /* the size of the thread */
|
|
void (* proc)(int); /* procedure for body of thread */
|
|
int param; /* parameter to base procedure */
|
|
jmp_buf state; /* the state of the thread */
|
|
|
|
/* size bytes of stack follow here */
|
|
|
|
};
|
|
|
|
#define thread_null (struct thread *)0
|
|
|
|
struct thread_queue {
|
|
struct thread * head;
|
|
struct thread * tail;
|
|
};
|
|
|
|
/**** *
|
|
* Fundamental scheduler data structures *
|
|
* ****/
|
|
|
|
static struct thread_queue readylist; /* the list of all ready threads */
|
|
static struct thread * current; /* the current running thread */
|
|
static void * mem_to_free; /* anything to be passed to free() */
|
|
|
|
static char * thread_error; /* string giving package death cause */
|
|
static jmp_buf thread_death; /* used on thread package death */
|
|
static jmp_buf go_free_it; /* used to call free from original stk */
|
|
|
|
/**** *
|
|
* code for thread_queue data type *
|
|
* ****/
|
|
|
|
static void thread_queue_init( struct thread_queue * q )
|
|
/* initialize q */
|
|
{
|
|
q->head = thread_null;
|
|
q->tail = thread_null;
|
|
}
|
|
|
|
static void thread_enqueue( struct thread * t, struct thread_queue * q )
|
|
/* enqueue t on q */
|
|
{
|
|
t->next = thread_null;
|
|
if (q->head == thread_null) {
|
|
q->head = t;
|
|
q->tail = t;
|
|
} else {
|
|
q->tail->next = t;
|
|
q->tail = t;
|
|
}
|
|
}
|
|
|
|
static struct thread * thread_dequeue( struct thread_queue * q )
|
|
/* dequeue and return a thread from q */
|
|
{
|
|
if (q->head == thread_null) {
|
|
return thread_null;
|
|
} else {
|
|
struct thread * t;
|
|
t = q->head;
|
|
q->head = t->next;
|
|
return t;
|
|
}
|
|
}
|
|
|
|
/**** *
|
|
* user callable thread management routines *
|
|
* ****/
|
|
|
|
void thread_manager_init()
|
|
/* call this once before launching any threads */
|
|
{
|
|
/* first study current system and see if thread launch worth trying */
|
|
(void) fill(); /* do a probe with filler on stack */
|
|
(void) boundlow(); /* do a probe without filler */
|
|
(void) studyit(); /* figure out what it all means */
|
|
|
|
/* now build thread manager data structures */
|
|
thread_queue_init( &readylist );
|
|
mem_to_free = (void *)0;
|
|
current = thread_null;
|
|
}
|
|
|
|
void thread_manager_start()
|
|
/* call this once after launching at least one thread */
|
|
/* this only returns when all threads are blocked! */
|
|
{
|
|
current = thread_dequeue( &readylist );
|
|
if (current == thread_null) {
|
|
/* crisis */
|
|
fprintf(stderr, "Thread manager start failure, no threads!\n");
|
|
exit(-1);
|
|
}
|
|
if (_setjmp( thread_death )) {
|
|
/* comes here when _longjmp( thread_death, 1 ) done */
|
|
fprintf(stderr,
|
|
"Thread manager terminated, %s!\n", thread_error );
|
|
if (mem_to_free != (void *)0 ) {
|
|
/* see comments below */
|
|
free( mem_to_free );
|
|
}
|
|
} else {
|
|
/* we will come back here whenever we need to deallocate */
|
|
(void) _setjmp( go_free_it );
|
|
|
|
if (mem_to_free != (void *)0 ) {
|
|
/* it's not safe to call free() anywhere but on the
|
|
real stack, so once the thread manager is running,
|
|
this is the only safe place to call it!
|
|
*/
|
|
free( mem_to_free );
|
|
mem_to_free = (void *)0;
|
|
}
|
|
|
|
_longjmp( current->state, 1 );
|
|
}
|
|
}
|
|
|
|
void thread_relinquish()
|
|
/* call this within a thread to allow scheduling of a new thread */
|
|
{
|
|
if (_setjmp( current->state ) == 0) {
|
|
thread_enqueue( current, &readylist );
|
|
current = thread_dequeue( &readylist );
|
|
_longjmp( current->state, 1 );
|
|
}
|
|
}
|
|
|
|
void thread_exit()
|
|
/* call this within a thread to terminate that thread */
|
|
{
|
|
/* we can't deallocate, so we schedule this thread for deallocation */
|
|
mem_to_free = (void *)current;
|
|
|
|
/* now, get the next thread to run */
|
|
current = thread_dequeue( &readylist );
|
|
if (current == thread_null) {
|
|
/* crisis */
|
|
thread_error = "ready list empty";
|
|
_longjmp( thread_death, 1 );
|
|
}
|
|
|
|
_longjmp( go_free_it, 1 );
|
|
}
|
|
|
|
void thread_launch( int size, void (* proc)(int), int param )
|
|
/* call this to launch proc(param) as a thread */
|
|
/* may be called from main or from any thread */
|
|
{
|
|
struct thread * t;
|
|
t = (struct thread *)malloc( sizeof(struct thread) + size );
|
|
t->size = size;
|
|
t->proc = proc;
|
|
t->param = param;
|
|
if (_setjmp( t->state )) {
|
|
/* comes here only when new thread scheduled first time */
|
|
(*current->proc)(current->param);
|
|
thread_exit();
|
|
}
|
|
/* continue initialization */
|
|
{
|
|
long int * s;
|
|
long int * probe_rec;
|
|
long int local_base = (long int)&t; /* address of local t */
|
|
long int new_base;
|
|
int i;
|
|
|
|
/* the following code copies the contents of the
|
|
activation record, just in case it might prove
|
|
useful; note that we don't know that this is
|
|
needed on any machine, but it might, so we do it. */
|
|
|
|
if (stack_direction == 1) { /* grows up */
|
|
long int * src;
|
|
long int * dst;
|
|
new_base = (long int)t + sizeof( struct thread )
|
|
+ local_offset;
|
|
src = (long int *) (local_base - local_offset);
|
|
dst = (long int *) (new_base - local_offset);
|
|
for (i = 0; i <= local_offset; i += sizeof(long int)) {
|
|
*dst++ = *src++;
|
|
}
|
|
} else { /* grows down */
|
|
long int * src;
|
|
long int * dst;
|
|
new_base = (long int)t + sizeof( struct thread )
|
|
+ size - (local_offset + sizeof(long int));
|
|
src = (long int *) (local_base);
|
|
dst = (long int *) (new_base);
|
|
for (i = 0; i <= local_offset; i += sizeof(long int)) {
|
|
*dst++ = *src++;
|
|
}
|
|
}
|
|
|
|
/* the following code adjusts the references to the
|
|
activation record in the saved thread state so that
|
|
they point to the base of the newly allocated stack */
|
|
|
|
s = (long int *)(t->state);
|
|
probe_rec = (long int *)probe_record;
|
|
for (i = 0; i < sizeof(jmp_buf); i += sizeof(long int) ) {
|
|
if (*probe_rec != 0) {
|
|
/* adjust this field of state */
|
|
*s += new_base - local_base;
|
|
}
|
|
s++;
|
|
probe_rec++;
|
|
}
|
|
}
|
|
thread_enqueue( t, &readylist );
|
|
}
|
|
|
|
void thread_free( void * ptr )
|
|
/* call this from within threads instead of free, because some
|
|
heap managers are paranoid enough to forbid a call to free
|
|
when the stack itself is in a block allocated in the heap */
|
|
{
|
|
mem_to_free = ptr;
|
|
if (_setjmp( current->state ) == 0) {
|
|
_longjmp( go_free_it, 1 );
|
|
}
|
|
/* after call to free(), there will be a longjmp back to here */
|
|
}
|
|
|
|
void thread_safety_check()
|
|
/* call this to check for thread stack overflow */
|
|
{
|
|
long int t = (long int)current + sizeof(struct thread);
|
|
if ( ((long int)&t < t) || ((long int)&t > (t + current->size)) ) {
|
|
/* crisis */
|
|
thread_error = "thread stack overflow";
|
|
_longjmp( thread_death, 1 );
|
|
}
|
|
}
|
|
|
|
/**** *
|
|
* thread manager extension for semaphores *
|
|
* ****/
|
|
|
|
|
|
/* semaphore representation known only to semaphore methods */
|
|
struct thread_semaphore {
|
|
int count;
|
|
struct thread_queue queue;
|
|
};
|
|
|
|
/* methods applying to semaphores */
|
|
void thread_semaphore_init( struct thread_semaphore * s, int v )
|
|
/* call this to initialize semaphore s to a count of v */
|
|
{
|
|
s->count = v;
|
|
thread_queue_init( &s->queue );
|
|
}
|
|
|
|
void thread_wait( struct thread_semaphore * s )
|
|
/* call this within a thread to block on a semaphore */
|
|
{
|
|
if (s->count > 0) {
|
|
s->count--;
|
|
} else {
|
|
if (_setjmp( current->state ) == 0) {
|
|
thread_enqueue( current, &s->queue );
|
|
current = thread_dequeue( &readylist );
|
|
if (current == thread_null) {
|
|
/* crisis */
|
|
thread_error = "possible deadlock";
|
|
|
|
_longjmp( thread_death, 1 );
|
|
}
|
|
_longjmp( current->state, 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void thread_signal( struct thread_semaphore * s )
|
|
/* call this within a thread to signal a semaphore */
|
|
{
|
|
struct thread * t = thread_dequeue( &s->queue );
|
|
if (t == thread_null) {
|
|
s->count++;
|
|
} else {
|
|
thread_enqueue( t, &readylist );
|
|
}
|
|
}
|