diff --git a/.gitignore b/.gitignore index 1f7b35e..4facd9c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *~ .*.swp +*.o approximate-pi linked-list mandelbrot @@ -19,3 +20,4 @@ lua-foo binsearch test-inline-assembly test-inline-assembly.s +uiowa-threads-example diff --git a/Makefile b/Makefile index 046d249..af47822 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CFLAGS=-std=c99 -Wall -g -O2 INDENTOPTS=-kr --no-tabs --braces-on-func-def-line --indent-level2 -TARGETS=approximate-pi linked-list mandelbrot threads circular-buffer structs ncurses-pong bit-fuckery bit-fuckery2 checkcheck multibrot bloom wo-lernen lua-foo binsearch test-inline-assembly +TARGETS=approximate-pi linked-list mandelbrot threads circular-buffer structs ncurses-pong bit-fuckery bit-fuckery2 checkcheck multibrot bloom wo-lernen lua-foo binsearch test-inline-assembly uiowa-threads-example EXTRAS=mandelbrot.bmp multibrot.png test-inline-assembly.s .PHONY: all @@ -40,3 +40,6 @@ test-inline-assembly: test-inline-assembly.c test-inline-assembly.s: test-inline-assembly.c $(CC) -Wall -S -o $@ $< + +uiowa-threads-example: uiowa-threads.o uiowa-threads-example.c + $(CC) $(CFLAGS) -o $@ $^ diff --git a/uiowa-threads-example.c b/uiowa-threads-example.c new file mode 100644 index 0000000..b3c8db8 --- /dev/null +++ b/uiowa-threads-example.c @@ -0,0 +1,29 @@ +/**** + * file example.c + * + Program to demonstrate the thread management package * + Written by Douglas Jones, Feb 18, 1998 * + ****/ + +#include +#include "uiowa-threads.h" + +void test_thread( int n ) +{ + printf( "A%d\n", n ); + if (n > 1) thread_launch( 4000, test_thread, n-1 ); + thread_relinquish(); + printf( "B%d\n", n ); + thread_relinquish(); + printf( "C%d\n", n ); + thread_relinquish(); +} + +int main() +{ + thread_manager_init(); + thread_startup_report(); /***********/ + thread_launch( 4000, test_thread, 5 ); + thread_manager_start(); + /* control never reaches this point */ +} diff --git a/uiowa-threads.c b/uiowa-threads.c new file mode 100644 index 0000000..2df5790 --- /dev/null +++ b/uiowa-threads.c @@ -0,0 +1,486 @@ +/**** + * 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 +#include +#include + +/* 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 ); + } +} diff --git a/uiowa-threads.h b/uiowa-threads.h new file mode 100644 index 0000000..3a292ea --- /dev/null +++ b/uiowa-threads.h @@ -0,0 +1,77 @@ +/**** + * file threads.h + * + Header files for the user-level thread package + Written by Douglas Jones, Feb 18, 1998 + Added thread_free interface, Feb 19, 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! * + ****/ + + +/**** * + * Initialization routines * + * ****/ + +void thread_manager_init(); +/* call this once before launching any threads */ + +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 */ + +void thread_manager_start(); +/* call this once after launching at least one thread */ +/* this only returns when all threads are blocked! */ + + + +/**** * + * Routines to call from within a thread * + * ****/ + +void thread_relinquish(); +/* call this within a thread to allow scheduling of a new thread */ + +void thread_exit(); +/* call this within a thread to terminate that thread */ + +void thread_free( void * ptr ); +/* call this within a thread instead of calling free() */ + +/* note, thread_launch() may also be called within a thread */ + +/**** * + * Utility and debugging routines * + * ****/ + +void thread_startup_report(); +/* reports on the results of the study done in the startup */ + +void thread_safety_check(); +/* call this to check for thread stack overflow */ + + +/**** * + * Semaphore extension to thread package * + * ****/ + +/* the declaration of the semaphore type given here is a lie! */ +struct thread_semaphore { + void * unused_fields[3]; +}; + +void thread_semaphore_init( struct thread_semaphore * s, int v ); +/* call this to initialize a semaphore */ + +void thread_wait( struct thread_semaphore * s ); +/* call this within a thread to block on a semaphore */ + +void thread_signal( struct thread_semaphore * s ); +/* call this within a thread to signal a semaphore */