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。
}