Skip to content

Instantly share code, notes, and snippets.

@Arachnid
Created August 3, 2009 20:26
Show Gist options
  • Save Arachnid/160808 to your computer and use it in GitHub Desktop.
Save Arachnid/160808 to your computer and use it in GitHub Desktop.
Simple fiber library. Compile with: gcc -o eepy.o -g -c eepy.c; gcc -o test -lpthread -g test.c eepy.o
#include <assert.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
#include <stdlib.h>
#include "eepy.h"
// Information about an active eepy process
typedef struct eepy_process {
jmp_buf context;
stack_t stack;
volatile int state;
int free_stack;
void (*start)(void);
struct eepy_process *next;
} eepy_process;
typedef struct {
eepy_process *current;
eepy_process *prev;
} eepy_thread_state;
struct {
volatile int initialized;
malloc_func *alloc;
free_func *free;
eepy_process *runnable;
eepy_process *last_runnable;
// Key for per-thread current process info
pthread_key_t threadkey;
// Used to lock and signal the runnable list
pthread_cond_t runnable_cond;
pthread_mutex_t runnable_lock;
// Prevents multiple concurrent process creations
pthread_mutex_t spawn_lock;
eepy_process *spawn_process;
} state;
void _eepy_restore(eepy_thread_state *threadstate, int nowait) {
pthread_mutex_lock(&state.runnable_lock);
if(nowait && state.runnable == NULL) {
pthread_mutex_unlock(&state.runnable_lock);
threadstate->current = threadstate->prev;
threadstate->prev = NULL;
threadstate->current->state = EEPY_RUNNING;
return;
}
while(state.runnable == NULL)
// Nothing to run, wait until there is
pthread_cond_wait(&state.runnable_cond, &state.runnable_lock);
// Pop the oldest runnable off the stack
threadstate->current = state.runnable;
state.runnable = state.runnable->next;
pthread_mutex_unlock(&state.runnable_lock);
threadstate->current->state = EEPY_RUNNING;
longjmp(threadstate->current->context, 1);
}
void _eepy_runqueue_add(eepy_process *process) {
pthread_mutex_lock(&state.runnable_lock);
process->next = NULL;
if(state.runnable != NULL) {
state.last_runnable->next = process;
state.last_runnable = process;
} else {
state.runnable = process;
state.last_runnable = process;
// Unblock someone waiting for something to run
pthread_cond_signal(&state.runnable_cond);
}
pthread_mutex_unlock(&state.runnable_lock);
}
void _eepy_onresume(eepy_thread_state *threadstate) {
if(threadstate->prev != NULL) {
// Clean up after the previous work unit
switch(threadstate->prev->state) {
case EEPY_STOPPED:
// Store the previous process to the end of the runqueue
_eepy_runqueue_add(threadstate->prev);
break;
case EEPY_TERMINATING:
// Clean up after the previous (terminated) process
if(threadstate->prev->free_stack)
state.free(threadstate->prev->stack.ss_sp);
state.free(threadstate->prev);
break;
}
threadstate->prev = NULL;
}
}
// Initializes the eepy library
void eepy_init() {
eepy_process *main_process;
eepy_thread_state *main_thread;
if(!state.initialized) {
pthread_key_create(&state.threadkey, NULL);
pthread_cond_init(&state.runnable_cond, NULL);
pthread_mutex_init(&state.runnable_lock, NULL);
pthread_mutex_init(&state.spawn_lock, NULL);
state.alloc = malloc;
state.free = free;
state.initialized = TRUE;
// Turn the current execution unit into a process
main_process = state.alloc(sizeof(eepy_process));
main_process->free_stack = FALSE;
main_process->state = EEPY_RUNNING;
main_thread = state.alloc(sizeof(eepy_thread_state));
main_thread->current = main_process;
main_thread->prev = NULL;
pthread_setspecific(state.threadkey, main_thread);
}
}
void eepy_yield() {
eepy_thread_state *threadstate = pthread_getspecific(state.threadkey);
eepy_process *current = threadstate->current;
current->state = EEPY_STOPPED;
threadstate->prev = current;
setjmp(current->context);
if(current->state != EEPY_STOPPED) {
// Subsequent invocation - clean up previous process and continue
_eepy_onresume(pthread_getspecific(state.threadkey));
} else {
// Initial invocation - go find a process to resume
_eepy_restore(threadstate, TRUE);
}
}
void eepy_main() {
eepy_thread_state *thread_state = state.alloc(sizeof(eepy_thread_state));
thread_state->current = NULL;
thread_state->prev = NULL;
pthread_setspecific(state.threadkey, thread_state);
_eepy_restore(NULL, FALSE);
}
void _eepy_process_main(int argument) {
// Save the current context, and return to terminate the signal handler scope
if(!setjmp(state.spawn_process->context))
return;
// We are being called again from the main context - call the function.
eepy_thread_state *threadstate = pthread_getspecific(state.threadkey);
_eepy_onresume(threadstate);
threadstate->current->start();
// Process terminating
threadstate->current->state = EEPY_TERMINATING;
_eepy_restore(threadstate, FALSE);
}
void eepy_spawn(void (*start)(), int stack_size) {
eepy_process *newprocess = state.alloc(sizeof(eepy_process));
stack_t oldStack;
struct sigaction handler, oldHandler;
if(stack_size <= 0)
stack_size = SIGSTKSZ;
newprocess->state = EEPY_STOPPED;
newprocess->start = start;
newprocess->free_stack = TRUE;
newprocess->stack.ss_flags = 0;
newprocess->stack.ss_size = stack_size;
newprocess->stack.ss_sp = state.alloc(stack_size);
// Grab the spawn lock
pthread_mutex_lock(&state.spawn_lock);
state.spawn_process = newprocess;
// Install the new stack for the signal handler
assert(sigaltstack(&newprocess->stack, &oldStack) == 0);
// Set and call the signal handler to create the stack
handler.sa_handler = &_eepy_process_main;
handler.sa_flags = SA_ONSTACK;
sigemptyset(&handler.sa_mask);
assert(sigaction(SIGUSR1, &handler, &oldHandler) == 0);
assert(raise(SIGUSR1) == 0);
// Restore the original stack and handler
sigaltstack(&oldStack, 0);
sigaction(SIGUSR1, &oldHandler, 0);
pthread_mutex_unlock(&state.spawn_lock);
_eepy_runqueue_add(newprocess);
}
#include <stdlib.h>
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
#define EEPY_STOPPED 0
#define EEPY_RUNNING 1
#define EEPY_TERMINATING 2
typedef void* (malloc_func)(size_t);
typedef void (free_func)(void*);
void eepy_init();
void eepy_yield();
void eepy_main();
void eepy_spawn(void (*start)(), int stack_size);
#include "eepy.h"
#include <stdio.h>
void other() {
printf("In other()\n");
eepy_yield();
printf("Back in other()\n");
}
int main(int argc, char *argv[]) {
eepy_init(NULL, NULL, 0);
eepy_spawn(other, 0);
printf("In main()\n");
eepy_yield();
printf("Back in main()\n");
eepy_yield();
exit(0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment