最近由于工作需要用到协程,这里将学习记录进行总结。
vi ucontext_simple.cpp,在文件中添加如下代码:
#include <stdio.h>
#include <ucontext.h>
#include <unistd.h>
int main(int argc, const char *argv[]){
ucontext_t context;
getcontext(&context);
puts("Hello world");
sleep(1);
setcontext(&context);
return 0;
}
执行g++ -g -o ucontext_simple ucontext_simple.cpp
然后执行
[root@localhost context_study]# ./ucontext_simple
Hello world
Hello world
Hello world
Hello world
Hello world
Hello world
......
发现一直在不停的输出Hello world,代码中没有for循环之类的代码,为什么还能不停的输出Hello world呢?太神奇了。
原来程序通过getcontext先保存了一个上下文,然后输出"Hello world",再通过setcontext恢复到getcontext的地方,重新执行代码,所以导致程序不断的输出”Hello world“。那么为什么setcontext和getcontext可以实现这样的功能呢?
查看ucontext.h,可以看到ucontext_t结构体如下所示:
/* Userlevel context. */
typedef struct ucontext
{
unsigned long int uc_flags;
struct ucontext *uc_link;//当前上下文(如使用makecontext创建的上下文)运行终止时系统会恢复uc_link
指向的上下文
stack_t uc_stack;//该上下文中使用的栈
mcontext_t uc_mcontext;//保存的上下文的特定机器表示,包括调用线程的特定寄存器等
__sigset_t uc_sigmask;//该上下文中的阻塞信号集合
struct _libc_fpstate __fpregs_mem;
} ucontext_t;
/* Get user context and store it in variable pointed to by UCP. */
//初始化ucp结构体,将当前的上下文保存到ucp中
extern int getcontext (ucontext_t *__ucp) __THROWNL;
/* Set user context from information of variable pointed to by UCP. */
//设置当前的上下文为ucp,setcontext的上下文ucp应该通过getcontext或者makecontext取得,如果调用成功则不返回。如果上下文是通过调用getcontext()取得,程序会继续执行这个调用。如果上下文是通过调用makecontext取得,程序会调用makecontext函数的第二个参数指向的函数,如果func函数返回,则恢复makecontext第一个参数指向的上下文context_t中指向的uc_link.如果uc_link为NULL,则线程退出。
extern int setcontext (const ucontext_t *__ucp) __THROWNL;
/* Save current context in context variable pointed to by OUCP and set
context from variable pointed to by UCP. */
//保存当前上下文到oucp结构体中,然后激活ucp上下文。如果执行成功,getcontext返回0,setcontext和swapcontext不返回;如果执行失败,getcontext,setcontext,swapcontext返回-1,并设置对于的errno。
extern int swapcontext (ucontext_t *__restrict __oucp,
const ucontext_t *__restrict __ucp) __THROWNL;
/* Manipulate user context UCP to continue with calling functions FUNC
and the ARGC-1 parameters following ARGC when the context is used
the next time in `setcontext' or `swapcontext'.
We cannot say anything about the parameters FUNC takes; `void'
is as good as any other choice. */
//makecontext修改通过getcontext取得的上下文ucp(这意味着调用makecontext前必须先调用getcontext)。然后给该上下文指定一个栈空间ucp->stack,设置后继的上下文ucp->uc_link.
当上下文通过setcontext或者swapcontext激活后,执行func函数,argc为func的参数个数,后面是func的参数序列。当func执行返回后,继承的上下文被激活,如果继承上下文为NULL时,线程退出。
extern void makecontext (ucontext_t *__ucp, void (*__func) (void),
int __argc, ...) __THROW;
简单来说getcontext
获取当前上下文,setcontext
设置当前上下文,swapcontext
切换上下文,makecontext
创建一个新的上下文。
这种设计的关键在于实现主函数到一个协程的切换,然后从协程返回主函数。这样无论是一个协程还是多个协程都能够完成与主函数的切换,从而实现协程的调度。