libco的协程创建和线程创建不同,线程创建后立刻可以运行,而对于协程而言,创建和运行是分开的,创建协程需要调用接口co_create,而运行协程则要调用接口co_resume。
1.1 获取线程环境:co_get_curr_thread_env
1.2 创建线程环境和主协程:co_init_curr_thread_env
1、协程创建:co_create
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;
}
- ppco是协程的主体结构,存储着一个协程所有的信息;
- attr其实和线程一样,是我们希望创建的协程的一些属性,不过libco中这个参数简单一点,只是标记了栈的大小和是否使用共享栈,传入为NULL时表示不使用共享栈;
- pfn 是我们希望协程执行的函数,当然实际执行的是一个封装后的函数;
- arg是传入函数的参数;
1.1 获取线程环境:co_get_curr_thread_env
这个函数用于返回线程的私有变量。
static stCoRoutineEnv_t* g_arrCoEnvPerThread[ 204800 ] = { 0 };
stCoRoutineEnv_t *co_get_curr_thread_env()
{
return g_arrCoEnvPerThread[ GetPid() ];
}
1.2 创建线程环境和主协程:co_init_curr_thread_env
如果当前协程是线程的第一个协程时,这个函数用于初始化线程的环境变量,即初始化stCoRoutineEnv_t这个结构,并且创建一个主协程,主协程是线程环境栈中的第一个协程,该协程不执行任何函数。
void co_init_curr_thread_env()
{
pid_t pid = GetPid();
g_arrCoEnvPerThread[ pid ] = (stCoRoutineEnv_t*)calloc( 1,sizeof(stCoRoutineEnv_t) );
stCoRoutineEnv_t *env = g_arrCoEnvPerThread[ pid ];
env->iCallStackSize = 0;
struct stCoRoutine_t *self = co_create_env( env, NULL, NULL,NULL ); // 创建主协程实体,主协程不执行任何函数
self->cIsMain = 1; // 一个线程调用这个函数的肯定是主协程
env->pending_co = NULL;
env->ocupy_co = NULL;
coctx_init( &self->ctx );
env->pCallStack[ env->iCallStackSize++ ] = self; // 主协程放入线程独有环境中
stCoEpoll_t *ev = AllocEpoll();
SetEpoll( env,ev );
}
int coctx_init( coctx_t *ctx )
{
memset( ctx,0,sizeof(*ctx));
return 0;
}
/* 给线程环境分配一个epoll */
stCoEpoll_t *AllocEpoll()
{
stCoEpoll_t *ctx = (stCoEpoll_t*)calloc( 1,sizeof(stCoEpoll_t) );
ctx->iEpollFd = co_epoll_create( stCoEpoll_t::_EPOLL_SIZE );
ctx->pTimeout = AllocTimeout( 60 * 1000 );
ctx->pstActiveList = (stTimeoutItemLink_t*)calloc( 1,sizeof(stTimeoutItemLink_t) );
ctx->pstTimeoutList = (stTimeoutItemLink_t*)calloc( 1,sizeof(stTimeoutItemLink_t) );
return ctx;
}
void SetEpoll( stCoRoutineEnv_t *env,stCoEpoll_t *ev )
{
env->pEpoll = ev;
}
1.3 创建协程:co_create_env
这个函数用于创建协程的stCoRoutine_t
类型的结构,即创建一个协程实体。
struct stCoRoutine_t *co_create_env( stCoRoutineEnv_t * env, const stCoRoutineAttr_t* attr,
pfn_co_routine_t pfn,void *arg )
{
stCoRoutineAttr_t at;
if( attr )
{
memcpy( &at,attr,sizeof(at) );
}
if( at.stack_size <= 0 )
{
at.stack_size = 128 * 1024;
}
else if( at.stack_size > 1024 * 1024 * 8 )
{
at.stack_size = 1024 * 1024 * 8;
}
if( at.stack_size & 0xFFF ) // 4KB对齐,也就是说如果对stacksize取余不为零的时候对齐为4KB
{
at.stack_size &= ~0xFFF;
at.stack_size += 0x1000;
}
stCoRoutine_t *lp = (stCoRoutine_t*)malloc( sizeof(stCoRoutine_t) ); // 为协程实体分配空间
memset( lp,0,(long)(sizeof(stCoRoutine_t)));
lp->env = env;
lp->pfn = pfn;
lp->arg = arg;
stStackMem_t* stack_mem = NULL;
if( at.share_stack ) // 共享栈模式 栈需要自己指定
{
stack_mem = co_get_stackmem( at.share_stack);
at.stack_size = at.share_stack->stack_size;
}
else // 每个协程有一个私有的栈
{
stack_mem = co_alloc_stackmem(at.stack_size);
}
lp->stack_mem = stack_mem;
lp->ctx.ss_sp = stack_mem->stack_buffer; // 这个协程栈的基址,保存在上下文环境中
lp->ctx.ss_size = at.stack_size; // 栈未使用大小,与前者相加为esp指针,见coctx_make解释
lp->cStart = 0;
lp->cEnd = 0;
lp->cIsMain = 0;
lp->cEnableSysHook = 0;
lp->cIsShareStack = at.share_stack != NULL;
lp->save_size = 0;
lp->save_buffer = NULL;
return lp;
}
/* 获取共享栈 */
static stStackMem_t* co_get_stackmem(stShareStack_t* share_stack)
{
if (!share_stack)
{
return NULL;
}
int idx = share_stack->alloc_idx % share_stack->count;
share_stack->alloc_idx++;
return share_stack->stack_array[idx];
}
/* 根据栈大小分配一个私有栈 */
stStackMem_t* co_alloc_stackmem(unsigned int stack_size)
{
stStackMem_t* stack_mem = (stStackMem_t*)malloc(sizeof(stStackMem_t));
stack_mem->ocupy_co= NULL;
stack_mem->stack_size = stack_size;
stack_mem->stack_buffer = (char*)malloc(stack_size);
stack_mem->stack_bp = stack_mem->stack_buffer + stack_size;
return stack_mem;
}
- env: 线程的环境变量
- attr :协程属性信息
- pfn :协程所要执行函数指针
- arg :函数参数
2、协程运行:co_resume
co_resume函数调度一个协程开始运行。
void co_resume( stCoRoutine_t *co )
{
stCoRoutineEnv_t *env = co->env;
/* 获取线程环境中的栈顶协程实体 */
stCoRoutine_t *lpCurrRoutine = env->pCallStack[ env->iCallStackSize - 1 ];
if( !co->cStart ) // 如果协程没有执行过resume
{
coctx_make( &co->ctx,(coctx_pfn_t)CoRoutineFunc,co,0 );
co->cStart = 1;
}
env->pCallStack[ env->iCallStackSize++ ] = co; // 把当前协程压入线程环境的栈中
co_swap( lpCurrRoutine, co ); // 进行两个协程的上下文切换
}
调用co_resume时需要用到两个函数,分别是coctx_make()和co_swap(),这两个函数是实现协程的关键,将在下一篇博客中进行介绍。