-
初始化co-routine-env(在任意一个线程中调用co-create都会引发该函数,如果没有初始化则初始化)
-
针对当前线程实例化一个env出来
-
创建初始co-routine(主线程中co-routine),分配栈空间128K(该栈空间没有用到),设置回调函数为NULL,is_main=true
-
将初始co-routine加入到env中Call-stack数组中,形成调用栈
-
设置env中的epoll实例
-
-
co_create:创建工作co-routine
-
设置该co-routine的env是当前线程的env,分配栈空间128K,设置用户赋予的回调函数
-
返回该co-routine
-
-
co_resume:将cpu的执行权转交到工作co-routine
-
如果该co-routine未初始化,则初始化
-
128k栈空间从高地址到低地址插入该co-routine的函数调用栈(每个线程都有自己的私有栈空间,这里协程可以看做跟线程等同,拥有自己的私有栈)
-
分配该co-routine的上下文ctx的14个reg数组,用来临时存储CPU寄存器的值。
-
在栈空间第一位置插入指向co-routine的指针(这个是CoRoutineFunc的参数),设置协程ctx中reg数组的EIP和ESP,ESP指向栈顶,EIP指向函数CoRoutineFunc,该函数内会调用用户设置的回调函数,这个是当前co-routine的返回地址。
-
-
将该co-routine加入到call-stack数组中,
-
交换和当前co-routine的上下文(cll-stack的栈顶2个元素进行交换上下文),执行coctx_swap函数,开始执行新创建的co-routine。
-
将当前的寄存器值存储到当前co-routine的ctx的reg数组中,esp值为去除返回地址的栈顶地址,同样存返回地址到reg数组中,当前的返回地址为调用coctx_swap函数的下一条语句。
-
将新co-routine的ctx中reg数组中的值加载到cpu寄存器中,现在已经运行在新co-routine的栈空间内了。
-
push新co-routine的返回地址到栈空间,coctx_swap函数退出,转到返回地址处开始执行新的co-routine的代码。
-
开始执行CoRoutineFunc函数。
-
-
-
co_yield:让出cpu的执行权,转交给上一个co_routine
-
整体逻辑和co_resume一致,交换call_stack里面的最后两个co_routine的上下文ctx,当前co_routine退出call_stack。
-
-
socket网络调用函数
-
设置当前协程syshook=1,表明后续创建的socket都要被拦截。
-
创建socket函数:
-
如果syshook=0,则直接调用系统的socket函数,返回。否则
-
首先调用系统的socket函数。进程有一个全局co_socket_fd数组,长度为102400。每个co_socket_fd的结构内容包括:socket读写超时。将系统生成的fd位置的co_cocket_fd元素设置读写超时为1s。
-
设置socket为非阻塞
-
-
connect函数
-
如果syshook=0,则直接调用系统的connect函数返回。否则
-
首先调用系统connect函数进行远程socket连接。因为syshook=1时cocket为非阻塞,调用完即退出函数,此时并不代表已经创建好连接,需要在下面检查。
-
调用hook的poll函数将fd插入到epoll中,并使用co_yied函数让出cpu执行权,等待下次获得执行权时执行后续的逻辑
-
超时时间设置为25s,如果25s到期该socket可写则说明连接创建成功。该过程重试3次,也就是说可能最迟需要75秒才能创建连接。
-
如果poll可读,则代表创建好连接。否则报连接超时错误。
-
-
write函数
-
如果syshook=1, 则直接调用系统的write函数返回。否则
-
首先调用系统的write函数,非阻塞,一次可能没有写完。这时候需要将fd插入到epoll中,等待下次可写的时候再写剩下的。
-
调用co_yield函数让出当前co-routine的执行权。同时设置超时时间为1s
-
等待当前协程获取下次执行权时候,此时fd可写,再调用write函数将剩下数据写完
-
-
read函数
-
如果syshook=1, 则直接调用系统的read函数返回。否则
-
将fd插入到epoll中,并调用co_yield函数让出当前co-routine的执行权。等待下次获取执行权。
-
获得执行权后,说明目前fd可读,此时调用系统read函数读出来数据。
-
-
-
主协程co-routine逻辑
-
主线程在创建完工作协程后,进入co_eventloop逻辑,监听事件。
-
每毫秒进行一次epoll_wait,将所有触发到的fd找到其所在的co-routine调用co-resume切换cpu执行权。
-
-
超时处理
-
使用时间轮方法解决超时问题,在往epoll里面插入fd时候,同时往时间轮里插入事件到合适的位置。时间轮方法可以做到O(1)的时间复杂度读写。
-
每次主协程调用co_eventloop循环时,每毫秒循环一次。首先从时间轮中取出来过期的事件,并标识该fd已经过期,然后一起插入到active表。然后对active的每个fd调用co-resume切换,后续处理过期逻辑。
-
-
co_cond协程条件变量使用
-
co_cond_wait
-
将cond item加入到item链表,同时根据它的超时时间加入到超时时间轮中。调用co_yield让出cpu执行权。
-
如果在中间发现该item超时,则从链表中剔除出去。
-
-
co_cond_signal
-
从item链表中拿出来第一个item,然后将它加入到epoll的active链表里,等待下次主线程执行时进行切换。
-
-
-
协程服务端处理
-
首先创建多个工作协程,并把这些协程加入到stack中。
-
再创建一个accept协程,接受过来的accept网络请求。一旦过来请求,则从stack弹出来一个co,并把过来的fd设置为co的fd。切换到工作线程执行。如果stack为空,则说明所有的工作协程都被占用,这时候出错。
-
工作线程调用read函数,将fd写入到epoll中,并释放执行权。
-
等待fd可读时,重新获取执行权,从中读取数据并处理。处理完成后,fd设置为-1,重新加入到stack中。
-