Intro
最近开发服务器相关的项目时,接触到了协程(Coroutine)这个概念,其在 Linux 系统下的实现大多是基于 POSIX 库提供的ucontext
相关 API。这部分 API 之前没有怎么接触过,趁此机会做一下记录。另外,这篇 blog 不会涉及和协程相关的具体内容,重点是ucontext
及其相关 API 的使用。
不过,在进入正式主题之前,我们对ucontext
的功能还不是很清晰。那么它到底是做什么的呢?直白来说,就是实现了用户级别的上下文切换,允许程序保存和恢复执行上下文。
以现实生活为例,我们在做一件事情的时候会突然去做另外一件事情,在这件事情完成之后,再回来做之前的那件事。比如,我们正在集中精力写代码,突然接到一个电话,打完电话后,回来继续写代码。在这个过程中,我们从写代码的状态(上下文)切换到了接电话的状态(上下文)中,在打完电话(执行完成后)后,又(切换)回来继续写代码了。ucontext
API 要做的事情,就类似上述的过程。
ucontext
定义了一个单独的数据结构ucontext_t
和四个接口函数,我们逐个介绍。
ucontext_t
ucontext_t
是ucontext
的核心数据结构,定义如下:1
2
3
4
5
6
7typedef struct ucontext_t {
struct ucontext_t *uc_link;
sigset_t uc_sigmask;
stack_t uc_stack;
mcontext_t uc_mcontext;
... // 其他成员
} ucontext_t;
这是一个标准实现,前四个数据成员是必须存在的,对应的含义:
uc_link
:指向ucontext_t
的指针,在当前上下文结束时,切换到其指向的上下文。uc_sigmask
:当前上下文所使用的信号量的集和。uc_stack
:当前上下文所使用的栈信息。uc_mcontext
:保存当前上下文的机器信息,如寄存器状态。
getcontext
函数原型:1
int getcontext(ucontext_t *ucp);
功能:初始化ucp
指向的ucontext_t
结构体。函数执行成功返回0
,失败返回-1
。
setcontext
函数原型:1
int setcontext(const ucontext_t *ucp);
功能:恢复ucp
指向的上下文。函数执行成功不返回,失败返回-1
。
makecontext
函数原型:1
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
功能:为ucp
指向的ucontext_t
设置一个新的上下文,并设置入口函数,注意此时ucp
所指向的内容必须是实际存在的,并且已经分配好了供其使用的栈空间。
参数:ucp
,指向新的上下文的指针;func
,新上下文的入口函数;argc
,参数个数;...
,具体的参数。
swapcontext
函数原型:1
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);
功能:切换到ucp
指向的上下文,并保存当前上下文到oucp
。函数指向成功不返回,执行失败返回-1
。
test
最后,给出一个示例代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 0 for main, 1 for func
ucontext_t uct[2];
void func() {
printf("func: begin\n");
printf("func: switching to main\n");
swapcontext(&uct[1], &uct[0]);
printf("func: end\n");
}
int main() {
char stack[8192]; // uct[1] 栈空间
getcontext(&uct[1]); // 初始化 ucontext_t 结构体
uct[1].uc_stack.ss_sp = stack; // 设置栈指针
uct[1].uc_stack.ss_size = 8192; // 设置栈大小
uct[1].uc_link = &uct[0]; // 设置结束后返回 main
makecontext(&uct[1], func, 0);
printf("main: switching to func\n");
swapcontext(&uct[0], &uct[1]);
printf("main: back from func\n");
printf("main: switching to func again\n");
swapcontext(&uct[0], &uct[1]);
printf("main: end\n");
return 0;
}