💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] ## 概述 最近在研究协程的实现原理,看了云风的coroutine和腾讯的开源库libco后,原来要实现一个协程库也没那么难。我先来讲讲云风的coroutine库。他使用的是 `uncontext`来保存程序运行上下文,进而实现协程库,这个库很值深入了解一番,吃透了这个库,协程的原理也就了解了。 头文件 ``` #include <ucontext.h> ``` `ucontext`结构体 ```c typedef struct ucontext { unsigned long int uc_flags; struct ucontext *uc_link; stack_t uc_stack; mcontext_t uc_mcontext; __sigset_t uc_sigmask; struct _libc_fpstate __fpregs_mem; } ucontext_t; ``` * `sigset_t `和 `stack_t` 定义在标准头文件 `<signal.h>` 中 * `uc_link`字段保存当前context结束后继续执行的context记录; * `uc_sigmask` 记录该context运行阶段需要屏蔽的信号; * `uc_stack` 是该context运行的栈信息, * `uc_mcontext` 则保存具体的程序执行上下文——如PC值、堆栈指针、寄存器值等信息 操作`ucontext`的四个函数 ``` getcontext() : 获取当前context setcontext() : 切换到指定context makecontext() : 设置 函数指针 和 堆栈 到对应context保存的sp和pc寄存器中,调用之前要先调用 getcontext() swapcontext() : 保存当前context,并且切换到指定context ``` ## 通过getcontext和swapcontext实现流程控制 代码如下 ```c int main() { ucontext_t ctx, main; int done = 1; getcontext(&ctx); if (done > 5) { printf("ctx done\n"); swapcontext(&ctx, &main); //切换到指定的main } printf("done: %d\n", done++); swapcontext(&main, &ctx); //保存当前上下文`main`,并且切换到指定`ctx` printf("return 1\n"); return 1; } ``` 具体步骤如下: * 获取当前上下文: getcontext(&ctx); * 判断 `done>5`, * 如果为假则保存当前上下文`main`,并且切换到指定`ctx`, `swapcontext(&main, &ctx)` * 如果为真则切换上下文: `swapcontext(&ctx, &main);`, 输出如下: ``` done: 1 done: 2 done: 3 done: 4 done: 5 ctx done return 1 ``` ## 使用`uncontet`写一个消费者和生产者的程序 步骤如下: * 生产者,产生一个值,保存当前上下文,切换到消费者上下文 * 消费者,输出生产者传递过来的值。保存消费者的当前上下文,切换到生产者 上下文切换已经做了标记,代码如下: ```c //procus.c #include <stdlib.h> #include <stdio.h> #include <ucontext.h> #include <stddef.h> #include <sys/types.h> #define STACK_SIZE (1024*1024) struct args { int n; int send; ucontext_t* ctx; ucontext_t* main_ctx; }; void product(void *arg) { struct args* a = (struct args*)arg; a->send = 1; while (a->n<5) { printf("a->n: %d\n", a->n++); swapcontext(a->ctx, a->main_ctx); //标记1, 保存当前上下文到a->ctx, 切换到a->main_ctx, 跳转到标记4 } a->send = 0; printf("send is 0: %d\n", a->n++); swapcontext(a->ctx, a->main_ctx); //标记2,保存当前上下文到a->ctx, 切换到a->main_ctx, 跳转到标记4 } void customer(void *arg) { struct args* a = (struct args*)arg; while (a->send==1) { printf("n: %d\n", a->n); swapcontext(a->main_ctx, a->ctx); //标记3,保存当前上下文到a->main_ctx, 切换到a->ctx, 跳转到标记1 } } int main() { ucontext_t ctx, main_ctx; getcontext(&ctx); struct args a; a.n = 1; a.ctx = &ctx; a.main_ctx = &main_ctx; ctx.uc_stack.ss_sp = malloc(STACK_SIZE); //分配一块空间 ctx.uc_stack.ss_size = STACK_SIZE; ctx.uc_link = a.main_ctx; makecontext(&ctx, product, 1, &a); //指定上下文与对应的生产者函数 swapcontext(a.main_ctx, &ctx); //标记4, 保存当前上下文到a.main_ctx, 切换到ctx,跳转到product函数 customer(&a); //消费者函数 printf("finally return\n"); return 0; } ``` 编译,运行,输出如下: ``` $ gcc -g -Wall -o procus procus.c $ ./procus a->n: 1 n: 2 a->n: 2 n: 3 a->n: 3 n: 4 a->n: 4 n: 5 send is 0: 5 finally return ```