开源协程库libco汇编原理分析

本文深入探讨了x86_64架构下协程的上下文切换,详细解析了栈帧的布局、ESP和EBP寄存器的作用,以及如何通过汇编代码在栈中保存和恢复寄存器状态。同时介绍了coctx_swap函数的实现,该函数用于在不同的栈帧之间切换,以实现协程的切换。文章还展示了初始化和创建协程上下文的函数,如coctx_init和coctx_make。
摘要由CSDN通过智能技术生成

本文件进行协程切换,把协程运行时的上下文信息在寄存器和栈之间传输。栈空间是向下增长的。AT&T格式的汇编和intel格式的汇编不同,王爽书中写的是intel格式的汇编,gcc反汇编是AT&T格式的。在C语言和现代CPU的设计规范中,栈帧是一块由栈分配的内存块,在运行时,每当调用一次函数,都要存储其自动变量。因此对于同一函数的递归调用在每一次都会连续的获得自己独立的栈帧。函数的栈帧是指esp和ebp之间的一块地址。因此如果一个函数把更多的值压入堆栈,实际上是在扩展它本身的栈帧。

(1)ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。

(2)EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。

栈帧就是esp和ebp之间的一块内存,看一下一个栈帧的实际布局:

调用一个函数时,先将堆栈原先的基址(EBP)入栈,以保存之前任务的信息。然后将栈顶指针ESP的值赋给EBP,将之前的栈顶作为新的基址(栈底),然后再这个基址上开辟相应的空间用作被调用函数的堆栈。函数返回后,从EBP中可取出之前的ESP值,使栈顶恢复函数调用前的位置;再从恢复后的栈顶可弹出之前的EBP值,因为这个值在函数调用前一步被压入堆栈。这样,EBP和ESP就都恢复了调用前的位置,堆栈恢复函数调用前的状态。

coctx_swap.S

%rdi,%rsi分别是调用此汇编代码时的参数1和参数2,在coctx_swap(&(curr->ctx),&(pending_co->ctx) )执行时,参数curr->ctx地址入rdi,pending_co->ctx地址入rsi。rsp是栈顶寄存器。x86_64中函数传参优先使用rdi,rsi,rdx,rcx,r8,r9,当这6个不够用的时候才会借用栈。

.globl coctx_swap    // 让符号对链接器可见
#if !defined( __APPLE__ ) && !defined( __FreeBSD__ )
.type  coctx_swap, @function    // 告诉GNU汇编器,coctx_swap是个函数
#endif
coctx_swap:    // 函数入口
 
#if defined(__i386__)
	//...............
#elif defined(__x86_64__)
	leaq 8(%rsp),%rax
	leaq 112(%rdi),%rsp
	pushq %rax
	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

以上代码可分为两部分:切入(恢复上下文信息,即从栈pop到寄存器)切出(保存寄存器的上下文信息,push进栈)。

前半部分(切出过程):

	leaq 8(%rsp),%rax	//将rsp的上一个地址保存到%rax上,就是把当前的%rsp地址保存到%rax上,
    //后面会移动%rsp把寄存器信息保存到regs数组中,%rsp是栈顶寄存器,通过移动它可以指定栈帧地址。
	leaq 112(%rdi),%rsp    //%rsp把curr->ctx加112个字节偏移量处的地址给rsp,
                            //112是sizeof(void*)(=8)*14,即regs[14]
	pushq %rax    //后面的pushq就是把寄存器中的内容保存到regs数组中
	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

后半部分(切入过程)

rsi为第二个参数,rsp指向新的栈空间即regs[0],以上实现把regs中的原有内容popq到各寄存器中。

	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    //清0rax寄存器的低32位
	ret    //将栈顶的返回地址弹出给rip寄存器

reg数组对应寄存器的值

enum
{
	kRDI = 7,
	kRSI = 8,
	kRETAddr = 9,
	kRSP = 13,
};
//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 | //该值为上个栈帧在调用该函数前的值

协程上下文(协程控制字):

//coctx.h
struct coctx_t
{
#if defined(__i386__)
    void *regs[ 8 ];
#else
    void *regs[ 14 ];
#endif
    size_t ss_size;//协程剩余大小。
    char *ss_sp; //协程栈底,每个协程都有独立的栈空间 ,sp+size=栈顶指针
};

以上结构体(64位部分)主要包含了14个寄存器,每个寄存器是64位(8字节),所以寄存器组最后一个位置偏移112=13*8字节。

初始化函数:

#elif defined(__x86_64__)
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;

	ctx->regs[ kRETAddr] = (char*)pfn;

	ctx->regs[ kRDI ] = (char*)s;
	ctx->regs[ kRSI ] = (char*)s1;
	return 0;
}

int coctx_init( coctx_t *ctx )
{
	memset( ctx,0,sizeof(*ctx));
	return 0;
}

coctx_init函数初始化ctx,全部赋值0。coctx_make初始化上下文,即相关寄存器。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值