Skip to content

Instantly share code, notes, and snippets.

@Jeswang
Last active February 18, 2020 00:30
Show Gist options
  • Save Jeswang/4ae2b612f0c2da20265d1d6407b6cd2b to your computer and use it in GitHub Desktop.
Save Jeswang/4ae2b612f0c2da20265d1d6407b6cd2b to your computer and use it in GitHub Desktop.
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stddef.h>
#include <string.h>
#include <stdint.h>
#include <ucontext.h>
#include <sys/cdefs.h>
#include <sys/param.h>
#include <sys/signal.h>
#include <ucontext.h>
#include <errno.h>
#include <stddef.h>
#define uc_flags uc_onstack
#define UCF_SWAPPED 0x80000000
#define COROUTINE_DEAD 0
#define COROUTINE_READY 1
#define COROUTINE_RUNNING 2
#define COROUTINE_SUSPEND 3
struct schedule;
typedef void (*coroutine_func)(struct schedule *, void *ud);
#define STACK_SIZE (1024*1024)
#define DEFAULT_COROUTINE 16
struct coroutine;
struct schedule {
char stack[STACK_SIZE];
ucontext_t main;
int nco;
int cap;
int running;
struct coroutine **co;
};
struct coroutine {
coroutine_func func;
void *ud;
ucontext_t ctx;
struct schedule * sch;
ptrdiff_t cap;
ptrdiff_t size;
int status;
char *stack;
};
int
myswapcontext(ucontext_t *oucp, const ucontext_t *ucp)
{
int ret;
if ((oucp == NULL) || (ucp == NULL)) {
errno = EINVAL;
return (-1);
}
// Clean the UCF_SWAPPED field.
oucp->uc_flags &= ~UCF_SWAPPED;
ret = getcontext(oucp);
// It's possible that UCF_SWAPPED is set to true now.
// Enter only if ret is 0 and UCF_SWAPPED in oucp->uc_flags is 0.
if ((ret == 0) && !(oucp->uc_flags & UCF_SWAPPED)) {
// Set UCF_SWAPPED in oucp->uc_flags to 1.
oucp->uc_flags |= UCF_SWAPPED;
ret = setcontext(ucp);
}
return (ret);
}
int
myswapcontext2(ucontext_t *oucp, const ucontext_t *ucp, struct schedule * S, struct coroutine *C, char *top)
{
char dummy = 0;
assert(top - &dummy <= STACK_SIZE);
if (C->cap < top - &dummy) {
free(C->stack);
C->cap = top-&dummy;
C->stack = malloc(C->cap);
}
C->size = top - &dummy;
memcpy(C->stack, &dummy, C->size);
C->status = COROUTINE_SUSPEND;
S->running = -1;
int ret;
if ((oucp == NULL) || (ucp == NULL)) {
errno = EINVAL;
return (-1);
}
// Clean the UCF_SWAPPED field.
oucp->uc_flags &= ~UCF_SWAPPED;
ret = getcontext(oucp);
// It's possible that UCF_SWAPPED is set to true now.
// Enter only if ret is 0 and UCF_SWAPPED in oucp->uc_flags is 0.
if ((ret == 0) && !(oucp->uc_flags & UCF_SWAPPED)) {
// Set UCF_SWAPPED in oucp->uc_flags to 1.
oucp->uc_flags |= UCF_SWAPPED;
ret = setcontext(ucp);
}
return (ret);
}
struct coroutine *
_co_new(struct schedule *S , coroutine_func func, void *ud) {
struct coroutine * co = malloc(sizeof(*co));
co->func = func;
co->ud = ud;
co->sch = S;
co->cap = 0;
co->size = 0;
co->status = COROUTINE_READY;
co->stack = NULL;
return co;
}
void
_co_delete(struct coroutine *co) {
free(co->stack);
free(co);
}
struct schedule *
coroutine_open(void) {
struct schedule *S = malloc(sizeof(*S));
S->nco = 0;
S->cap = DEFAULT_COROUTINE;
S->running = -1;
S->co = malloc(sizeof(struct coroutine *) * S->cap);
memset(S->co, 0, sizeof(struct coroutine *) * S->cap);
return S;
}
void
coroutine_close(struct schedule *S) {
int i;
for (i=0;i<S->cap;i++) {
struct coroutine * co = S->co[i];
if (co) {
_co_delete(co);
}
}
free(S->co);
S->co = NULL;
free(S);
}
int
coroutine_new(struct schedule *S, coroutine_func func, void *ud) {
struct coroutine *co = _co_new(S, func , ud);
if (S->nco >= S->cap) {
int id = S->cap;
S->co = realloc(S->co, S->cap * 2 * sizeof(struct coroutine *));
memset(S->co + S->cap , 0 , sizeof(struct coroutine *) * S->cap);
S->co[S->cap] = co;
S->cap *= 2;
++S->nco;
return id;
} else {
int i;
for (i=0;i<S->cap;i++) {
int id = (i+S->nco) % S->cap;
if (S->co[id] == NULL) {
S->co[id] = co;
++S->nco;
return id;
}
}
}
assert(0);
return -1;
}
static void
mainfunc(uint32_t low32, uint32_t hi32) {
uintptr_t ptr = (uintptr_t)low32 | ((uintptr_t)hi32 << 32);
struct schedule *S = (struct schedule *)ptr;
int id = S->running;
struct coroutine *C = S->co[id];
C->func(S,C->ud);
_co_delete(C);
S->co[id] = NULL;
--S->nco;
S->running = -1;
}
void
coroutine_resume(struct schedule * S, int id) {
assert(S->running == -1);
assert(id >=0 && id < S->cap);
struct coroutine *C = S->co[id];
if (C == NULL)
return;
int status = C->status;
switch(status) {
case COROUTINE_READY:
getcontext(&C->ctx);
C->ctx.uc_stack.ss_sp = S->stack;
C->ctx.uc_stack.ss_size = STACK_SIZE;
// C->ctx.uc_link = &S->main;
S->running = id;
C->status = COROUTINE_RUNNING;
uintptr_t ptr = (uintptr_t)S;
makecontext(&C->ctx, (void (*)(void)) mainfunc, 2, (uint32_t)ptr, (uint32_t)(ptr>>32));
myswapcontext(&S->main, &C->ctx);
break;
case COROUTINE_SUSPEND:
memcpy(S->stack + STACK_SIZE - C->size, C->stack, C->size);
S->running = id;
C->status = COROUTINE_RUNNING;
myswapcontext(&S->main, &C->ctx);
break;
default:
assert(0);
}
}
static void
_save_stack(struct coroutine *C, char *top) {
char dummy = 0;
assert(top - &dummy <= STACK_SIZE);
if (C->cap < top - &dummy) {
free(C->stack);
C->cap = top-&dummy;
C->stack = malloc(C->cap);
}
C->size = top - &dummy;
memcpy(C->stack, &dummy, C->size);
}
void
coroutine_yield(struct schedule * S) {
int id = S->running;
assert(id >= 0);
struct coroutine * C = S->co[id];
assert((char *)&C > S->stack);
// _save_stack(C,S->stack + STACK_SIZE);
// C->status = COROUTINE_SUSPEND;
// S->running = -1;
myswapcontext2(&C->ctx , &S->main, S, C, S->stack + STACK_SIZE);
}
int
coroutine_status(struct schedule * S, int id) {
assert(id>=0 && id < S->cap);
if (S->co[id] == NULL) {
return COROUTINE_DEAD;
}
return S->co[id]->status;
}
int
coroutine_running(struct schedule * S) {
return S->running;
}
#include <stdio.h>
struct args {
int n;
};
static void
foo(struct schedule * S, void *ud) {
struct args * arg = ud;
int start = arg->n;
int i;
for (i=0;i<5;i++) {
printf("coroutine %d : %d\n",coroutine_running(S) , start + i);
coroutine_yield(S);
}
}
static void
test(struct schedule *S) {
struct args arg1 = { 0 };
struct args arg2 = { 100 };
int co1 = coroutine_new(S, foo, &arg1);
int co2 = coroutine_new(S, foo, &arg2);
printf("main start\n");
while (coroutine_status(S,co1) && coroutine_status(S,co2)) {
coroutine_resume(S,co1);
coroutine_resume(S,co2);
}
printf("main end\n");
}
int
main() {
struct schedule * S = coroutine_open();
test(S);
coroutine_close(S);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment