最近在看C++
协程相关的代码, 发现很多协程的实现都用到了ucontext
库, 因此这里记录下ucontext
库的学习笔记。
1. 概述
ucontext
是 POSIX 标准中定义的一组函数,用于实现用户级上下文切换。它允许程序保存和恢复执行上下文(如寄存器、程序计数器、栈等),常用于实现协程、轻量级线程或任务调度等功能。以下是对 ucontext
的分章节详细介绍,并结合示例代码进行说明。
因此, ucontext
是实现协程的一种方式吗因为协程本质上就是一种用户级的线程, 既然是一种特殊的线程, 其上下文切换的实现方式应该和线程的上下文切换类似, 也需要保存必要的寄存器、程序计数器、栈等信息。故ucontext
是实现协程库的的一种手段, 例如云风的coroutine
库就是基于ucontext
实现的: https://github.com/cloudwu/coroutine/
ucontext
提供了一种在用户空间管理执行上下文的方式,避免了内核级线程切换的开销。它的核心功能包括:
- 保存当前执行上下文。
- 恢复之前保存的上下文。
- 创建新的执行上下文。
- 在上下文之间切换。
ucontext
的主要数据结构是 ucontext_t
,它保存了上下文的所有信息。
2. 核心数据结构:ucontext_t
ucontext_t
是 ucontext
的核心数据结构,定义如下:
1 | typedef struct ucontext { |
uc_link
: 当前上下文结束时,切换到哪个上下文。如果为NULL
,则程序终止。uc_sigmask
: 上下文中的信号掩码,用于控制信号处理。uc_stack
: 当前上下文使用的栈信息。uc_mcontext
: 保存机器相关的上下文信息(如寄存器状态)。
3. 核心函数
3.1 getcontext
:保存当前上下文
1 | int getcontext(ucontext_t *ucp); |
- 功能: 将当前执行上下文保存到
ucp
指向的ucontext_t
结构体中。 - 返回值: 成功返回
0
,失败返回-1
。 - 用途: 通常用于保存当前状态,以便后续恢复。
3.2 setcontext
:恢复上下文
1 | int setcontext(const ucontext_t *ucp); |
- 功能: 从
ucp
指向的ucontext_t
结构体恢复上下文。 - 返回值: 如果成功,不会返回;如果失败,返回
-1
。 - 用途: 用于跳转到之前保存的上下文。
注意:
setcontext
类似fork
, 调用后会更改当前的执行流, 但区别是fork
后会创建一个执行流(新的进程), 而setcontext
不会创建新的进程, 而是直接切换到之前的上下文。
3.3 makecontext
:创建新上下文
1 | void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...); |
- 功能: 为
ucp
设置一个新的上下文,指定入口函数func
和参数。 - 参数:
ucp
: 需要初始化的上下文。func
: 新上下文的入口函数。argc
: 传递给func
的参数个数。...
: 具体的参数值。
- 用途: 用于创建一个新的执行上下文。
makecontext
只负责创建一个新的上下文,但不会立即进入或切换到该上下文
3.4 swapcontext
:切换上下文
1 | int swapcontext(ucontext_t *oucp, const ucontext_t *ucp); |
- 功能: 保存当前上下文到
oucp
,并切换到ucp
指定的上下文。 - 返回值: 如果成功,不会返回;如果失败,返回
-1
。 - 用途: 用于在两个上下文之间切换。
4. 示例代码
以下是一个完整的示例,展示如何使用 ucontext
实现上下文切换。
1 |
|
这段代码的执行流程如下:
初始化上下文:
- 使用
getcontext
初始化ctx[1]
。 - 设置
ctx[1]
的栈空间和大小。 - 使用
makecontext
将ctx[1]
的入口函数设置为func1
。
- 使用
第一次切换:
- 调用
swapcontext
切换到ctx[1]
,执行func1
。 func1
打印消息后,切换回ctx[0]
(即main
)。
- 调用
第二次切换:
main
再次调用swapcontext
,切换到ctx[1]
。func1
继续执行,打印剩余消息后结束。
输出结果
1 | $ ./a.out |