协程解析一 | ucontext
概述
最近在研究协程的实现原理,看了云风的coroutine和腾讯的开源库libco后,原来要实现一个协程库也没那么难。我先来讲讲云风的coroutine库。他使用的是 uncontext
来保存程序运行上下文,进而实现协程库,这个库很值深入了解一番,吃透了这个库,协程的原理也就了解了。
头文件
#include <ucontext.h>
ucontext
结构体
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实现流程控制
代码如下
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写一个消费者和生产者的程序
步骤如下:
- 生产者,产生一个值,保存当前上下文,切换到消费者上下文- 消费者,输出生产者传递过来的值。保存消费者的当前上下文,切换到生产者 上下文切换已经做了标记,代码如下:
//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
--完--
- 原文作者: 留白
- 原文链接: https://zfunnily.github.io/2021/03/coroutineone/
- 更新时间:2024-04-16 01:01:05
- 本文声明:转载请标记原文作者及链接