最近在看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 |