协程的实现思路与原理
为什么要有协程
- 以同步的方式实现异步的效率,同步的效率低
- 综合同步和异步的优势
异步方式
发数据
- socket
- connect 服务器
- 设置协议
- send
- fd ADD epoll
接数据
- epoll_wait()
- read fd
- 解析对应数据
上面两个流程是异步的。
那么如何使用同步的方式实现异步的效率? 我们引入yield 和 resume原语。yield 为让出,resume为恢复
那么流程就变成如下:
发数据
- socket
- connect 服务器
- 设置协议
- send
- fd ADD epoll
- yield
接数据
- epoll_wait()
- read fd
- 解析对应数据
- resume
在一个线程里做。
yield 和 resume实现(协程的切换)
实现方式
-
longjmp/setjmp
-
ucontext
-
用汇编实现
很多开源代码都用的汇编实现,我们着重看看汇编实现。可读性看似是汇编,实际上可读性比longjmp/setjmp好多了。
汇编实现
yield = switch(a, b);
resume = switch(b, a);
CPU上下文
typedef struct _nty_cpu_ctx {
void *esp; //
void *ebp;
void *eip;
void *edi;
void *esi;
void *ebx;
void *r1;
void *r2;
void *r3;
void *r4;
void *r5;
} nty_cpu_ctx;
切换就是指先保存当前协程的寄存器值然后加载另一个协程的寄存器值
#ifdef __i386__
__asm__ (
" .text \n"
" .p2align 2,,3 \n"
".globl _switch \n"
"_switch: \n"
"__switch: \n"
"movl 8(%esp), %edx # fs->%edx \n"
"movl %esp, 0(%edx) # save esp \n"
"movl %ebp, 4(%edx) # save ebp \n"
"movl (%esp), %eax # save eip \n"
"movl %eax, 8(%edx) \n"
"movl %ebx, 12(%edx) # save ebx,esi,edi \n"
"movl %esi, 16(%edx) \n"
"movl %edi, 20(%edx) \n"
"movl 4(%esp), %edx # ts->%edx \n"
"movl 20(%edx), %edi # restore ebx,esi,edi \n"
"movl 16(%edx), %esi \n"
"movl 12(%edx), %ebx \n"
"movl 0(%edx), %esp # restore esp \n"
"movl 4(%edx), %ebp # restore ebp \n"
"movl 8(%edx), %eax # restore eip \n"
"movl %eax, (%esp) \n"
"ret \n"
);
#elif defined(__x86_64__)
__asm__ (
" .text \n"
" .p2align 4,,15 \n"
".globl _switch \n"
".globl __switch \n"
"_switch: \n"
"__switch: \n"
" movq %rsp, 0(%rsi) # save stack_pointer \n"
" movq %rbp, 8(%rsi) # save frame_pointer \n"
" movq (%rsp), %rax # save insn_pointer \n"
" movq %rax, 16(%rsi) \n"
" movq %rbx, 24(%rsi) # save rbx,r12-r15 \n"
" movq %r12, 32(%rsi) \n"
" movq %r13, 40(%rsi) \n"
" movq %r14, 48(%rsi) \n"
" movq %r15, 56(%rsi) \n"
" movq 56(%rdi), %r15 \n"
" movq 48(%rdi), %r14 \n"
" movq 40(%rdi), %r13 # restore rbx,r12-r15 \n"
" movq 32(%rdi), %r12 \n"
" movq 24(%rdi), %rbx \n"
" movq 8(%rdi), %rbp # restore frame_pointer \n"
" movq 0(%rdi), %rsp # restore stack_pointer \n"
" movq 16(%rdi), %rax # restore insn_pointer \n"
" movq %rax, (%rsp) \n"
" ret \n"
);
#endif
协程启动
- 协程创建
- 协程加入就绪队列
- 入口函数
- eip–>func
- esp–>void *stack;
协程定义
-
context 上下文
-
stack 栈
-
size 栈大小
-
func 入口函数
-
arg 参数
-
wait (集合元素)
-
sleep (集合元素)
-
ready (集合元素)
-
exit 退出
-
status 协程状态
调度器
过程:
- sockfd添加到epoll管理
- 进行上下文切换,由协程上下文切换到调度器上下文
- 调度器获取下一个协程的上下文。resume新的协程
接口定义
- coroutine_create 协程创建
- scheduler_loop 调度器运行
- IO操作都需要封装
以后有机会会自己实现一个自己的协程框架
参考:libco libgo