co_routine(第二部分 : 协程的创建)—— libco源码分析、学习笔记

 

博客一级目录

二级目录——libco源码分析/学习笔记

参考大牛GitHub注释

由于本源代码蛮长的,所以按照功能划分模块来分析,分为若干部分,详见二级目录↑

代码文件:co_routine.hco_routine.cppco_routine_inner.h

参考博客

二、协程(task)的创建

每当启动(resume)一个协程时,就将它的协程控制块stCoRoutine_t 结构指针保存在pCallStack的“栈顶”,然后“栈指针”iCallStackSize加1,最后切换context到待启动协程运行。当协程要让出(yield)CPU时,就将它的stCoRoutine_t从pCallStack 
弹出,“栈指针”iCallStackSize减1,然后切换context到当前栈顶的协程(原来被挂起的调用者)恢复执行。这个“压栈”和“弹栈”的过程我们在co_resume()和co_yield()函数中将会再次讲到那么这里有一个问题,libco程序的第一个协程呢,假如第一个协程yield时,CPU控制权让给谁呢?关于这个问题,我们首先要明白这“第一个”协程是什么。实际上,libco的第一个协程,即执行main函数的协程,是一个特殊的协程。这个协程又可以称作主协程,它负责协调其他协程的调度执行(后文我们会看到,还有网络 
I/O以及定时事件的驱动),它自己则永远不会yield,不会主动让出 。

 

struct stCoRoutine_t为协程环境变量类型(主要是物理环境,比如上下文)。保存着运行时协程运行环境所有信息。

(销毁见第三部分co_free函数)

struct stCoRoutine_t
{
	stCoRoutineEnv_t *env;  //全局协程管理结构,协程嵌套调用栈管理结构体
	pfn_co_routine_t pfn; //协程主函数
	void *arg; //协程主函数的参数
	coctx_t ctx; //协程上下文信息,包括寄存器、用户栈信息

	char cStart;  //主函数是否被运行过
	char cEnd;    //主函数是否运行结束过
	char cIsMain; //协程是否是主协程
	char cEnableSysHook;//允许hook标记
	char cIsShareStack; //本结构体成员stack_mem用户栈是否是从share栈中取下来的

	void *pvEnv;  

	//char sRunStack[ 1024 * 128 ];
	stStackMem_t* stack_mem;  //用户栈结构体


	//save satck buffer while confilct on same stack_buffer;
	char* stack_sp;  //下面三个成员都是用于对用户栈内容的备份,但是销毁env的时候不知道为什么没有销毁这个备份,推测之前有提前销毁备份的动作
	unsigned int save_size;//save_buffer中保存的数据大小
	char* save_buffer; //共享栈的时候,此指针指向临时保存栈内有效数据的内存空间

	stCoSpec_t aSpec[1024];//协程私有数据

};

代码段 小部件

struct stCoRoutineEnv_t是全局协程管理结构,主要用于控制协程嵌套。
/*
 * 该结构的作用是什么呢? - 我们知道, 非对称协程允许嵌套创建子协程, 为了记录这种嵌套创建的协程, 以便子协程退出
 * 时正确恢复到挂起点(挂起点位于父协程中), 我们就需要记录这种嵌套调用过程; 另外, 协程中的套接字向内核注册了事件,
 * 我们必须保存套接字和协程的对应关系, 以便该线程的eventloop中检测到套接字上事件发生时, 能够恢复该套接字对应的
 * 协程来处理事件.
 * */

struct stCoRoutineEnv_t
{
	stCoRoutine_t *pCallStack[ 128 ]; //调用栈
// 该线程内允许嵌套创建128个协程(即协程1内创建协程2, 协程2内创建协程3... 协程127内创建协程128.
// 该结构虽然是数组, 但将其作为栈来使用, 满足后进先出的特点)
	int iCallStackSize;               
// 该线程内嵌套创建的协程数量, 即pCallStack数组中元素的数量
	stCoEpoll_t *pEpoll;              
// epoll抽象体,管理所有事件。该线程内的epoll实例(套接字通过该结构内的epoll句柄向内核注册事件), 也用于该线程的事件循环eventloop中
	//for copy stack log lastco and nextco

