srs(state thread)如何实现协程切换
srs(state thread)如何实现协程切换? srs是单线程上运行的协程模型, 一个线程交替执行多个协程, 那么协程在用户空间是如何切换的呢?
首先关于thread.stack等内容可以自行阅读st thread代码, 这里只聊协程上下文切换过程.
417行的宏执行协程A上下文的保存
419行 _st_vp_schedule 在RUNQ中找到一个待执行协程B, 恢复协程B的上下文, 切换到该协程B执行.
协程B执行到io阻塞或者sleep事件, 就会重新把协程B缓存起来, 并寻找一个待执行协程(假设这里就AB两个协程),恢复协程A的上下文继续执行. 完成协程切换.
协程上下文保存
#define MD_SETJMP(env) _st_md_cxt_save(env) /****************************************************************/ /* * Internal __jmp_buf layout */ #define JB_RBX 0 #define JB_RBP 1 #define JB_R12 2 #define JB_R13 3 #define JB_R14 4 #define JB_R15 5 #define JB_RSP 6 #define JB_PC 7 .file "md.S" .text /* _st_md_cxt_save(__jmp_buf env) */ .globl _st_md_cxt_save .type _st_md_cxt_save, @function .align 16 _st_md_cxt_save: /* * Save registers. * 寄存器数据的转存 */ movq %rbx, (JB_RBX*8)(%rdi) movq %rbp, (JB_RBP*8)(%rdi) movq %r12, (JB_R12*8)(%rdi) movq %r13, (JB_R13*8)(%rdi) movq %r14, (JB_R14*8)(%rdi) movq %r15, (JB_R15*8)(%rdi) /* Save SP */ // 关于leaq和movq的区别: // refs: https://courses.cs.washington.edu/courses/cse374/16wi/lectures/leaq-movq.pdf // rsp寄存器存放的内容是栈顶的地址, leaq指令是把rsp+8的地址信息存放到(JB_RSP*8)(%rdi)中. // 相当于是把栈pop之后的栈顶地址存放到了(JB_RSP*8)(%rdi). leaq 8(%rsp), %rdx movq %rdx, (JB_RSP*8)(%rdi) /* Save PC we are returning to */ // rsp寄存器指向的栈顶地址上存放的是_st_md_cxt_save函数的返回地址, // 把rsp放在(JB_PC*8)(%rdi)中, 等恢复协程的时候就可以jump到(JB_PC*8)(%rdi),相当于恢复的时候直接返回了. // 注意现在只是把协程上下文保存了, 还没有切换呢.切回该协程的时候, 会jump到PC(下面_st_md_cxt_restore函数写明) movq (%rsp), %rax movq %rax, (JB_PC*8)(%rdi) // 返回值为rax的异或, 恒为0; // _st_md_cxt_save返回值什么时候为1呢? // 在切回该协程的时候,jump到PC,PC就是_st_md_cxt_save返回的地方(上图417行), _st_md_cxt_restore根据输入的val设置了eax, eax作为函数返回值. xorq %rax, %rax ret .size _st_md_cxt_save, .-_st_md_cxt_save /****************************************************************/
协程上下文恢复
#define MD_LONGJMP(env, val) _st_md_cxt_restore(env, val) /****************************************************************/ /* _st_md_cxt_restore(__jmp_buf env, int val) */ .globl _st_md_cxt_restore .type _st_md_cxt_restore, @function .align 16 _st_md_cxt_restore: /* * Restore registers. */ movq (JB_RBX*8)(%rdi), %rbx movq (JB_RBP*8)(%rdi), %rbp movq (JB_R12*8)(%rdi), %r12 movq (JB_R13*8)(%rdi), %r13 movq (JB_R14*8)(%rdi), %r14 movq (JB_R15*8)(%rdi), %r15 /* Set return value */ // 把val参数作为返回值. jump之后作为了_st_md_cxt_save的返回值. test %esi, %esi mov $01, %eax cmove %eax, %esi mov %esi, %eax // 把栈顶寄存器rsp恢复为(之前_st_md_cxt_save中rsp栈pop之后的栈顶地址) // 把PC指针恢复为_st_md_cxt_save的调用者, 并跳转到调用_st_md_cxt_save的地方 movq (JB_PC*8)(%rdi), %rdx movq (JB_RSP*8)(%rdi), %rsp /* Jump to saved PC */ jmpq *%rdx .size _st_md_cxt_restore, .-_st_md_cxt_restore /****************************************************************/