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流程