//64 bit
extern "C"
{
extern void coctx_swap( coctx_t *,coctx_t* ) asm("coctx_swap");
};
struct coctx_t
{
#if defined(__i386__)
void *regs[ 8 ];
#else
void *regs[ 14 ];
#endif
size_t ss_size;
char *ss_sp;
};
coctx_swap的汇编源码:
#elif defined(__x86_64__)
leaq 8(%rsp),%rax
leaq 112(%rdi),%rsp
pushq %rax //把 %rax 的值放入到 regs[13] 中,
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
movq %rsi, %rsp
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
xorl %eax, %eax
ret
#endif
leaq 用于把其第一个参数的值赋值给第二个寄存器参数。
%rsp寄存器是栈顶寄存器,通过移动它来指定栈帧地址。
%rax是返回地址寄存器,如在函数调用时保存函数返回后继续要执行的地址
%rdi,%rsi则分别存储函数调用时的参数1和参数2
112(%rdi) 表示的就是第一个协程的 coctx_t 中 regs[14] 数组的下一个64位地址。
其中的pushq即通过调整%rsp 把 regs[14] 当作堆栈,然后利用 pushq 把寄存器的值和返回地址存储到 regs[14] 整个数组中。
协程上下文的regs数组:
//low | regs[0]: r15 |
// | regs[1]: r14 |
// | regs[2]: r13 |
// | regs[3]: r12 |
// | regs[4]: r9 |
// | regs[5]: r8 |
// | regs[6]: rbp |
// | regs[7]: rdi |
// | regs[8]: rsi |
// | regs[9]: ret | //函数的返回地址
// | regs[10]: rdx |
// | regs[11]: rcx |
// | regs[12]: rbx |
//hig | regs[13]: rsp | //该值为上个栈帧在调用该函数前的值
之后的pop 语句就是用 regs[0-13] 中的值填充cpu 的寄存器。
这里需要注意的是popq 会使得 %rsp 的值增加而不是减少,这一点保证了会从 regs[0] 到regs[13] 依次弹出到 cpu 寄存器中。在执行完最后一句 popq %rsp 后,%rsp 已经指向了新协程要恢复的栈指针(即新协程之前调用 coctx_swap 时父函数的栈帧顶指针),由于每个协程都有一个自己的栈空间,可以认为这一语句使得%rsp 指向了要进入协程的栈空间。
pushq %rax
xorl %eax, %eax
ret
pushq %rax 用来把 %rax 的值压入到新协程的栈中,这时 %rax 是要进入的目标协程的返回地址,即要恢复的执行点。
然后用 xorl 把 %rax 低32位清0以实现地址对齐。
最后ret 语句用来弹出栈的内容,并跳转到弹出的内容表示的地址处,而弹出的内容正好是上面 pushq %rax 时压入的 %rax 的值,即之前保存的此协程的返回地址。
即最后这三条语句实现了转移到新协程返回地址处执行,从而完成了两个协程的切换。
初始化函数:
enum
{
kRDI = 7,
kRSI = 8,
kRETAddr = 9,
kRSP = 13,
};
extern "C"
{
extern void coctx_swap( coctx_t *,coctx_t* ) asm("coctx_swap");
};
int coctx_make( coctx_t *ctx,coctx_pfn_t pfn,const void *s,const void *s1 )
{
char *sp = ctx->ss_sp + ctx->ss_size;
sp = (char*) ((unsigned long)sp & -16LL );
memset(ctx->regs, 0, sizeof(ctx->regs));
ctx->regs[ kRSP ] = sp - 8;//将rsp寄存器替换为了该协程私有的栈空间地址,这样就保证了每个协程具备独立的栈空间。
ctx->regs[ kRETAddr] = (char*)pfn;//返回地址(即下一条执行指令)替换为了用户创建协程时传入的开始函数地址。
ctx->regs[ kRDI ] = (char*)s; //rdi寄存器保存从左到右的第一个参数
ctx->regs[ kRSI ] = (char*)s1;//rsi寄存器保存从左到右的第二个参数
return 0;
}