1. 程式人生 > >co_routine.cpp/.h/inner.h(上 : 協程的建立)—— libco原始碼分析、學習筆記

co_routine.cpp/.h/inner.h(上 : 協程的建立)—— libco原始碼分析、學習筆記

由於本原始碼蠻長的,所以按照功能劃分模組來分析。

一、協程(task)的建立

參考部落格

struct stCoRoutine_t為協程環境變數型別(主要是物理環境,比如上下文)。儲存著執行時協程執行環境所有資訊。

程式碼段 小部件

struct stCoRoutineEnv_t是struct stCoRoutine_t的成員之一,主要用於控制協程巢狀。 /* 協程環境事件型別(主要用於管理) - 每個執行緒有且僅有一個該型別的變數 ,  * 該結構的作用是什麼呢? - 我們知道, 非對稱協程允許巢狀建立子協程, 為了記錄這種巢狀建立的協程, 以便子協程退出  * 時正確恢復到掛起點(掛起點位於父協程中), 我們就需要記錄這種巢狀呼叫過程; 另外, 協程中的套接字向核心註冊了事件,  * 我們必須儲存套接字和協程的對應關係, 以便該執行緒的eventloop中檢測到套接字上事件發生時, 能夠恢復該套接字對應的  * 協程來處理事件.  * */

struct stCoRoutineEnv_t
{
	stCoRoutine_t *pCallStack[ 128 ]; 
// 該執行緒內允許巢狀建立128個協程(即協程1內建立協程2, 協程2內建立協程3... 協程127內建立協程128.
// 該結構雖然是陣列, 但將其作為棧來使用, 滿足後進先出的特點)
	int iCallStackSize;               
// 該執行緒內巢狀建立的協程數量, 即pCallStack陣列中元素的數量
	stCoEpoll_t *pEpoll;              
// 該執行緒內的epoll例項(套接字通過該結構內的epoll控制代碼向核心註冊事件), 也用於該執行緒的事件迴圈eventloop中
};

co_create_env()函式負責struct stCoRoutine_t的空間申請以及初始化。

引數1就是相應的結構體,引數2stCoRoutineEnv_t是共享棧封裝控制型別,內部有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;
}

co_create():第一個引數是協程環境變數(引用),第二個引數用於初始化stCoRoutine_t其型別是棧控制型別,定義在co_routine.h中;第三個引數和第四個引數分別是協程入口函式指標和它的引數

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(),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;//將self協程加入協程巢狀的棧

	stCoEpoll_t *ev = AllocEpoll();//申請epoll結構
	SetEpoll( env,ev );//與當前env繫結
}

void co_resume(stCoRoutine_t co)函式,執行一個協程(交換執行許可權,執行co,阻塞另一個),如果是第一次執行則會上下文初始化,而且如果co是主協程(第一個協程),則之前的co_init_curr_thread_env()函式已經將co入棧,這裡co_swap的時候兩個引數都是co自己,那麼co_swap函式的功能就變成了將當前的上下文(我們把當前執行緒看做主協程)也就是主協程(也就是co)的上下文儲存到引數一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。


}