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]

  • 协程上下文切换汇编源码:参考文献[2]

源码解析

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

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

  2. 如上图,进入 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 : &regs[7] + sizeof(void*)

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

  3. 接下来就是按照约定,依次将 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 数组中。
    移入寄存器后的内存布局如下:

  4. 接下来将第二个参数的值(即 切换的新上下文信息的结构的地址 )存入栈顶寄存器 ESP, 作为栈顶指针。

    movl 4(%eax), %esp //parm b -> &regs[0]

    操作后的内存布局如下:

  5. 返回地址(return address) 的值弹出到 EAX 寄存器中:

    popl %eax  //ret func addr

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

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

    操作后的内存布局如下:

  6. 接下来是恢复之前的栈数据。根据前面的分析,我们可以知道当前栈顶 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 位的更多相关文章

  1. socketserver源码解析和协程版socketserver

    来,贴上一段代码让你仰慕一下欧socketserver的魅力,看欧怎么完美实现多并发的魅力 client import socket ip_port = ('127.0.0.1',8009) sk = ...

  2. Golang源码探索(二) 协程的实现原理(转)

    Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱,虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻底 ...

  3. Golang源码探索(二) 协程的实现原理

    Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻 ...

  4. skynet源码阅读<5>--协程调度模型

    注:为方便理解,本文贴出的代码部分经过了缩减或展开,与实际skynet代码可能会有所出入.    作为一个skynet actor,在启动脚本被加载的过程中,总是要调用skynet.start和sky ...

  5. 04 flask源码剖析之LocalStack和Local对象实现栈的管理

    04 LocalStack和Local对象实现栈的管理 目录 04 LocalStack和Local对象实现栈的管理 1.源码入口 1. flask源码关于local的实现 2. flask源码关于l ...

  6. Flask核心机制--上下文源码剖析

    一.前言 了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很 ...

  7. 【Python源码剖析】对象模型概述

    Python 是一门 面向对象 语言,实现了一个完整的面向对象体系,简洁而优雅. 与其他面向对象编程语言相比, Python 有自己独特的一面. 这让很多开发人员在学习 Python 时,多少有些无所 ...

  8. 最清晰易懂的 Go WaitGroup 源码剖析

    hi,大家好,我是haohongfan. 本篇主要介绍 WaitGroup 的一些特性,让我们从本质上去了解 WaitGroup.关于 WaitGroup 的基本用法这里就不做过多介绍了.相对于< ...

  9. socket_server源码剖析、python作用域、IO多路复用

    本节内容: 课前准备知识: 函数嵌套函数的使用方法: 我们在使用函数嵌套函数的时候,是学习装饰器的时候,出现过,由一个函数返回值是一个函数体情况. 我们在使用函数嵌套函数的时候,最好也这么写. def ...

  10. 玩转Android之Picasso使用详详详详详详解,从入门到源码剖析!!!!

    Picasso是Squareup公司出的一款图片加载框架,能够解决我们在Android开发中加载图片时遇到的诸多问题,比如OOM,图片错位等,问题主要集中在加载图片列表时,因为单张图片加载谁都会写.如 ...

随机推荐

  1. 如何理性看待国内大热的HuTool工具包

    一.序言 关于HuTool工具包,相信很多技术朋友都听说甚至使用过.在HuTool之前,已经有比较成熟的工具包比如Apache Common包,谷歌推出的Guava包,他们已经在全世界大范围使用了. ...

  2. [数学建模]层次分析法AHP

    评价类问题. 问题: ① 评价的目标? ② 可选的方案? ③ 评价的指标? 分层 目标层.准则层.方案层 层次分析法可分为四个步骤建立: 第一步:标度确定和构造判断矩阵: 第二步:特征向量,特征根计算 ...

  3. APP异常测试点汇总

    在测试APP时异常测试是非常必要的. 安装卸载中的异常测试 一.安装 安装过程中设备重启 安装过程中息屏 安装过程中断网 安装过程中切换网络 安装过程中收到短信提醒 安装过程中收到来电提醒 安装过程中 ...

  4. jmeter json提取器提取某个属性的所有值

    json 提取器各字段说明: Variable names:保存的变量名,后面使用${Variable names}引用 JSON Path  expressions:调试通过的json path表达 ...

  5. matlab狄拉克锥的三维图

    石墨烯(graphene)中的狄拉克锥(Dirac cone)图形.直接按照能级公式绘图,公式参考[1]中 energy of the electrons 公式. %matlab代码 clear; g ...

  6. kali之pip和pip3安装

    安装pip 首先安装setuptools,setuptools是 Python Enterprise Application Kit(PEAK)的一个副项目,它 是一组Python的 distutil ...

  7. python进阶之路9文件的处理方法

    内容回顾 字典内置方法 1.类型转换 dict() 2.重要操作 get() d[key] = values 常用 pop() update() 键存在则修改键值对 键不存在则新增键值对 fromke ...

  8. Go语言的赞和喷

    (原发于 GitHub Pages,2019-01-01 23:22:43) 2019 年,我回来了. 不知不觉中,我入 PHP 的坑已经 3 年有余,入 Go 的坑也大半年了.作为不评论不舒服斯基星 ...

  9. 让 Win8.1 微软拼音新体验模式支持 Metro 应用

    内容回顾: [1]十个步骤找回 Win8 中的微软拼音新体验模式 [2]发布个工具,一键恢复Win8/8.1中的微软拼音长句模式(新体验模式) 在 [1] 中,我们找回了 Win8 里的微软拼音新体验 ...

  10. [C#]C++/CLI中interior_ptr和pin_ptr的区别

    interior_ptr 当垃圾回收器移动对象时,Interior pointer能随之移动,并始终指向该对象. 但是如果把这个指针返回给外部函数,那么当垃圾回收时(垃圾回收期间会压缩对象,),对象地 ...