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
源码解析:
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;
}