Missmiaom
10/8/2019 - 1:32 PM

上下文切换

lea是取址指令,b,w,l,q是操作属性限定符,分别表示1字节,2字节,4字节,8字节。

LEA 取有效地址指令 (Load Effective Address)
  指令格式:LEA 目的,源
  指令功能:取源操作数地址的偏移量,并把它传送到目的操作数所在的单元。

在x86_64架构下,函数调用时,参数传递将从左到右分别存入rdi,rsi,rdx,rcx,r8,r9,当这6个不够用的时候才会借用栈。

.globl coctx_swap
#if !defined( __APPLE__ ) && !defined( __FreeBSD__ )
.type  coctx_swap, @function
#endif
coctx_swap:
    //push %rbp  //将上个栈帧的基址压入栈中
    //movq %rsp,%rbp  //将rbp赋值为当前的栈顶的值,即开启了新的栈帧
    //保存当前工作线程的上下文
    leaq 8(%rsp),%rax
    leaq 112(%rdi),%rsp
    pushq %rax
    pushq %rbx
    pushq %rcx
    pushq %rdx
    pushq -8(%rax) //函数返回地址,即下一条指令的执行地址
    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 //函数返回地址,即下一条指令的执行地址
    popq %rdx
    popq %rcx
    popq %rbx
    popq %rsp
    pushq %rax
    xorl %eax, %eax
    //leaveq  该指令将rbp赋值给rsp,再弹出栈顶的上个栈帧的基址,并将其赋值给rbp寄存器,从而恢复上个栈帧调用该函数前的结构。相当于movq %ebp, %esp和popq %ebp两条指令
    ret //相当于popq %rip

源码解析:

https://zxylvlp.github.io/blog/coctx_swap.html

struct coctx_t
{
#if defined(__i386__)
	void *regs[ 8 ];
#else
	void *regs[ 14 ];
#endif
	size_t ss_size;
	char *ss_sp;
};

// regs数组中对应寄存器值的下标
enum
{
    kRDI = 7,
    kRSI = 8,
    kRETAddr = 9,
    kRSP = 13,
};

// 初始化ctx对象
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));

    // 将rsp寄存器替换为了该协程私有的栈空间地址,这样就保证了每个协程具备独立的栈空间
    // 为什么替换了rsp寄存器就保证了该协程将使用自己的栈空间地址呢?
    // 因为栈空间的分配和回收,是通过rsp寄存器来控制的。
    // 比如要分配4个字节时,可执行sub $0x4,%rsp,回收4个字节时,可执行add $0x4,%rsp,因此当替换了rsp寄存器的值时,即替换了栈空间
    ctx->regs[ kRSP ] = sp - 8;

    // 返回地址(即下一条执行指令)替换为了用户创建协程时传入的开始函数地址
    ctx->regs[ kRETAddr] = (char*)pfn;
    
    // 当然一个函数的执行少不了传参,因此接下来的两行代码,就把参数赋值给了regs数组中对应与rdi寄存器和rsi寄存器的位置
    ctx->regs[ kRDI ] = (char*)s;
    ctx->regs[ kRSI ] = (char*)s1;
    return 0;
}