user-level thread code from http://homepage.cs.uiowa.edu/~jones/opsys/threads/
parent
fcdc22b861
commit
a0999008e0
@ -0,0 +1,29 @@
|
||||
/****
|
||||
* file example.c
|
||||
*
|
||||
Program to demonstrate the thread management package *
|
||||
Written by Douglas Jones, Feb 18, 1998 *
|
||||
****/
|
||||
|
||||
#include <stdio.h>
|
||||
#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 */
|
||||
}
|
@ -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 <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 );
|
||||
}
|
||||
}
|
@ -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 */
|
Loading…
Reference in New Issue