一种在C语言中用汇编指令和System V ucontext 支撑实现的协程
1 源码内容
此文在看了 python 中基于 yield
和 yield from
(同步基于 yield 的子生成器/协程)机制的生成器/协程后,就想在C语言中实现类似机制。
在阅读 System V ucontext
手册后,便选择基于他来支撑协程切换。此篇文字发布后,Windoze 和 AbbyCin 为 用汇编指令支撑协程切换 提供了信息和时间上的信心,遂用汇编指令实现了对协程切换的最小支撑环境。
由于很喜欢 ramonza/libcoro 中对 sp 指针的使用,所以此文在汇编程序中延用了该指针相关的使用方式,同时使用汇编指令实现向协程传递参数——从而避免了全局变量;并为汇编支撑的协程添加了主动结束逻辑——汇编环境尚不能处理协程结束时C默认的 return 返回机制。
此文用C语言所实现协程机制包括:
[1] yield —— 类似 python 的 yield; [2] yield from —— 类似 python 的yield from,用于同步包含 yield 的协程; [3] loop scheduler —— 调度基于 yield 和 yield from 的协程。
2 源码结构
以下是源码目录树图(已略去工程管理文件)。
. ├── doc │ ├── A coroutine ... in C-language.md │ └── An optimization ... in C-language.md ├── experiences │ ├── loop_e │ │ ├── loop_e.c │ ├── yield_e │ │ └── yield_e.c │ └── yield_from_e │ └── yield_from_e.c ├── include │ ├── ln_co.h │ └── ln_comm.h ├── src │ ├── ln_co.c │ ├── context │ ├── ln_context.h │ ├── assembly │ │ ├── ln_asm.c │ └── ucontext │ ├── ln_uc.c └── readme.md
[1] 协程切换逻辑在 src/ln_cs.c 中实现,经过此文的一些技巧性修剪后 其仍不到 500 行哟。
[2] 描述协程上下文和支撑协程切换的代码在 context 目录下。此文曾分别运行 10000000 个_co_fn
协程作测试——汇编指令和 ucontext 在支撑协程切换时无明显的时间性能差距。
[3] experiences 为体验 yield,yield from,loop scheduler 各功能的目录;doc是 ucontext 支撑协程切换时期编写的日记文档。
[4] 另外,此文本着更好的工程管理理念——写了一大推工程管理文件,充斥在各目录下。
3 源码备份
增加了汇编支撑协程切换后,该工程叫什么好呢?由于源码中主含C代码,就将其命为 ccoroutine 吧。ccoroutine 在 github 和 gitee之上各有备份。
4 源码提升
对CPU执行程序原理有所理解的同学应该都能胜任对该程序的阅读理解。此文又已黔驴技穷,心情好时快来一起提升该程序吧。如
[1] 提升汇编代码以支撑协程的 return 语句
若可实现,对汇编语法熟悉的肯定能完成该机制:把 _CORET 标号处地址压入栈中——即解决在汇编中,如何将标号压入栈的问题。
/** * ln_asm.c, * coroutine context, switching implement * by assembly instructions. * * lxr, 2020.02 */ #include "ln_context.h" #include "ln_comm.h" #include <stdlib.h> void _co_arg_medium(void); /** * because of the instructions on * stack-frame would be automatically * added by C-compiler in C-functions, * those routines can't be * inline-assembly in C-function. * * @caution: the routines on __i386 * not tested by me. */ __asm__ ( "\t.globl _co_arg_medium\n" "_co_arg_medium:\n" #if __i386 "popl %eax\n\t" // get co_fn "popl %ecx\n\t" // get arg "popl %edx\n\t" // get ci /* call convention on arguments of gcc on __i386 e.g. void fn(int a, int b); fn(1, 2); passing argument by stack in caller: push 2 --> b push 1 --> a */ "movl $0, %esi\n\t" "pushl %esi\n\t" "pushl %ecx\n\t" "pushl %esi\n\t" "pushl %edx\n\t" #elif __amd64 "popq %rax\n\t" // get co_fn "popq %rsi\n\t" // get arg "popq %rdi\n\t" // get ci /* call convention on arguments of gcc on __amd64 e.g. void fn(uint32_t a, uint32_t b, uint32_t c, uint32_t e, uint32_t f, uint32_t g, uint32_t h); passing arguments by registers and stack in caller: edi --> a, esi --> b, edx --> c, ecx --> e, r8d --> f, r9d --> g, push real h --> formal h */ "movl %esi, %edx\n\t" "movq %rsi, %rcx\n\t" "shrq $32, %rcx\n\t" "movq %rdi, %rsi\n\t" "shrq $32, %rsi\n\t" "jmpq *%rax\n\t" #endif ); __asm__( "\t.globl co_switch_asm\n" "co_switch_asm:\n" #if __i386 "pushl %ebp\n\t" "pushl %ebx\n\t" "pushl %esi\n\t" "pushl %edi\n\t" "movl %esp, (%eax)\n\t" "movl (%edx), %esp\n\t" "popl %edi\n\t" "popl %esi\n\t" "popl %ebx\n\t" "popl %ebp\n\t" "popl %ecx\n\t" "jmpl *%ecx\n\t" #elif __amd64 "pushq %rbp\n\t" "pushq %rbx\n\t" "pushq %r12\n\t" "pushq %r13\n\t" "pushq %r14\n\t" "pushq %r15\n\t" /* I want to save _CORET here by instructions just like 'pushq _CORET'(etc.)to accept coroutine return. unfortunately fail. so coroutines which use CCTX_ASM to support coroutine-switching must use co_end() to terminate itself before return. the same situation on __i386, please do a favor to accept the 'return' statement of coroutine if you owns the same faith. */ "movq %rsp, (%rdi)\n\t" "movq (%rsi), %rsp\n\t" "popq %r15\n\t" "popq %r14\n\t" "popq %r13\n\t" "popq %r12\n\t" "popq %rbx\n\t" "popq %rbp\n\t" "popq %rcx\n\t" "jmpq *%rcx\n" "_CORET:" #else #error "coroutine-context unsupported" " on current architecture" #endif ); int co_start_asm(ci_s *ci) { cctx_s *ctx = NULL; int ss = co_ssize(ci); char *stack = co_stack(ci); IF_EXPS_THEN_RETURN(!ci || !stack || !ss, CODE_BADPARAM); ctx = co_cctx(ci); ctx->sp = (void **)(stack + ss); /* ctx->sp points to (void *), so arithmetic unit of ctx->sp is sizeof(void *). initial co-stack as follow: ------+---------+------+----+----+----+ ... |co_medium| co_fn| arg| ci |NULL| ------+---------+------+----+----+----+ ^ ^ ^ | | | stack sp stack+ss */ *--ctx->sp = NULL; *--ctx->sp = ci; *--ctx->sp = co_arg(ci); *--ctx->sp = co_cofn(ci); *--ctx->sp = _co_arg_medium; /* Reserved for subroutines to backup registers: ebp, ebx, esi, edi on i386; rbp, rbx, r12, r13, r14, r15 on amd64.*/ ctx->sp -= CS_RESERVE_NR; (void)co_switch_asm(co_bcctx(ci), ctx); return CODE_NONE; }
[2] 修整有瑕疵的工程管理文件及管理理念本身。如 context 目录中代码有更新,experiences 还不能同步该更新。
[...]

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
当面试官要求现场手敲代码,该如何体现你对Python的编程能力?
云栖号:https://yqh.aliyun.com第一手的上云资讯,不同行业精选的上云企业案例库,基于众多成功案例萃取而成的最佳实践,助力您上云决策! 如果你已经通过了招聘人员的电话面试,那么下面正是该展现你代码能力的时候了。无论是练习,作业,还是现场白板面试,这都是你证明自己的代码技巧的时刻。 我们知道面试官常常会出一些题让你来解决,作为一名程序员,除了需要具备解决问题的思路以外,代码的质量和简洁性也很关键。因为从一个人的代码可以直接看出你的基本功。对于Python而言,这就意味着你需要对Python的内置功能和库有很深入的了解。 本篇给大家介绍一些很强大的功能,它们能让面试官眼前一亮,觉得你很高级,这可以很大程度上给你加分。对于这些功能,我们从Python内置函数开始,然后是Python对数据结构的天然支持,最后是Python强大的标准库。 选择正确的内置功能 Python有一个大型标准库,但只有一个内置函数的小型库,这些函数总是可用的,不需要导入。它们每一个都值得我们仔细研究,但是在研究前,我还是给大家一些小的提示,尤其是在其中一些函数的情况下,可以用什么替代更好。 使用enu...
- 下一篇
AntV 架构演进-G6 篇
本文作者:AntV 架构师-萧庆 简介 G6 是一个图关系可视化引擎,起始于我们的业务需求,历经波折,每次改版其架构都有很大的变化,这些变化背后都有来自业务上的思考和我们对 G6 定位的调整,今天我们一起来回顾: G6 之前的关系可视化 V1.0 关系映射 V2.0 图编辑器 V3.0 图分析引擎 G6 发展的时间线如下: G6 之前的关系可视化 早在做 G2之前我们就接触了集团内部一些关系图的项目,以安全和风控的业务为主,也有一些动态的流程图,但是团队迟迟没有决定编写一套关系图框架,很大的一个原因在于:有太多失败的关系图项目。 往往是项目一开始得到各个方面的大力支持,我们配合设计师做了一套好看炫酷的关系图展示页面,初期开发者、设计者都很满意,但是真正的使用者依然解决不了问题,大都类似于这类图: 一方面用户很难完成业务上的任务,看起来好看但是不好用,另一方面使用的技术栈很零散,一旦我们退出这个项目,后期基本处于维护乏力的状况。 当我们开始做 G2 后,需要在 G2 中实现一些关系图: 这时候很多部门的开发同学希望我们在 G2 中也能支持流程图,例如: 但是 G2 的架构来做关系图的展示...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS8安装Docker,最新的服务器搭配容器使用