头文件
ucontext结构体定义:
typedef struct ucontext
{
unsigned long int uc_flags;
struct ucontext *uc_link;//后序上下文
__sigset_t uc_sigmask;// 信号屏蔽字掩码
stack_t uc_stack;// 上下文所使用的栈
mcontext_t uc_mcontext;// 保存的上下文的寄存器信息
long int uc_filler[5];
} ucontext_t;
//其中mcontext_t 定义如下
typedef struct
{
gregset_t __ctx(gregs);//所装载寄存器
fpregset_t __ctx(fpregs);//寄存器的类型
} mcontext_t;
//其中gregset_t 定义如下
typedef greg_t gregset_t[NGREG];//包括了所有的寄存器的信息
getcontext(ucontext_t *ucp):保存当前CPU上下文信息到指定ucontext_t 结构体;
setcontext(const ucontext_t *ucp):将上下文ucp恢复到CPU
makecontext(ucontext_t *ucp, void (*func)(), int argc, …):func是上下文的入口函数;argc是入口函数的参数个数,后面的…是具体的入口函数参数,该参数必须为整形值。这里就是将func的地址保存到寄存器中,把ucp上下文结构体下一条要执行的指令rip改变为func函数的地址。并且将其所运行的栈改为用户自定义的栈
swapcontext(ucontext_t *oucp, ucontext_t *ucp):将当前cpu中的上下文信息保存带oucp结构体变量中,然后将ucp中的结构体的上下文信息恢复到cpu中,这里可以理解为调用了两个函数,第一次是调用了getcontext(oucp)然后再调用setcontext(ucp)
#include
#define _XOPEN_SOURCE
#include
#undef _XOPEN_SOURCE
int fib_res;
ucontext_t main_ctx, fib_ctx;
char fib_stack[1024 * 32];
void fib() {
// (1)
int a0 = 0;
int a1 = 1;
while (1) {
fib_res = a0 + a1;
a0 = a1;
a1 = fib_res;
// send the result to outer env and hand over the right of control.
swapcontext(&fib_ctx, &main_ctx); // (b)
// (3)
}
}
int main(int argc, char **argv) {
// initialize fib_ctx with current context.
getcontext(&fib_ctx);
fib_ctx.uc_link = 0; // after fib() returns we exit the thread.
fib_ctx.uc_stack.ss_sp = fib_stack; // specific the stack for fib().
fib_ctx.uc_stack.ss_size = sizeof(fib_stack);
fib_ctx.uc_stack.ss_flags = 0;
makecontext(&fib_ctx, fib, 0); // modify fib_ctx to run fib() without arguments.
while (1) {
// pass the right of control to fib() by swap the context.
swapcontext(&main_ctx, &fib_ctx); // (a)
// (2)
printf("%d\n", fib_res);
if (fib_res > 100) {
break;
}
}
return 0;
}
代码中相关的调用我都加了注释,下面我来简单描述一下执行的过程:
首先标号 (a) 的调用会将控制权转交给 fib 函数同时保存状态,由于 fib_ctx 之前是由 makecontext 函数修改过的,所以这次跳转会跳转到标号 (1) 这个位置。
fib 计算好一次迭代的结果,通过标号 (b) 将控制权交回 main 函数,跳转到之前保存的状态,即标号 (2)。
如果 fib_res <= 100,标号 (a) 继续执行,由于标号 (b) 的调用保存了之前 fib 的状态,所以这次跳转到标号 (3) 的位置,fib 继续之前的状态执行。
至此,我们就在 C 中实现了这个简单的协程。
帮助理解:
下图是协程切换前的状态:

从协程 1 切换到协程 2 后的状态如下图所示:
