libco源码阅读(三):协程的创建和运行

    libco的协程创建和线程创建不同,线程创建后立刻可以运行,而对于协程而言,创建和运行是分开的,创建协程需要调用接口co_create,而运行协程则要调用接口co_resume。

1、协程创建:co_create

    1.1 获取线程环境:co_get_curr_thread_env

    1.2 创建线程环境和主协程:co_init_curr_thread_env

    1.3 创建协程:co_create_env

2、协程运行:co_resume


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(),这两个函数是实现协程的关键,将在下一篇博客中进行介绍。

猜你喜欢

转载自blog.csdn.net/MOU_IT/article/details/114683197