libco 源码剖析(1): 协程上下文切换之 32 位
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");
};
- 协程上下文切换汇编源码:参考文献[2]
源码解析
根据协程上下文结构及上下文切换函数的定义,可以画出进入上下文切换汇编时的内存布局:
To pass parameters to the subroutine, push them onto the stack before the call. The parameters should be pushed in inverted order. —— 参考文献[7]
如上图,进入
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
。即,将栈顶设置为®s[7] + sizeof(void*)
。后续向栈顶压入上下文时,即是在将数据存入coctx_t::regs
中)。leal 32(%esp), %esp //parm a : ®s[7] + sizeof(void*)
上述一系列操作后内存布局如下:
接下来就是按照约定,依次将 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 保存的是
®s[7] + sizeof(void*)
,因此将寄存器信息压入栈的过程实际上就是将数据保存在coctx_swap
函数的第一个参数指向的coctx_t
结构的reg
数组中。
移入寄存器后的内存布局如下:
接下来将第二个参数的值(即 切换的新上下文信息的结构的地址 )存入栈顶寄存器 ESP, 作为栈顶指针。
movl 4(%eax), %esp //parm b -> ®s[0]
操作后的内存布局如下:
将 返回地址(return address) 的值弹出到 EAX 寄存器中:
popl %eax //ret func addr
然后,依次弹出接下来的几个寄存器的值:
popl %ebx
popl %ecx
popl %edx
popl %edi
popl %esi
popl %ebp
操作后的内存布局如下:
接下来是恢复之前的栈数据。根据前面的分析,我们可以知道当前栈顶
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
最新文章
- 嵌入式Linux驱动学习之路(十三)按键驱动-异步通知
- Static List
- 稍览了一下CommonJS
- day5----正则
- Python入门笔记(11):集合
- MogileFS 的介绍(MogileFS 系列1)[分布式文件系统]
- Sphinx+MySQL5.1x+SphinxSE+mmseg
- UML学习
- 进击的Hybrid App,量身定做缓存机制
- RN学习1——前奏,app插件化和热更新的探索
- Android编程心得-JSON使用心得(二)
- [x-means] 1.x-means简介
- mac date命令详解
- Thymeleaf3语法详解
- calendar模块
- git小笔记
- mysql 数据表操作 目录
- VC++使用服务做守护进程的示例(转载)
- mybatis学习(一)-------XML 映射配置文件详解
- Writing Genres 英文文章文体