有关 ucontext 的一些笔记

水一篇~😁

Intro

最近开发服务器相关的项目时,接触到了协程(Coroutine)这个概念,其在 Linux 系统下的实现大多是基于 POSIX 库提供的ucontext相关 API。这部分 API 之前没有怎么接触过,趁此机会做一下记录。另外,这篇 blog 不会涉及和协程相关的具体内容,重点是ucontext及其相关 API 的使用。
不过,在进入正式主题之前,我们对ucontext的功能还不是很清晰。那么它到底是做什么的呢?直白来说,就是实现了用户级别的上下文切换,允许程序保存和恢复执行上下文。
以现实生活为例,我们在做一件事情的时候会突然去做另外一件事情,在这件事情完成之后,再回来做之前的那件事。比如,我们正在集中精力写代码,突然接到一个电话,打完电话后,回来继续写代码。在这个过程中,我们从写代码的状态(上下文)切换到了接电话的状态(上下文)中,在打完电话(执行完成后)后,又(切换)回来继续写代码了。
ucontext API 要做的事情,就类似上述的过程。

ucontext定义了一个单独的数据结构ucontext_t和四个接口函数,我们逐个介绍。

ucontext_t

ucontext_tucontext的核心数据结构,定义如下:

1
2
3
4
5
6
7
typedef 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
#include <stdio.h>
#include <ucontext.h>

// 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;
}


Buy me a coffee ? :)
0%