	stCoRoutine_t* pending_co;    //在co_swap函数中这临时保存将要占用share stack条目的协程是哪一个。
	stCoRoutine_t* occupy_co;    //在co_swap函数中这临时保存当前的share stack条目正在被哪一个协程占用
};

co_create_env()函数负责struct stCoRoutine_t的空间申请以及初始化。

参数1就是全局协程管理结构,参数2:内部有stShareStack_t共享栈底层类型变量。参数3和参数4是协程入口函数和参数。

stStackMem_t是普通栈底层类型。包含栈空间指针,栈帧,栈size,使用者。

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 ) //拷贝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 ) //如果size大于等于2的12次方(1024*4),size就向上对齐
	{
		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; //stCoRoutineEnv_t
	lp->pfn = pfn; //入口函数
	lp->arg = arg; //入口函数参数

	stStackMem_t* stack_mem = NULL; //stStackMem_t包含栈空间指针,栈帧,栈size,使用者。
	if( at.share_stack )//若共享栈存在,则stack_mem从共享栈中取,否则直接向操作系统申请。
	{
		stack_mem = co_get_stackmem( at.share_stack);
                        //co_get_stackmem()函数是从共享栈阵列中取出下一个共享栈 
		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;//协程栈空闲大小
	lp->cStart = 0;//=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;
}

int co_create( stCoRoutine_t **ppco,const stCoRoutineAttr_t *attr,pfn_co_routine_t pfn,void *arg );

创建一个协程,申请、初始化协程控制块;并负责协程环境的初始化。

参数1:协程控制块(结构体)指针的指针。

参数2:内存池(share stack),可以写NULL表示不使用内存池。

参数3:函数指针:typedef void *(*pfn_co_routine_t)( void * );

参数4:参数3表示的函数的参数。void*类型。

返回值:始终返回0。

注意:主协程不需要create(由操作系统创建),也不需要resume(由操作系统赋予执行权),更不需要yield(return或exit)。一旦create了其它协程,它就会被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 );        //申请空间并初始化stCoRoutine_t协程环境
	*ppco = co;
	return 0;
}

上面代码提到了co_init_curr_thread_env(),那我们按图索骥看一下它的实现和功能。

void co_init_curr_thread_env(),初始化当前线程的主协程。嵌套协程的栈等信息。

void co_init_curr_thread_env()
{
	pid_t pid = GetPid();	
	g_arrCoEnvPerThread[ pid ] = (stCoRoutineEnv_t*)calloc( 1,sizeof(stCoRoutineEnv_t) );
            //每个线程的全局协程管理结构都保存在g_arrCoEnvPerThread[]里,线程pid就是编号。
	stCoRoutineEnv_t *env = g_arrCoEnvPerThread[ pid ];

	env->iCallStackSize = 0;  //初始化的之前嵌套协程数量为0
	struct stCoRoutine_t *self = co_create_env( env, NULL, NULL,NULL );
                                              //env结构体,不使用共享栈,入口函数/参数为空
	self->cIsMain = 1;

	env->pending_co = NULL;
	env->occupy_co = NULL;

	coctx_init( &self->ctx );
//之前pCallStack是空的,self是当前协程  iCallStackSize为嵌套协程栈内元素数量,也是栈顶指针(空递增)
	env->pCallStack[ env->iCallStackSize++ ] = self;//将主协程加入调用栈首部

	stCoEpoll_t *ev = AllocEpoll();//申请epoll结构
	SetEpoll( env,ev );//与当前env绑定
}

 

void co_resume(stCoRoutine_t co)函数,执行一个协程(交换执行权限,执行co,阻塞当前协程)。如果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->pfn,
// 也就是co_create里设置的。
		co->cStart = 1;//标记这次运行
	}
	env->pCallStack[ env->iCallStackSize++ ] = co; //将co入栈
	co_swap( lpCurrRoutine, co );//切换协程,执行协程co。


}

co_swap函数见第三部分,注意区分co_swap和coctx_swap,前者在c++语言层面,后者是汇编层面,

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值