协程
- 协程:编程语言层面的概念,又叫做用户态线程,由用户进行切换,因为不用陷入内核态,所以切换效率更高。
- 协程的分类:
- 非对称式协程:协程主动挂起时,只能返回最初调用它的协程继续执行。在非对称协程中,存在着调用与被调用的关系。比如腾讯的libco
- 对称协程:运行的协程和挂起的协程之间没有关系。
与协程相关的四大系统函数
在Linux上,与协程相关的四大函数分别是:getcontext、setcontext、makecontext、swapcontext
1.信号栈
- 信号栈定义如下所示:
/* Structure describing a signal stack. */
typedef struct
{
void *ss_sp;
int ss_flags;
size_t ss_size;
} stack_t;
2.上下文
- 与平台相关的上下文数据结构如下所示:x86_x64架构
typedef long long int greg_t;
#define __NGREG 23
/* Container for all general registers. */
typedef greg_t gregset_t[__NGREG];
/* Context to describe whole processor state. */
typedef struct
{
// long long int[23],保存23个寄存器的信息
gregset_t __ctx(gregs);
/* Note that fpregs is a pointer. */
fpregset_t __ctx(fpregs);
__extension__ unsigned long long __reserved1 [8];
} mcontext_t;
3.函数介绍
以下四个函数都定义在ucontext.h头文件中
- getcontext:get the user context,将上下文信息保存在传入的实参中,主要做的事情是将相关的寄存器值保存。保存现场
int getcontext(ucontext_t *ucp);
// Userlevel context
typedef struct ucontext_t {
struct ucontext_t *uc_link; // 指向将要恢复的上下文(当前上下文执行结束后需要执行的上下文)
sigset_t uc_sigmask;// 当前上下文阻塞(屏蔽)的信号集
stack_t uc_stack; // 当前上下文执行需要使用的信号栈
mcontext_t uc_mcontext; // 保存的上下文信息,包括寄存器(比如说pc值)等
struct _libc_fpstate __fpregs_mem; // FPU寄存器信息
__extension__ unsigned long long int __ssp[4];
} ucontext_t;
函数形参:
ucp:传出参数,保存上下文信息
函数返回值:
成功则返回0失败返回-1
- setcontext:set the user context,这个函数根据传入的实参恢复上下文。恢复现场
int setcontext(const ucontext_t *ucp);
- makecontext:manipulate user context,修改第一参数指向的上下文信息,这个上下文信息可以由getcontext函数获得。
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);
函数形参:
argc:传递给func的参数的个数
- swapcontext:manipulate user context,保存第一参数指向的上下文信息,恢复第二参数指向的上下文信息。恢复现场
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);
4.四大函数的基本使用
- 示例如下:来自官方文档
#include <ucontext.h>
#include <stdio.h>
#include <stdlib.h>
static ucontext_t uctx_main, uctx_func1, uctx_func2;
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
static void
func1(void)
{
printf("func1: started\n");
printf("func1: swapcontext(&uctx_func1, &uctx_func2)\n");
// 切换至上下文uctx_func2
if (swapcontext(&uctx_func1, &uctx_func2) == -1)
handle_error("swapcontext");
printf("func1: returning\n");
}
static void
func2(void)
{
printf("func2: started\n");
printf("func2: swapcontext(&uctx_func2, &uctx_func1)\n");
// 切换至上下文uctx_func1
if (swapcontext(&uctx_func2, &uctx_func1) == -1)
handle_error("swapcontext");
printf("func2: returning\n");
}
int
main(int argc, char *argv[])
{
char func1_stack[16384];
char func2_stack[16384];
if (getcontext(&uctx_func1) == -1)
handle_error("getcontext");
uctx_func1.uc_stack.ss_sp = func1_stack;
uctx_func1.uc_stack.ss_size = sizeof(func1_stack);
uctx_func1.uc_link = &uctx_main;
makecontext(&uctx_func1, func1, 0);
if (getcontext(&uctx_func2) == -1)
handle_error("getcontext");
uctx_func2.uc_stack.ss_sp = func2_stack;
uctx_func2.uc_stack.ss_size = sizeof(func2_stack);
/* Successor context is f1(), unless argc > 1 */
uctx_func2.uc_link = (argc > 1) ? NULL : &uctx_func1;
makecontext(&uctx_func2, func2, 0);
printf("main: swapcontext(&uctx_main, &uctx_func2)\n");
// 切换至上下文uctx_func2
if (swapcontext(&uctx_main, &uctx_func2) == -1)
handle_error("swapcontext");
printf("main: exiting\n");
exit(EXIT_SUCCESS);
}
//main: swapcontext(&uctx_main, &uctx_func2)
//func2: started
//func2: swapcontext(&uctx_func2, &uctx_func1)
//func1: started
//func1: swapcontext(&uctx_func1, &uctx_func2)
//func2: returning
//func1: returning
//main: exiting
5.原理剖析
一些开源协程库分析
1.云风的协程库
地址:https://github.com/cloudwu/coroutine/tree/master,这是一个非对称协程库
- 定义协程的状态有以下四种:
#define COROUTINE_DEAD 0
#define COROUTINE_READY 1 // 新创建的协程的状态为这个
#define COROUTINE_RUNNING 2
#define COROUTINE_SUSPEND 3
- 核心成员
// 管理一个线程中的客户端创建的各个协程,包括统计数量、记录运行协程的id等
struct schedule {
char stack[STACK_SIZE]; // 一个线程中的各个协程运行所需要的公用的栈空间
ucontext_t main; // 用于保存上下文信息
int nco; // 使用coroutine_new接口创建的协程的数量
int cap; // 容纳协程个数的容量
int running; // 记录正在运行的协程的id
struct coroutine **co; // 维护着一个动态分配的协程数组
};
// 协程信息
struct coroutine {
coroutine_func func; // 协程执行体
void *ud; // 协程执行的函数参数
ucontext_t ctx; // 用于保存上下文信息
struct schedule * sch; // 当前协程由谁维护
ptrdiff_t cap; // 堆栈的容量
ptrdiff_t size; // 用于表示堆栈的大小
int status; // 协程的状态
char *stack; // 保存堆栈信息的起始位置,当协程主动挂起时需要将堆栈信息保存到这里,以便再次运行时使用。
};
- 核心接口:
- coroutine_open:初始化一个schedule,这个时候schedule内部的running字段的值为-1,表示没有一个客户端创建的协程在运行

- coroutine_new:新建协程,执行一些初始化动作。这个时候新创建的协程的状态为COROUTINE_READY
- coroutine_resume:要么启动新创建协程的运行,此时需要进行context的初始化,然后切换至该上下文运行;要么将处于挂起态的协程恢复执行,此时需要从stack字段中恢复堆栈信息
- coroutine_yield:主动将协程挂起,切换至另一个上下文运行。在切换至另一个上下文运行之前需要先将当前上下文信息保存到coroutine结构体的stack字段中。
- _save_stack:协程挂起时调用这个函数,将当前协程的上下文信息从schedule的栈中拷贝至coroutine的stack字段中
- coroutine_open:初始化一个schedule,这个时候schedule内部的running字段的值为-1,表示没有一个客户端创建的协程在运行