C/C++协程学习笔记丨C/C++实现协程及原理分析视频( 三 )


static stCoRoutineEnv_t* g_arrCoEnvPerThread[204800]
stCoRoutineEnv_t *co_get_curr_thread_env()
{
return g_arrCoEnvPerThread[ GetPid() ];
}
初始化 stCoRoutineEnv_t 时主要完成以下几步:

  1. 为 stCoRoutineEnv_t 申请空间并且进行初始化 , 设置协程调度器 pEpoll 。
  2. 创建一个空的 coroutine , 初始化其上下文环境( 有关 coctx 在后文详细介绍 ) , 将其加入到该线程的协程环境中进行管理 , 并且设置其为 main coroutine 。 这个 main coroutine 用来运行该线程主逻辑 。
当初始化完成协程环境之后 , 调用函数 co_create_env 来创建具体的协程 , 该函数初始化一个协程结构 stCoRoutine_t , 设置该结构中的各项字段 , 例如运行的函数 pfn , 运行时的栈地址等等 。 需要说明的就是 , 如果使用了非共享栈模式 , 则需要为该协程单独申请栈空间 , 否则从共享栈中申请空间 。 栈空间表示如下:
struct stStackMem_t
{
stCoRoutine_t* occupy_co; // 使用该栈的协程
int stack_size; // 栈大小
char* stack_bp; // 栈的指针 , 栈从高地址向低地址增长
char* stack_buffer; // 栈底
};
使用 co_create 创建完一个协程之后 , 将调用 co_resume 来将该协程激活运行:
void co_resume( stCoRoutine_t *co )
{
stCoRoutineEnv_t *env = co->env;
// 获取当前正在运行的协程的结构
stCoRoutine_t *lpCurrRoutine = env->pCallStack[ env->iCallStackSize - 1 ];
if( !co->cStart )
{
// 为将要运行的 co 布置上下文环境
coctx_make(
co->cStart = 1;
}
env->pCallStack[ env->iCallStackSize++ ] = co; // 设置co为运行的线程
co_swap( lpCurrRoutine, co );
}
函数 co_swap 的作用类似于 Unix 提供的函数 swapcontext:将当前正在运行的 coroutine 的上下文以及状态保存到结构 lpCurrRoutine 中 , 并且将 co 设置成为要运行的协程 , 从而实现协程的切换 。 co_swap 具体完成三项工作:
  1. 记录当前协程 curr 的运行栈的栈顶指针 , 通过 char c; curr_stack_sp= --tt-darkmode-color: #999999;">对应于 co_resume 函数 , 协程主动让出执行权则调用 co_yield 函数 。 co_yield 函数调用了 co_yield_env , 将当前协程与当前线程中记录的其他协程进行切换:
    void co_yield_env( stCoRoutineEnv_t *env )
    {
    stCoRoutine_t *last = env->pCallStack[ env->iCallStackSize - 2 ];
    stCoRoutine_t *curr = env->pCallStack[ env->iCallStackSize - 1 ];
    env->iCallStackSize--;
    co_swap( curr, last);
    }
    前面我们已经提到过 , pCallStack 栈顶所指向的即为当前正在运行的协程所对应的结构 , 因此该函数将 curr 取出来 , 并将当前正运行的协程上下文保存到该结构上 , 并切换到协程 last 上执行 。 接下来我们以 32-bit 的系统为例来分析 libco 是如何实现协程运行环境的切换的 。
    协程上下文的创建和切换libco 使用结构 struct coctx_t 来表示一个协程的上下文环境:
    struct coctx_t
    {
    #if defined(__i386__)
    void *regs[ 8 ];
    #else
    void *regs[ 14 ];
    #endif
    size_t ss_size;
    char *ss_sp;
    };
    可以看到 , 在 i386 的架构下 , 需要保存 8 个寄存器信息 , 以及栈指针和栈大小 , 究竟这 8 个寄存器如何保存 , 又是如何使用 , 需要配合后续的 coctx_swap 来理解 。 我们首先来回顾一下 Unix-like 系统的 stack frame layout , 如果不能理解这个 , 那么剩下的内容就不必看了 。