libco源码笔记(2)主要结构与函数

在之前的文章libco源码笔记(1)协程与上下文切换中,我们介绍了协程的基本概念以及libco中的上下文切换核心代码。本文结合一个示例,介绍libco提供的几个重要函数接口。建议配合我自己的注释版本阅读。

libco主要结构体

首先我们介绍libco中的三个核心结构体,下图1描述了三者的关系。

coctx_t

保存协程切换时所需的上下文信息,详尽的说明请参考libco源码笔记(1)协程与上下文切换,此处不再赘述。

stCoRoutine_t

协程主要结构体,包含单个协程的全部信息,如协程启停状态,执行函数,上下文信息,共享栈信息等。

stCoRoutineEnv_t

static __thread stCoRoutineEnv_t* gCoEnvPerThread = NULL;   //协程运行环境 __thread:线程私有

线程私有全局静态变量,包含全局协程环境信息,如协程调用栈,epoll句柄等。其中pCallStack为当前线程中的协程调用栈,由于libco为非对称协程

图1. libco核心结构 图1. libco核心结构

libco主要接口函数

/*  协程创建接口
 *  @param
 *  co          :协程主结构体二级指针
 *  attr        :协程可配置属性, 包括栈大小、共享栈地址
 *  pfn         :协程调用函数
 *  arg         :协程调用函数参数
 *  @return     :0
 */
int co_create( stCoRoutine_t **ppco,const stCoRoutineAttr_t *attr,pfn_co_routine_t pfn,void *arg )
{
    if( !co_get_curr_thread_env() ) 
    {
        co_init_curr_thread_env();      //初始化本线程环境,主协程才会调用
    }
    stCoRoutine_t *co = co_create_env( co_get_curr_thread_env(), attr, pfn,arg );   //创建协程运行环境, 初始化协程数据
    *ppco = co;
    return 0;
}

co_create主要负责两件事情。首先,在当前线程没有初始化运行环境stCoRoutineEnv_t时,对其进行初始化,包括初始化协程调用栈,创建主协程并压栈等。其次,根据传入的配置参数attr创建协程,分配私有栈(设置共享栈)并返回句柄co。

/*  协程切回接口
 *  @param
 *  co          :协程主结构体指针
 */
void co_resume( stCoRoutine_t *co )
{
    stCoRoutineEnv_t *env = co->env;
    stCoRoutine_t *lpCurrRoutine = env->pCallStack[ env->iCallStackSize - 1 ];  //当前正在运行的协程
    if( !co->cStart )                                                           //第一次进入
    {
        coctx_make( &co->ctx,(coctx_pfn_t)CoRoutineFunc,co,0 );                 //在co->ctx中保存上下文(当前寄存器)
        co->cStart = 1;                                                         //标记为已开始
    }
    env->pCallStack[ env->iCallStackSize++ ] = co;                              //压入协程调用栈
    co_swap( lpCurrRoutine, co );                                               //切换
}

co_resume负责切换至某一协程。在co尚未启动时,通过coctx_make初始化协程栈,然后将协程压栈,并与当前协程进行上下文切换。

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);
}
/* 当前协程切出接口
 */
void co_yield_ct()
{
    co_yield_env( co_get_curr_thread_env() );
}
/*  协程切出接口
 *  @param
 *  co          :协程主结构体指针
 */
void co_yield( stCoRoutine_t *co )
{
    co_yield_env( co->env );
}

co_yield系列函数负责让当前协程让出CPU,将其出栈,并与栈上的前一个协程进行上下文切换。

示例

//example_test.cpp
#include <stdio.h>
#include <stdlib.h>
#include "co_routine.h"

void* f(void* args) {
    while (1) {
        printf("f\n");
        co_yield_ct();
    }
    return NULL;
}
void* g(void* args) {
    while (1) {
        printf("g\n");
        co_yield_ct();
    }
    return NULL;
}
int main() {
    stCoRoutine_t* co_f;
    stCoRoutine_t* co_g;
    co_create(&co_f, NULL, f, NULL); 
    co_create(&co_g, NULL, g, NULL); 
    while(1) {
        co_resume(co_f);
        co_resume(co_g);
    }
    return 0;
}

使用上面提到的三个基本函数,我在这里写了一个小例子,程序创建了两个协程f和g,每个协程在打印自己的函数名后,让出CPU。主协程循环调用co_resume依次唤起两个协程,程序的运行输出为循环打印:

./example_test
f
g
f
...

最后

至此,我们了解了libco核心部分的相关函数与执行过程。感谢你的阅读。如果你有任何疑问和感想,或发现本文有任何错误,请一定让我知道。

参考

  1. libco源码分析,csdn
  2. libco分享,李方源