Libco源码学习

  • 初始化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中。


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值