co_create 初始化相关
1 | int co_create( stCoRoutine_t **ppco,const stCoRoutineAttr_t *attr, |
将Env_t信息保存在全局变量g_arrCoEnvPerThread中对应于threadId的位置
创建一个空协程,被设置为当前Env_t的main routine,用于运行该线程的主逻辑
Epoll_t相关的信息初始化,管理时间片相关
stCoRoutine_t 保存每个协程的信息
co_create_env
创建协程的函数是co_create_env(),每个协程有自己密切相关的结构stCoRoutine_t
支持共享栈
所以共享栈采用的方式就是每次发生协程切换的时候,把实际用到的栈内容stack_bp stack_sp通过save_stack_buffer来保存到malloc的内存中去,然后调用coctx_swap进行寄存器信息的切换,再把切换进来的新协程之前以相同方式保存的栈数据再拷贝到上面的共享栈空间的对应的内存位置上去(栈指针在coctx_swap已经更新完了,这里只是填补数据的作用,而且每个协程切换前后一直使用相同的共享栈,即使有局部指针也没有问题),从而大大增加了内存的利用效率。
协程执行
协程 = 回调 + 栈内存
协程执行实际是协程的切换,包括协程上下文(寄存器状态)的切换,回调执行之后会再次切换回来
如果协程中创建了新的协程,则会有嵌套的协程切换
co_resume
同时创建的协程第一次启动也是使用这个接口,并且在第一次启动的时候会初始化特殊的coctx_t结构,在协程执行结束后,会自动设置cEnd=1,同时将自己yield出去
栈帧stack frame layout:
调用子函数时,父函数从右到左将函数入栈,最后将返回地址入栈保存后,跳到子函数的地址执行。子函数压栈保存父函数的 %ebp,并将 %ebp 设置为当前 %esp。子函数通过 %ebp + 4 读取参数1,%ebp + 8 读取参数2
libco程序的第一个协程呢,假如第一个协程yield时,CPU控制权让给谁呢?关于这个问题,我们首先要明白这“第一个”协程是什么。实际上,libco的第一个协程,即执行main函数的协程,是一个特殊的协程。这个协程又可以称作主协程,它负责协调其他协程的调度执行(后文我们会看到,还有网络
I/O以及定时事件的驱动),它自己则永远不会yield,不会主动让出
CPU。不让出CPU,不等于说它一直霸占着CPU。我们知道CPU执行权有两种转移途径,一是通过yield让给调用者,其二则是resume启动其他协程运行。
阻塞调用Hook
通过glibc中dlfcn.h的dlsym和RTLD_NEXT结合起来,从而给标准库函数添加钩子
#define HOOK_SYS_FUNC(name) if( !g_sys_##name##_func ) { g_sys_##name##_func = (name##_pfn_t)dlsym(RTLD_NEXT,#name); }
epoll 基于事件驱动的IO多路复用技术,1. mmap,2. 红黑树,33. rdlist
int epoll_create(**int size);**
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生
协程的事件管理
初始化 AllocEpoll
添加监听事件 co_poll
轮询 co_eventloop
感受:
总体代码质量并不是特别好,总感觉为了使用C++而使用C++,甚至有些命名都不是 特别规范,最好理解的协程库还是云风c语言版本的,强力推荐,libco最大的优势就是作为微信内部框架的底层库,在线上也承受了巨大的流量,稳定跑到几千台服务器上,其他的都不重要。
libco github :地址
云风 github: 地址