libco协程框架
协程简介
协程简称用户态线程,是在线程下自己实现的切换的用户态程序切换。实现方式大致分为以下方式:
- 使用glibc ucontext
- 使用汇编实现
- c 语言 switch-case
- c setjmp longjmp
libco协程切换使用汇编实现, 支持独立栈(128k大小)和共享栈(写时拷贝)模式。支持hook socket族函数来完成网络自动协程的框架包装。
Libco协程切换
Libco使用void co_swap(stCoRoutine_t* curr, stCoRoutine_t* pending_co)协程切换内部调用汇编实现函数coctx_swap做切换,以__x86_x64__的汇编coctx_swap简单注释如下:
用到的一些基础知识:
x86-64 下函数调用及栈帧原理” 中指出,调用子函数时,父函数会把调用参数放入了寄存器中,并且把返回地址压入了栈中。即在进入 coctx_swap 时,第一个参数值已经放到了 %rdi 寄存器中,第二个参数值已经放到了 %rsi 寄存器中,并且栈指针 %rsp 指向的位置即栈顶中存储的是父函数的返回地址。
ret指令用栈中的数据,修改IP的内容,从而实现近转移。
/*
父函数栈帧中除返回地址外栈帧顶的位置放到rax
*/
leaq 8(%rsp),%rax
leaq 112(%rdi),%rsp //当前协程地址放到rsp 下面保存14个寄存器
pushq %rax
pushq %rbx
pushq %rcx
pushq %rdx
pushq -8(%rax) //ret func addr 第一句放入的返回地址
pushq %rsi
pushq %rdi
pushq %rbp
pushq %r8
pushq %r9
pushq %r12
pushq %r13
pushq %r14
pushq %r15
//至此当前协程的寄存器放入了当前的数据coctx_t结构体中
movq %rsi, %rsp //第2个参数即新协程coctx_t作为堆栈指针 然后出栈恢复寄存器
popq %r15
popq %r14
popq %r13
popq %r12
popq %r9
popq %r8
popq %rbp
popq %rdi
popq %rsi
popq %rax //ret func addr
popq %rdx
popq %rcx
popq %rbx
popq %rsp
pushq %rax //返回地址压入rsp ret指令使用 rsp -> ip寄存器
xorl %eax, %eax
ret //回到新协程
libco基础框架
- 创建协程co_create
检测协程环境文是否初始化(每个线程一个协程环境,没有则初始化)->创建协程结构。
- 运行co_resume
判断当前协程是否是已运行的协程没有则构建上下文
获取正在执行的协程
切换到参数的协程
- 挂起co_yield
将此协程移除执行列表,切换到上一个协程
可以使用co_resume再次运行并放入协程运行环境
- 销毁co_release
libco hook模式下的阻塞函数自动协程切换
注意所有的这类函数需要在协程里面调用
- 检测到系统调用socket hook函数会把当前fd放到一个列表里面。
- 调用其他相关函数会查找这个列表并完成相关操作。
- 以read为例说明hook流程