简介#
ucontext.h
是GNU C库的一个头文件,主要用于用户态下的上下文切换。需要注意的是,由于makecontext
中设计的一些问题,该文件已经被标记为过时的[1]。如果需要类似的功能,可以看一下Boost提供的fcontext[2]。本文主要还是介绍一下ucontext的结构和使用。
结构体#
ucontext.h
有两个比较重要的结构体,分别是mcontext_t
和ucontext_t
,其中mcontext_t
中主要保存了上下文的各种寄存器信息,因此一般情况下不会修改mcontext_t
的信息。
ucontext_t
主要需要关注的字段如下
typedef struct ucontext_t {
struct ucontext_t *uc_link;
stack_t uc_stack;
mcontext_t uc_mcontext;
sigset_t uc_sigmask;
} ucontext_t;
uc_link
指向一个上下文,当当前上下文结束时,将返回执行该上下文。sigset_t
当上下文被激活时,被屏蔽的信号集合。stack_t
栈消息,具体结构如下所示。uc_mcontext
保存了上下文的各种寄存器信息。
typedef struct {
void *ss_sp;
int ss_flags;
size_t ss_size;
} stack_t;
ss_sp
栈空间的指针,指向当前栈所在的位置。ss_flags
栈空间的flags。ss_size
整个栈的大小,在makecontext
中会使用ss_size + ss_sp
然后对齐再减去对应的系统位数(32位减4[3],64位减8[4])。需要注意的是,getcontext
返回的ucp
中的uc_stack
只有赋值了ss_sp
,其他对象没有赋值[5]。
函数#
ucontext.h
提供了四个方法对上下文进行操作,分别是getcontext
、setcontext
、makecontext
、swapcontext
。
getcontext#
这个函数的签名为
int getcontext(ucontext_t *ucp);
函数的用法和说明也非常简单,将ucp
初始化并保存当前的上下文。
setcontext#
函数签名为
int setcontext(const ucontext_t *ucp);
函数的作用为切换当前的上下文为ucp
。成功执行后,setcontext
将不会返回,程序将执行ucp
所指向的上下文。在这个函数中,ucp
以下几种方式被创建。
- 如果由
getcontext
创建,那么程序表现为从getcontext
返回处开始执行; - 如果由
makecontext
创建,那么程序将执行makecontext
的传入函数,当函数执行结束后,程序将表现为执行setcontext
其ucp
参数为makecontext
的ucp
参数; - 如果
uc_link
指向为0,即空指针,那么当前上下文为主上下文,当返回时,线程将直接退出。
makecontext#
这些函数里面最有用的应该就是这一个了,通过使用这个函数对上下文进行处理,可以创建一个新的上下文。该函数的签名为
void makecontext(ucontext_t *ucp, (void *func)(), int argc, ...); // https://pubs.opengroup.org/onlinepubs/7908799/xsh/makecontext.html
extern void makecontext (ucontext_t *__ucp, void (*__func) (void), int __argc, ...) __THROW; // from my pc
其中需要注意的是第二个参数,由于C是一个强类型语言。但是由于无法判断一个函数参数的数量和类型,因此直接定义为空的。需要注意的是,在使用这个函数之前,需要对栈空间进行修改即uc_stack
字段。这一部分我的理解是因为makecontext
实现功能的方法就是修改栈,而如果当前栈和修改的栈为同一个栈,那么势必会造成未定义行为。
正确的使用方法如下所示:
#include <ucontext.h>
#include <stdio.h>
#include <string.h>
struct ucontext_t test;
char stack[102400];
int n = 0;
int testv() {
printf("Hello World!");
return 0;
}
int test4() {
getcontext(&test);
printf("Test\n");
if (n == 0) {
test.uc_stack.ss_sp = stack;
test.uc_stack.ss_size = 102400;
makecontext(&test, testv, 0);
n = 1;
setcontext(&test);
}
return 0;
}
int main() {
test4();
return 0;
}
swapcontext#
该函数的签名为
int swapcontext(ucontext_t *restrict oucp, const ucontext_t *restrict ucp);
函数的作用是载入上下文ucp
,将当前上下文保存到oucp
。