认识ucontext函数簇

ucontext函数簇提供了4个函数用于控制用户态的上下文。利用这几个函数可以实现一个协程。

ucontext_t结构体

1
2
3
4
5
6
7
typedef struct {
ucontext_t *uc_link; //当前上下文结束后要恢复到时的上下文,
sigset_t uc_sigmask; //上下文要阻塞的信号集合
stack_t uc_stack; //上下文所使用的栈
mcontext_t uc_mcontext; //机器特定的保护上下文的表示,包括协程的机器寄存器
...
} ucontext_t;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <ucontext.h>

// 获取当前用户上下文,并初始化指针ucp指向的结构体。
int getcontext(ucontext_t *ucp);
// 设置当前的上下文为ucp指向的内容
int setcontext(const ucontext_t *ucp);

// 构造一个上下文,修改通过getcontext初始化的ucp,当切换到ucp指向的上下文时,将会执行func函数。
// 执行makecontext前必须为ucp分配栈和指定ucp->uc_link
void makecontext(ucontext_t *ucp, void (*func)(),
int argc, ...);
// 将当前上下文保存到指针oucp指向的结构,并切换到ucp指向的上下文
int swapcontext(ucontext_t *restrict oucp,
const ucontext_t *restrict ucp);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <ucontext.h>
#include <unistd.h>

int main(void)
{
ucontext_t context;

getcontext(&context);
printf("Hello world\n");
sleep(1);
setcontext(&context);
return 0;
}

每隔一秒打印一行Hello world


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
#include <stdio.h>
#include <ucontext.h>
#include <unistd.h>

void foo(void)
{
printf("foo\n");
}

int main(void)
{
ucontext_t context;
char stack[1024];

getcontext(&context); // 初始化context
context.uc_stack.ss_sp = stack; // 设置栈顶指针
context.uc_stack.ss_size = sizeof(stack); // 设置栈的大小
context.uc_link = NULL; //下一个要恢复的上下文为NULL, 将结束运行
makecontext(&context, foo, 0); //构建上下文,执行context上下文时,将执行foo函数

printf("Hello world\n");
sleep(1);
setcontext(&context); //设置上下文为context,在这里会切换到执行foo函数
return 0;
}

先打印Hello world,然后打印foo


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
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio.h>
#include <ucontext.h>

static ucontext_t ctx[3];

static void
f1(void)
{
printf("start f1\n");
// 将当前 context 保存到 ctx[1],切换到 ctx[2]
swapcontext(&ctx[1], &ctx[2]);
printf("finish f1\n");
}

static void
f2(void)
{
printf("start f2\n");
// 将当前 context 保存到 ctx[2],切换到 ctx[1]
swapcontext(&ctx[2], &ctx[1]);
printf("finish f2\n");
}

int main(void)
{
char stack1[8192];
char stack2[8192];

getcontext(&ctx[1]);
ctx[1].uc_stack.ss_sp = stack1;
ctx[1].uc_stack.ss_size = sizeof(stack1);
ctx[1].uc_link = &ctx[0]; // 将执行 return 0
makecontext(&ctx[1], f1, 0);

getcontext(&ctx[2]);
ctx[2].uc_stack.ss_sp = stack2;
ctx[2].uc_stack.ss_size = sizeof(stack2);
ctx[2].uc_link = &ctx[1];
makecontext(&ctx[2], f2, 0);

// 将当前 context 保存到 ctx[0],切换到 ctx[2]
swapcontext(&ctx[0], &ctx[2]); // 为什么么ctx[0]不用初始化栈?原因是swapcontext会将当前的上下文设置到ctx中,此时ctx[0]为main函数的上下文
return 0;
}

swapcontext(&ctx[0], &ctx[2])切换到ctx[2]上下文,此时将执行f2。打印start f2,然后切换上下文到ctx[1];执行f1函数,打印start f1;切换到f2,打印finish f2;f2执行完成后,因为ctx[2].uc_link=&ctx[1],再次进入ctx[1],打印出finish f1。

输出

1
2
3
4
start f2
start f1
finish f2
finish f1


Ref:
1.http://walkerdu.com/2017/01/09/ucontext-theory/
2.Complete Context Control
3.https://zhengyinyong.com/post/ucontext-usage-and-coroutine/