首页 > 编程语言 >libco 源码剖析(1): 协程上下文切换之 32 位

libco 源码剖析(1): 协程上下文切换之 32 位

时间:2022-12-11 22:00:33浏览次数:81  
标签:协程 coctx 32 regs eax 源码 寄存器 上下文


libco 源码剖析(1): 协程上下文切换之 32 位

相关背景资料

  • 关于汇编语言及内存布局相关基础,参看 ​​参考文献[0]​​, ​​参考文献[1]​
  • 32 位协程上下文结构如下:
// coctx.h
struct coctx_t
{
void *regs[ 8 ];
size_t ss_size;
char *ss_sp;
};
  • 32 位协程上下文中的寄存器信息注释如下:
// coctx.cpp
// low | regs[0]: ret |
// | regs[1]: ebx |
// | regs[2]: ecx |
// | regs[3]: edx |
// | regs[4]: edi |
// | regs[5]: esi |
// | regs[6]: ebp |
// high | regs[7]: eax | = esp
  • 协程上下文切换函数声明如下:
extern "C"
{
extern void coctx_swap( coctx_t *,coctx_t* ) asm("coctx_swap");
};

关于 C/C++ 调用汇编函数参看 ​​参考文献[5]​​​, ​​参考文献[6]​

源码解析

  1. 根据协程上下文结构及上下文切换函数的定义,可以画出进入上下文切换汇编时的内存布局:

To pass parameters to the subroutine, push them onto the stack before the call. The parameters should be pushed in inverted order. —— ​​参考文献[7]​

  1. 如上图,进入 ​​coctx_swap​​ 函数后, ESP 寄存器指向 返回地址(return address) 。 第一句汇编指令将 ​​coctx_swap​​ 函数的第一个参数的地址存入 EAX 寄存器中:
leal 4(%esp), %eax //sp

然后将 ​​coctx_swap​​ 函数的第一个参数的地址(即 返回地址(return address) 的地址 + ​​sizeof(void*)​​)存入 ESP 寄存器。

movl 4(%esp), %esp

最后将 ESP 寄存器的值增加 32(​​8*sizeof(void*) = 32​​​。即,将栈顶设置为 ​​&regs[7] + sizeof(void*)​​​。后续向栈顶压入上下文时,即是在将数据存入 ​​coctx_t::regs​​ 中)。

leal 32(%esp), %esp //parm a : ®s[7] + sizeof(void*)

上述一系列操作后内存布局如下:

libco 源码剖析(1): 协程上下文切换之 32 位_参考文献

  1. 接下来就是按照约定,依次将 EAX, EBP, ESI, EDI, EDX, ECX, EBX 保存的数据以及**返回地址(​​%eax​​-4)**压入栈内。
pushl %eax //esp ->parm a 

pushl %ebp
pushl %esi
pushl %edi
pushl %edx
pushl %ecx
pushl %ebx
pushl -4(%eax)

由于当前栈顶指针 ESP 保存的是 ​​&regs[7] + sizeof(void*)​​​,因此将寄存器信息压入栈的过程实际上就是将数据保存在 ​​coctx_swap​​​ 函数的第一个参数指向的 ​​coctx_t​​​ 结构的 ​​reg​​​ 数组中。

移入寄存器后的内存布局如下:

libco 源码剖析(1): 协程上下文切换之 32 位_参考文献_02

  1. 接下来将第二个参数的值(即 切换的新上下文信息的结构的地址 )存入栈顶寄存器 ESP, 作为栈顶指针。
movl 4(%eax), %esp //parm b -> ®s[0]

操作后的内存布局如下:

libco 源码剖析(1): 协程上下文切换之 32 位_libco 源码_03

  1. 返回地址(return address) 的值弹出到 EAX 寄存器中:
popl %eax  //ret func addr

然后,依次弹出接下来的几个寄存器的值:

popl %ebx  
popl %ecx
popl %edx
popl %edi
popl %esi
popl %ebp

操作后的内存布局如下:

libco 源码剖析(1): 协程上下文切换之 32 位_libco 源码_04

  1. 接下来是恢复之前的栈数据。根据前面的分析,我们可以知道当前栈顶 ​​reg[7]​​ 保存的是上下文切换前的第一个参数的地址,即 实际栈顶地址+4
    而现在的 EAX 保存的是上下文切换前的 返回地址(return address) 。因此要恢复上下文切换之前的状态,只需要将 ​​reg[7]​​ 弹出到 ESP 寄存器,然后将 EAX 寄存器的值压入栈。
popl %esp
pushl %eax //set ret func addr

最后将 EAX 寄存器清空:

xorl %eax, %eax

其他

64位汇编与32位类似,就不赘述。主要差别在于 64 位通过寄存器传递参数。

leaq 112(%rdi),%rsp
... ...
movq %rsi, %rsp

To pass parameters to the subroutine, we put up to six of them into registers (in order: rdi, rsi,
rdx, rcx, r8, r9). If there are more than six parameters to the subroutine, then push the rest onto
the stack in reverse order —— ​参考文献 [8]

参考文献

[ 0 ] ​​内存布局与栈​​ [ 1 ] ​Lecture 4: x86_64 Assembly Language​​ [ 2 ] ​coctx_swap.S​​ [ 3 ] ​coctx.h​​ [ 4 ] ​coctx.cpp​​ [ 5 ] ​Calling Functions and Passing Parameters in Assembly​​ [ 6 ] ​Mixing Assembly and C​​ [ 7 ] ​The 32 bit x86 C Calling Convention​ [ 8 ] ​The 64 bit x86 C Calling Convention



标签:协程,coctx,32,regs,eax,源码,寄存器,上下文
From: https://blog.51cto.com/u_15903085/5928763

相关文章