您现在的位置是:首页 > 文章详情

一种在C语言中用汇编指令和 System V ucontext 支撑实现的协程

日期:2020-02-01点击:770
1 源码内容

此文在看了 python 中的yieldyield 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 还不能同步该更新。

[...]

原文链接:https://my.oschina.net/misskissC/blog/3162374
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章