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
libco 源码剖析(1): 协程上下文切换之 32 位的更多相关文章
- socketserver源码解析和协程版socketserver
来,贴上一段代码让你仰慕一下欧socketserver的魅力,看欧怎么完美实现多并发的魅力 client import socket ip_port = ('127.0.0.1',8009) sk = ...
- Golang源码探索(二) 协程的实现原理(转)
Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱,虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻底 ...
- Golang源码探索(二) 协程的实现原理
Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻 ...
- skynet源码阅读<5>--协程调度模型
注:为方便理解,本文贴出的代码部分经过了缩减或展开,与实际skynet代码可能会有所出入. 作为一个skynet actor,在启动脚本被加载的过程中,总是要调用skynet.start和sky ...
- 04 flask源码剖析之LocalStack和Local对象实现栈的管理
04 LocalStack和Local对象实现栈的管理 目录 04 LocalStack和Local对象实现栈的管理 1.源码入口 1. flask源码关于local的实现 2. flask源码关于l ...
- Flask核心机制--上下文源码剖析
一.前言 了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很 ...
- 【Python源码剖析】对象模型概述
Python 是一门 面向对象 语言,实现了一个完整的面向对象体系,简洁而优雅. 与其他面向对象编程语言相比, Python 有自己独特的一面. 这让很多开发人员在学习 Python 时,多少有些无所 ...
- 最清晰易懂的 Go WaitGroup 源码剖析
hi,大家好,我是haohongfan. 本篇主要介绍 WaitGroup 的一些特性,让我们从本质上去了解 WaitGroup.关于 WaitGroup 的基本用法这里就不做过多介绍了.相对于< ...
- socket_server源码剖析、python作用域、IO多路复用
本节内容: 课前准备知识: 函数嵌套函数的使用方法: 我们在使用函数嵌套函数的时候,是学习装饰器的时候,出现过,由一个函数返回值是一个函数体情况. 我们在使用函数嵌套函数的时候,最好也这么写. def ...
- 玩转Android之Picasso使用详详详详详详解,从入门到源码剖析!!!!
Picasso是Squareup公司出的一款图片加载框架,能够解决我们在Android开发中加载图片时遇到的诸多问题,比如OOM,图片错位等,问题主要集中在加载图片列表时,因为单张图片加载谁都会写.如 ...
随机推荐
- uniCloud云开发入门以及对传统开发方式的思考
事情缘由 作为选修了移动互联网应用的一员,老师讲的什么JS基础,还有ES6和uniapp,当然是没怎么听,因为是之前大二的时候都大概看过. 但是快到期末,老师讲了云开发,并且布置了与此相关的大作业,自 ...
- 网络I/O模型 解读
网络.内核 网卡能「接收所有在网络上传输的信号」,但正常情况下只接受发送到该电脑的帧和广播帧,将其余的帧丢弃. 所以网络 I/O 其实是网络与服务端(电脑内存)之间的输入与输出 内核 查看内核版本 : ...
- [.NET学习] EFCore学习之旅 -1
1.创建项目 这里我们先新建一个控制台项目:"jyq.EFCore.Learn",框架基于.NET6 2.安装 Neget包 Install-Package Microsoft.E ...
- 【每日一题】【链表.next.next判空条件】141. 环形链表/NC4 判断链表中是否有环-211120/220123
给你一个链表的头节点 head ,判断链表中是否有环. 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环. 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链 ...
- Map的遍历方式,常用的key-value遍历方式
在开发过程中经常会遇到 map 的遍历,本文将会介绍四种常用的 key-value 遍历方式 说明: 增强 for 循环遍历 先取出 map 的 keySet,进行遍历,再取出对应 key 的 val ...
- Springboot配置多Redis源
Springboot配置多Redis源 一.背景 因项目部署了新集群,某些缓存数据需要在旧的redis上取,就必须配置多个数据源动态获取相对应的源以兼容业务. 二.配置依赖 <dependenc ...
- ArcObjects SDK开发 013 MapFrame
1.如何获取MapFrame 打开一个Mxd文件,可能包含一个或多个Map,每个Map都会放到一个MapFrame中,加载到PageLayout上.我们可以通过PageLayout继承的IGraphi ...
- 关键字break和continue
关键字:break 和continue提供了另一种控制循环的方式. break 是直接退出循环体 如: continue 是退出当前循环迭代 如: 需要注意的是:使用过多的break和continu ...
- Yearning建立流程和数据源进行测试
1.前提说明 前面已经搭建好了平台,并且接入了LDAP.邮箱和钉钉,现在就是建立一下数据源和流程来进行测试,如果有什么疑问可以看上一篇文章安装Yearning审核平台 2.建立流程 2.1 新建流程 ...
- CH432,CH438,CH9434串口扩展芯片常见问题
目前WCH有三款串口扩展芯片CH432,CH438以及CH9434. 型号 CH432 CH438 CH9434 扩展串口数量 2 8 4 通讯接口 并口/SPI(具体需要看芯片封装) 并口 SPI ...