libco协程库

本文详细解析了64位环境下协程上下文切换的具体实现原理,包括协程上下文结构定义、协程切换的汇编代码流程及初始化过程。通过深入分析协程的寄存器保存与恢复机制,揭示了如何通过修改栈指针实现不同协程间的切换。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


//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;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值