Content #
先通过一个最简单的协程的例子,来观察协程的运作机制:
#include <stdio.h>
#include <stdlib.h>
#define STACK_SIZE 1024
typedef void(*coro_start)();
class coroutine {
public:
long* stack_pointer;
char* stack;
coroutine(coro_start entry) {
if (entry == NULL) {
stack = NULL;
stack_pointer = NULL;
return;
}
stack = (char*)malloc(STACK_SIZE);
char* base = stack + STACK_SIZE;
stack_pointer = (long*) base;
stack_pointer -= 1;
*stack_pointer = (long) entry;
stack_pointer -= 1;
*stack_pointer = (long) base;
}
~coroutine() {
if (!stack)
return;
free(stack);
stack = NULL;
}
};
coroutine* co_a, * co_b;
void yield_to(coroutine* old_co, coroutine* co) {
__asm__ (
"movq %%rsp, %0\n\t"
"movq %%rax, %%rsp\n\t"
:"=m"(old_co->stack_pointer):"a"(co->stack_pointer):);
}
void start_b() {
printf("B");
yield_to(co_b, co_a);
printf("D");
yield_to(co_b, co_a);
}
int main() {
printf("A");
co_b = new coroutine(start_b);
co_a = new coroutine(NULL);
yield_to(co_a, co_b);
printf("C");
yield_to(co_a, co_b);
printf("E\n");
delete co_a;
delete co_b;
return 0;
}
编译运行:
# g++ -g -o co -O0 coroutine.cpp
# ./co
ABCDE
yield_to函数的机器码如下:
000000000040076d <_Z8yield_toP9coroutineS0_>:
40076d: 55 push %rbp
40076e: 48 89 e5 mov %rsp,%rbp
400771: 48 89 7d f8 mov %rdi,-0x8(%rbp)
400775: 48 89 75 f0 mov %rsi,-0x10(%rbp)
400779: 48 8b 45 f0 mov -0x10(%rbp),%rax
40077d: 48 8b 00 mov (%rax),%rax
400780: 48 8b 55 f8 mov -0x8(%rbp),%rdx
400784: 48 89 22 mov %rsp,(%rdx)
400787: 48 89 c4 mov %rax,%rsp
40078a: 5d pop %rbp
40078b: c3 retq
yield_to 中,参数 old_co 指向老协程,co 则指向新的协程,也就是我们要切换过去执行的目标协程。
这段代码的作用是,首先,把当前 rsp 寄存器的值存储到 old_co 的 stack_pointer 属性,并且把新的协程的 stack_pointer 属性更新到 rsp 寄存器,然后,retq 指令将会从栈上取出调用者的地址,并跳转回调用者继续执行。