一种在C语言中用汇编指令和 System V ucontext 支撑实现的协程
1 源码内容
此文在看了 python 中的yield
和yield from
机制后,就想在C语言中实现类似机制。目前具体包括
[1] yield —— 类似 python 的 yield,用于协程切换;
[2] send —— 类似 python 中生成器中的 send(),用于切换到协程;
[3] yield from —— 类似 python 的 yield from,用于同步基于 yield 的协程;
[4] loop scheduler —— 略似于python 的 asyncio.loop(),用于各协程的并发调度。
2 基石——上下文和切换的支撑
以上所提功能机制的基石是“上下文描述”和“切换”。
在阅读 System V ucontext
手册后,便选择基于他来支撑基石部分。此篇文字发布后,Windoze 和 AbbyCin 为 用汇编指令来支撑基石部分 提供了信息和时间上的信心,遂用汇编指令实现了基石部分的最小支撑环境。
由于很喜欢 ramonza/libcoro 中对指针的使用,所以此文在汇编程序中延用了该部分指针相关的使用方式。同时使用汇编指令实现向协程传递参数——从而避免了全局变量;并为汇编支撑的协程添加了主动结束逻辑——汇编环境尚不能处理协程结束时C默认的 return 返回机制。
3 源码结构
以下是源码目录树图(已略去工程管理文件)。与刚诞生编写该程序时相比,真是不积小流无以成江海啊!
.
├── 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] 另外,此文本着更好的工程管理理念——写了一大推工程管理文件,充斥在各目录下。
4 源码备份
增加了汇编支撑协程切换后,该工程叫什么好呢?由于源码中主含C代码,就将其命为 ccoroutine 吧。ccoroutine 在 github 和 gitee之上各有备份。
5 源码提升
对CPU执行程序原理有所理解的同学应该都能胜任对该程序的阅读理解。此文又已黔驴技穷,心情好时快来一起提升该程序吧。如
[1] 提升汇编代码以支撑协程的 return 语句
若可实现,对汇编语法熟悉者肯定轻描淡写般完成该点睛之笔:把 _CORET 标号处地址压入栈中——即解决在AT&T汇编中,如何将标号地址入栈的问题。
/**
* 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业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
阿里云上线“人脸口罩检测”算法服务,助力智能化疫情防控!
面对来势汹汹的肺炎疫情,行人是否佩戴口罩是关键的疫情防控点,口罩佩戴检测则是一项核心工作。据有关统计,当前线下超市等公共场所出现的人员口罩佩戴率只有70%-90%,仍有大量行人暴露在易感环境中,还有部分行人口罩佩戴不规范。但是,以人盯人的方式去监督管理较为耗费人力并且存在疏漏的可能。近期,阿里云视觉智能平台(vision.aliyun.com)推出“人脸口罩检测”算法服务,并结合阿里云客流分析平台、钉钉小程序及天猫精灵,共同打造一站式公共场所出行人员口罩佩戴检测及统计预警系统。 阿里云视觉智能开放平台(vision.aliyun.com)是基于阿里巴巴视觉智能技术实践经验,面向视觉智能技术的开发与应用用户,为其提供好用、易用、普惠的视觉智能API服务,帮助企业、开发者快速建立视觉智能技术的应用能力的综合性视觉AI能力平台。疫情当前,平台紧急推出了基于视觉AI分析的“人脸口罩检测”算法服务,通过对接该服务可快速构建监控系统并可统计人员的口罩佩戴情况,实现疫情防控的AI化,数字化。 那么“人脸口罩检测”能力是如何让佩戴口罩落到实处,并协助监测系统实现闭环的发现、告警、监督以及管理的呢?...
-
下一篇
不应该相信的云计算和人工智能两个误区
如果人们认为云计算将导致数据中心消亡,而人工智能项目注定要失败,那么需要再考虑。 在技术界,有两个主要的缺点:人们过于渴望迎接未来,具有讽刺意味的是,如果发展速度不如人们预期的那么快,就无视它。举个例子,今天有两个持续存在的误区:第一,云支出正在使数据中心支出大打折扣;第二,人工智能过度炒作在很大程度上使企业购买者失败。 以下对此进行一下整理。 误区1:企业的数据中心注定要消亡 Gartner公司开创了第一个误区,分析师Dave Cappuccio认为,到2025年将有80%的企业关闭其数据中心(而2018年则为10%)。但是Cappuccio给出了他的思考的一些扎实的理由:“随着互连服务、云计算提供商、物联网(IoT)、边缘服务和SaaS产品的不断增加,留在传统数据中心拓扑结构中的原理将有优势有限。” 业务需要灵活性,但IT需要控制。答案?按企业的条件使用IT。 原因是数据引力。尽管这种引力作用了一段时间(如果数据存在于数据中心中,但是将其推送到云中进行处理变得效率低下),但现在产生了相反的效果:越来越多的数据诞生于云中,并且将会在那里存储、处理和分析。 但是数据中心并没有消亡。 这...
相关文章
文章评论
共有0条评论来说两句吧...