coroutine实现注解

来源:互联网 发布:哈尔滨师范大学知乎 编辑:程序博客网 时间:2024/06/05 07:46
//看了一个云风实现的coroutine c版本,注释了一下。(by kevin)//==============================================#ifndef C_COROUTINE_H#define C_COROUTINE_H#define COROUTINE_DEAD 0#define COROUTINE_READY 1#define COROUTINE_RUNNING 2#define COROUTINE_SUSPEND 3struct schedule;typedef void (*coroutine_func)(struct schedule *, void *ud);struct schedule * coroutine_open(void);void coroutine_close(struct schedule *);int coroutine_new(struct schedule *, coroutine_func, void *ud);void coroutine_resume(struct schedule *, int id);int coroutine_status(struct schedule *, int id);int coroutine_running(struct schedule *);void coroutine_yield(struct schedule *);#endif//==============================================#include "coroutine.h"#include <stdio.h>#include <stdlib.h>#ifdef _ON_MAC_#include <sys/ucontext.h>#else#include <ucontext.h>#endif//FIXME 在macosx下能编译过,但是运行时,co[1]->status不知何时被修改,导致191行assert()失败#include <assert.h>#include <stddef.h>#include <string.h>#include <stdint.h>#define STACK_SIZE (1024*1024)#define DEFAULT_COROUTINE 16struct coroutine;struct schedule {char stack[STACK_SIZE]; //1M的栈空间, 用来保存当前正在运行的coroutine的stack。                            //主函数的栈不保存,它随着swapcontext带来带去ucontext_t main; //main的上下文, 从coroutine切换回来就需要它int nco; //已经使用的coint cap; //总共co 的数量, 即将会malloc(cap*sizeof(coroutine*)))大小内存int running; // schedule状态, 正在执行哪个coroutine.. = idstruct coroutine **co; //保存所有的coroutine信息};struct coroutine {coroutine_func func; //协程所调用函数                      //typedef void (*coroutine_func)(struct schedule *, void *ud);void *ud;   //user dataucontext_t ctx; //此协程上下文struct schedule * sch; //此coroutine从属的scheduleptrdiff_t cap; //为coroutine私有栈分配的空间 cap>=sizeptrdiff_t size; //coroutine私有栈的实际大小int status; //此coroutine的状态,是正在运行,还是挂起                //#define COROUTINE_DEAD 0                //#define COROUTINE_READY 1                //#define COROUTINE_RUNNING 2                //#define COROUTINE_SUSPEND 3char *stack;};//创建和初始化一个coroutine, 内部使用struct coroutine * _co_new(struct schedule *S , coroutine_func func, void *ud) {struct coroutine * co = malloc(sizeof(*co));    printf("%s:%s co:%p\n", __FILE__, __FUNCTION__, co);co->func = func;co->ud = ud;co->sch = S;co->cap = 0;co->size = 0;co->status = COROUTINE_READY; //创建完进入READY状态,等resume()co->stack = NULL; //在需要切换 出去,要保存此coroutine的栈时才使用,动态分配内存return co;}//删除一个coroutinevoid_co_delete(struct coroutine *co) {    printf("%s:%s\n", __FILE__, __FUNCTION__);free(co->stack); //因创建时已经置空,故可直接free    // co->ud 不用删除? 算是由外界传入,非吾所管理.并且你都不知道调用者是怎么分配的,如示例中的argsfree(co);}//创建一个schedule, 感觉改名叫schedule_new或许更合适?想想不改也成,不能制造太多的概念给调用者struct schedule * coroutine_open(void) {    printf("%s:%s\n", __FILE__, __FUNCTION__);struct schedule *S = malloc(sizeof(*S));S->nco = 0;S->cap = DEFAULT_COROUTINE;S->running = -1; //-1是为尚无任何coroutine在运行S->co = malloc(sizeof(struct coroutine *) * S->cap); //分配空间存指针memset(S->co, 0, sizeof(struct coroutine *) * S->cap);return S;}//关闭schedule,释放所有的coroutinevoid coroutine_close(struct schedule *S) {    printf("%s:%s\n", __FILE__, __FUNCTION__);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);}//创建一个coroutine,并返回其idint coroutine_new(struct schedule *S, coroutine_func func, void *ud) {    printf("%s:%s\n", __FILE__, __FUNCTION__);    //调用内部函数创建coroutinestruct coroutine *co = _co_new(S, func , ud);    //将创建的coroutine加入到schedule中管理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;        printf("%s:%s:%d gen coroutine id:%d\n", __FILE__, __FUNCTION__,__LINE__, id);return id;} else {int i;        //这里从前向后查找空闲的位置并使用,看来此id会很快被复用的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;                printf("%s:%s:%d gen coroutine id:%d co:%p\n", __FILE__, __FUNCTION__,__LINE__, id, co);return id;}}}assert(0);return -1;}//从这里,调用coroutine的callback,当callback()返回后,此coroutine即结束static voidmainfunc(uint32_t low32, uint32_t hi32) {    printf("%s:%s\n", __FILE__, __FUNCTION__);    //为啥整了个这样的东东, ptr -- schedule*uintptr_t ptr = (uintptr_t)low32 | ((uintptr_t)hi32 << 32);struct schedule *S = (struct schedule *)ptr;int id = S->running;//可能-1吗, 目前是调用此函数前就设置了此次croutine idstruct coroutine *C = S->co[id];C->func(S,C->ud); //调用coroutine的callback(?)    //当func()调用结束后,此co即可宣告死亡_co_delete(C);S->co[id] = NULL;--S->nco;S->running = -1; //-1代表当前没有coroutine在执行, 在这个状态下,执行流(将)交给主线程(main)}//运行coroutine, resume是叫唤醒的意思吗,重新回到。。void coroutine_resume(struct schedule * S, int id) {    printf("%s:%s\n", __FILE__, __FUNCTION__);    printf("coroutine:[%d]\n", id);assert(S->running == -1);assert(id >=0 && id < S->cap);struct coroutine *C = S->co[id];    printf("coroutine:[%d] co:%p\n", id, C);if (C == NULL)return;int status = C->status;switch(status) {case COROUTINE_READY:        printf("coroutine:[%d] COROUTINE_READY\n", id);getcontext(&C->ctx);//这个调用 ,将会使ctx内的东西和当前的上下文关联上(ss,sp??)C->ctx.uc_stack.ss_sp = S->stack; //要点: 对于context,  stack需要自己分配给它,这里用S->stack传过去,相当于设置了堆栈的指针,如此使函数的调用过程中的栈信息放到了我们想要保存的位置 . 这S->stack相当于等下函数调用的栈顶C->ctx.uc_stack.ss_size = STACK_SIZE; //提供栈大小,计算出顶底C->ctx.uc_link = &S->main; //这个是当这个context返回时, resume的目标S->running = id;//正在运行croutine id, 如此看来这块是非线程安全?C->status = COROUTINE_RUNNING;uintptr_t ptr = (uintptr_t)S;        //这里为何要将uintptr分为两个uint32_t传递? --> man makecontextmakecontext(&C->ctx, (void (*)(void)) mainfunc, 2, (uint32_t)ptr, (uint32_t)(ptr>>32));        //将当前的上下文保存到S->main中,并且控制权交给C->ctx. 即去执行ctx中设置的函数等等swapcontext(&S->main, &C->ctx);        printf("coroutine:[%d] COROUTINE_READY swapcontext return\n", id);break;case COROUTINE_SUSPEND://之前被yield过了,它已经有了自己的stack数据        printf("coroutine:[%d] COROUTINE_SUSPEND\n", id);        //将要唤醒的croutine的stack拷贝到S->stack末尾去,这是何意?为什么是末尾        //答复上面:因为stack是从下往上(从高地址往低?)放的,比如入栈一个int,它会放在S->stack+STACK_SIZE位置,然后栈顶往上移sizeof(int), 所以我们拷贝时,要把coroutine的私有栈拷贝到S->stack + STACK_SIZE - C->size的位置memcpy(S->stack + STACK_SIZE - C->size, C->stack, C->size);S->running = id;C->status = COROUTINE_RUNNING;swapcontext(&S->main, &C->ctx);        printf("coroutine:[%d] COROUTINE_SUSPEND swapcontext\n", id);break;default:        printf("ERROR[coroutine status]:%d", status);assert(0);}}//保存croutine的stack内容static void_save_stack(struct coroutine *C, char *top) {    printf("%s:%s top:%p\n", __FILE__, __FUNCTION__, top);    //在栈上创建一个局部变量, 它就是栈顶了,往下的那一段都是函数调用的堆栈char dummy = 0;assert(top - &dummy <= STACK_SIZE);if (C->cap < top - &dummy) {free(C->stack);C->cap = top-&dummy; //保存了此时coroutine的私有栈大小C->stack = malloc(C->cap); //C->stack用来保存coroutine自身的栈内容}C->size = top - &dummy; //计算出需要保存的栈长度    printf("%s:%s C->stack:%p dummy:%p C->size:%ld\n",             __FILE__, __FUNCTION__, C->stack, &dummy, C->size);memcpy(C->stack, &dummy, C->size);}voidcoroutine_yield(struct schedule * S) {    printf("%s:%s\n", __FILE__, __FUNCTION__);int id = S->running;    //这个assert(id>=0) 即暗示了此函数只会在coroutine中调用。assert(id >= 0);    //取出正在运行的croutinestruct coroutine * C = S->co[id];    //这个assert()是何意? 这里的S->stack为何是栈上的地址?不是从堆上分配的吗    //上面的疑问解答:因为swapcontext()时,指定了ss_sp这S->stack,这样当context执行时,它的栈是放在S->stack上的。    //所以C, S->stack 它们的地址都是在相近地    printf("&C:%p S->stack:%p\n", &C, S->stack);assert((char *)&C > S->stack);    //保存这个coroutine的stack_save_stack(C,S->stack + STACK_SIZE);    //使进入SUSPEND状态C->status = COROUTINE_SUSPEND; S->running = -1;    //切换回S->main, 就是当初从coroutine_resume()中过来的那里swapcontext(&C->ctx , &S->main);}//获取某个coroutine的状态int coroutine_status(struct schedule * S, int id) {    printf("%s:%s\n", __FILE__, __FUNCTION__);assert(id>=0 && id < S->cap);if (S->co[id] == NULL) {return COROUTINE_DEAD;}return S->co[id]->status;}//获得正在运行的croutine idint coroutine_running(struct schedule * S) {    printf("%s:%s\n", __FILE__, __FUNCTION__);return S->running;}  

1 0