一种在C语言中用汇编指令和System V ucontext 支撑实现的协程切换
此文在看了 python 中基于 yield
和 yield from
(同步基于 yield 的子生成器/协程)机制的生成器/协程后,就想在C语言中实现类似机制。
在阅读 System V ucontext
手册后,便选择基于他来支撑协程切换;此篇文字发布后,Windoze 和 AbbyCin 为 用汇编支持协程切换 提供了信息和时间上的信心,遂用汇编指令实现了对协程切换的最小支持。
由于很喜欢 ramonza/libcoro 中对 sp 指针的运用,所以此文在汇编程序中延用了该指针相关方式,并使用纯汇编语句为协程传递参数——从而避免了全局变量的使用;同时为汇编支撑的协程添加了协程结束函数。
所实现协程机制包括:
- yield
- yield from
- 基于 yield 和 yield from 机制协程的调度器 loop
这些机制皆在 ln_cs.c 中实现;支撑协程上下文和切换的代码在context目录下——经此文测试,汇编指令和ucontext在支撑协程切换时无明显的时间性能差距。experiences目录下为体验以上功能机制的例子(运行体验可参见 doc 目录下的文档)。
以下是源码目录树图(已略去工程管理文件)。
. ├── 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 ├── readme.md └── src ├── ln_co.c ├── context ├── ln_context.h ├── assembly │ ├── ln_asm.c └── ucontext ├── ln_uc.c
核心逻辑代码 ln_cs.c 经过此文的一些技巧性修剪后仍不到 500 行,所以还是很想继续分享的——便补写了文档
与源码一起上传到了 github 和 gitee之上。
另外,此文本着更好管理工程的理念——写了一大推工程管理文件。
对CPU执行程序原理有所理解的同学应该都能胜任对该程序的阅读理解,有兴趣或与此文具相同程序理念(从源码或文档中可以看出一些吧)的同学一起出出新招继续提升下该程序吧。如
[1] 提升汇编代码以支撑协程的 return 语句
/** * 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] 修整有瑕疵的工程管理文件及管理理念本身。
[3...]
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
从一位在线教育客户的紧急咨询说起
“我们在 1 月底的时候遇到第一波的流量突增,主要集中在上午和晚上,可能是全国的学校已经明确了延期开学的事情,并通知各学校师生以在线教育的方式渡过困难期,当时,我们在阿里云上进行了紧急扩容来应对这波流量的突增。但我们也清楚,等正式开学,还会有更多的流量进来,而且很可能高流量会成为一种常态,这使得我们开始重新审视当前的基础设施是否能支撑未来的业务发展。” 这段时间,遇到这类情况的客户不只这一家,我们接到了大量的来自在线教育客户的紧急咨询,需求均是是否有一些不影响现有架构的无侵入工具,能应对流量的激增情况,同时也会开始重新梳理架构和业务之间的关系。 本文整理了阿里在高可用架构建设过程中的一些实践,分为架构设计、容量规划、业务监控、线上管控、日常巡检和常态化演练。 架构设计 首先要实现架构的可视化。 通过架构感知可以全面了解云上系统架构,以可视化的方式直观呈现云上资源、容器和应用间分层依赖关系。 服务器、存储、网络是现代云平台的基础设施,随着上云战略的推进,越来越多的企业将业务、服务、系统构建在云平台上。但开源软件和云服务的多样性,开发语言的异构性,以及企业 IT 团队的组织和能力差异,都提...
- 下一篇
NovalIDE 1.1.2 版本新年重磅发布啦!
软件官方网址:http://www.novalide.com NovalIDE 1.2.1 新年隆重发布,新版本添加了以下重磅功能! 发布gittool插件,支持clone,commit,checkout项目和代码 添加django插件,支持一键式转换django项目以及web调试 添加了自动完成插件,使用增强版智能提示功能可以边输入或边退格边提示 支持文本自动插入成对的符号,如引号,三引号,括号等 支持刷新项目文件夹下的文件并自动添加到项目文件中 优化调试器监视显示监视值类型 调试器运行时设置是否鼠标悬停时显示监视文本的内存值 修复了一些已知BUG 下面是软件功能截图:
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Docker安装Oracle12C,快速搭建Oracle学习环境
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS8安装Docker,最新的服务器搭配容器使用
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS7安装Docker,走上虚拟化容器引擎之路