简单协程的实现
简单协程的实现
基本原理
之前的一篇短文简单分析了Linux内核中任务切换的实现机制,其精巧的方法让人叹为观止:Linux内核源码诚然是世界范围内的IT精英的杰作,开源项目的典范。通过qemu虚拟机及gdb调试工具,对任务切换的功能可以有较为深刻的理解,不过我想可以更进一步,将内核的任务切换移植到应用层,这样也就是协程实现的简单实现了。
简单协程的实现
协程的结构体定义
struct co_thread {/* registers */struct co_context ctx;struct fp_context fpctx;
/* coroutine stack */unsigned long stk_end;unsigned long stk_top;unsigned int stk_size;unsigned int co_state;
struct co_thread * master;
/* for slave coroutine */void * co_arg;co_thread_func co_func;int co_id;
/* for input & output */int co_loop;void * inout;unsigned int iolen;struct co_thread * co_list[0];
};
如上图,C语言代码运行时需要一个上下文环境,即上面代码段的co_context;除此之外,还需要栈空间,即stk_end/stk_top。其中co_context与Linux内核中定义的cpu_context完全相同:
typedef unsigned long coreg_t;
struct co_context_arm64 {coreg_t x19;coreg_t x20;coreg_t x21;coreg_t x22;coreg_t x23;coreg_t x24;coreg_t x25;coreg_t x26;coreg_t x27;coreg_t x28;coreg_t x29;coreg_t sp;coreg_t pc;
};
协程的任务切换实现
extern void _co_fp_store(void *);
extern void _co_fp_restore(void *);
extern struct co_thread * _co_switch(void *, void *);
static struct co_thread * co_switch(struct co_thread * prev, struct co_thread * next)
{struct co_thread * rval;
if (__builtin_expect(prev == next, 0)) {fputs("Fatal Error, coroutine cannot switch to itself!\n", stderr);fflush(stderr);return NULL;}
_co_fp_store(&(prev->fpctx));_co_fp_restore(&(next->fpctx));if ((prev->co_state & COTHREAD_STATE_DEAD) == 0)prev->co_state = COTHREAD_STATE_SUSPEND;next->co_state = COTHREAD_STATE_RUNNING;rval = _co_switch(prev, next);return rval;
}
见上面的代码段,实现了简单协程的任务切换。主要是引用了三个汇编函数:调用co_fp_store存储当前的浮点运算的上下文,并调用co_fp_restore恢复新任务的浮点运算的上下文,最后调用了_co_switch汇编函数进行任务切换。该汇编函数是参照内核源码中的cpu_switch_to,稍加修改而成的:
#include "coroutine.h"static int test_func(struct co_thread * co, void * what)
{int idx; fprintf(stdout, "Coroutine %d running, what: %p\n", co->co_id, what);for (idx = 0; idx < 5; ++idx) {fprintf(stdout, "In [%s], index: %d, errno = %d\n", __FUNCTION__, idx, errno);fflush(stdout); co_thread_yield(co, NULL, 0, NULL);if (co->co_loop == 0)break;}return 0;
} int main(int argc, char *argv[])
{int idx;void * rval;struct co_thread * boss, * slave;boss = co_thread_master();if (boss == NULL)return 1;slave = co_thread_create(boss, 1, test_func,(void *) 0x2020ul, COROUTINE_STACK_SIZE);if (slave == NULL) {co_thread_free(boss, 1);return 2;}idx = 0; do {fprintf(stdout, "In main cothread, index: %d\n", idx++);fflush(stdout); errno = idx;rval = co_thread_resume(slave, NULL, 0, NULL);if (coroutine_dead(slave))break;} while (rval != COROUTINE_ERROR);
co_thread_destory(slave);co_thread_free(boss, 1);return 0;
}
协程仍属于同一进程的同一个线程,我们通过读写线程相关的全局变量errno来验证。接下来编译并运行此协程测试,其结果如下:
yejq@UNIX:~/program/coroutine$ make
aarch64-linux-gnu-gcc -Wall -O2 -fPIC -D_GNU_SOURCE -I. -c -o coroutine.o coroutine.c
aarch64-linux-gnu-gcc -Wall -O2 -fPIC -D_GNU_SOURCE -I. -c -o main.o main.c
aarch64-linux-gnu-gcc -Wall -O2 -fPIC -D_GNU_SOURCE -I. -c -o co_switch.o co_switch.S
aarch64-linux-gnu-gcc -o cor coroutine.o main.o co_switch.o
yejq@UNIX:~/program/coroutine$ ls
cor coroutine.c coroutine.h coroutine.o co_switch.o co_switch.S main.c main.o Makefile
yejq@UNIX:~/program/coroutine$ file cor
cor: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=5a1cd3636c738e6ac3dead55ad85b6a3c0972710, with debug_info, not stripped
yejq@UNIX:~/program/coroutine$ QEMU_LD_PREFIX=/opt/gcc-linaro-7.4.1-2019.02-x86_64_aarch64-linux-gnu/aarch64-linux-gnu/libc \
> qemu-aarch64 ./cor
In main cothread, index: 0
Coroutine 1 running, what: 0x2020
In [test_func], index: 0, errno = 1
In main cothread, index: 1
In [test_func], index: 1, errno = 2
In main cothread, index: 2
In [test_func], index: 2, errno = 3
In main cothread, index: 3
In [test_func], index: 3, errno = 4
In main cothread, index: 4
In [test_func], index: 4, errno = 5
In main cothread, index: 5
为了测试方便,我们使用qemu虚拟机直接运行该AArch64平台的二进制应用。实测在ARM 64位嵌入式设备也同样正常运行,输出结果相同。main函数与test_func交替运行,恰似线程。此外,可见主协程修改了全局变量errno,test_func协程能够读取到,说明二者同属一个线程。
至此,我们在ARM 64位平台就实现了简单的协程,这样也就更进一步加深了对Linux内核中任务切换的理解。该文的完整代码可在笔者的下载区域获取到。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
