Last active
February 18, 2020 00:30
-
-
Save Jeswang/4ae2b612f0c2da20265d1d6407b6cd2b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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