在整個框架下,系統將經過co_eventloop阻塞進入系統調用。這個很容易理解,一個進程不可能一直在空跑,因此在不須要系統信息的時候就可讓操做系統把本身掛起來。或者反過來講,當進程沒法運行的時候,它必定是在等待一個異步事件,此時就能夠在這個等待資源上把本身的運行權返回給操做系統。在libco中,這個等待在linux下就是經過epoll_wait系統調用完成。linux
在進入epoll等待的時候有一個問題:那就是一般等待都須要有一個超時時間,若是epoll_wati進入系統調用以後一直掛起,那麼協程中的超時就沒法執行。可是好在epoll_wait是有超時接口的。這樣libco就能夠在嘗試進入系統調用以前,計算出最先一個定時器到期的時間,從而保證本身最晚在這個時間點以前醒過來便可。
這個問題是全部異步框架都要考慮的問題,例如在redis的服務器中一樣須要在epoll_wait以前計算最先的定時器事件redis-5.0.4\src\ae.c
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
int processed = 0, numevents;redis
/* Nothing to do? return ASAP */
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;服務器
/* Note that we want call select() even if there are no
* file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready
* to fire. */
……
}
看了下libco的等待時間是寫死的1,那就是1毫秒都有可能從系統調用中返回,這個頻率其實仍是挺高的。對於CPU資源有些浪費,可是優勢就是實現簡單,不用每次都計算下次最先觸發的定時器時間。數據結構
在每一個線程初始化的時候,保證主線程使用協程棧的第一個槽位pCallStack[0]框架
wxlibco\libco-master\co_routine.cpp
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;oop
coctx_init( &self->ctx );佈局
env->pCallStack[ env->iCallStackSize++ ] = self;ui
stCoEpoll_t *ev = AllocEpoll();
SetEpoll( env,ev );
}
從實現上來看,每一個協程私有的被切出進程的堆棧上。因此,關鍵的信息是要找到切換目標協程的棧頂信息,而這個信息
struct coctx_t
{
#if defined(__i386__)
void *regs[ 8 ];
#else
void *regs[ 14 ];
#endif
size_t ss_size;
char *ss_sp;
};
進入該函數時,esp寄存器指向調用函數返回地址,esp+4爲第一個參數,esp+8存儲第二個參數
libco-master\coctx_swap.S
coctx_swap:
#if defined(__i386__)
leal 4(%esp), %eax //sp
movl 4(%esp), %esp
leal 32(%esp), %esp //parm a : ®s[7] + sizeof(void*) 因爲棧是從上向下增長,而數據結構按照定義順序從下到上佈局,因此這裏先跳到®s[7] + sizeof(void*) 處
pushl %eax //esp ->parm a
pushl %ebp
pushl %esi
pushl %edi
pushl %edx
pushl %ecx
pushl %ebx
pushl -4(%eax) 這個地方是把coctx_swap返回地址壓入棧頂
movl 4(%eax), %esp //parm b -> ®s[0]。因爲eax以前已經被指向第一個參數,此時+4指向第二個參數。
popl %eax //ret func addr
popl %ebx
popl %ecx
popl %edx
popl %edi
popl %esi
popl %ebp
popl %esp
pushl %eax //set ret func addr
xorl %eax, %eax
ret
man手冊中對於epoll_wait系統調用的說明
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
……
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
在每一個Event中能夠保存一個用戶自定義的epoll_data,有了這個指針,就能夠指向協程相關的信息。
返回到切換到這個過來的那個協程。
void co_yield_env( stCoRoutineEnv_t *env )
{
stCoRoutine_t *last = env->pCallStack[ env->iCallStackSize - 2 ];
stCoRoutine_t *curr = env->pCallStack[ env->iCallStackSize - 1 ];
env->iCallStackSize--;
co_swap( curr, last);
}
從代碼上看,若是協程處理函數返回,將會出現程序崩潰。在co_resume執行時會手動目標協程最爲關鍵的ESP和EIP指針,這個第一次運行時是手動設置的。
int coctx_make( coctx_t *ctx,coctx_pfn_t pfn,const void *s,const void *s1 )
{
//make room for coctx_param
char *sp = ctx->ss_sp + ctx->ss_size - sizeof(coctx_param_t);
sp = (char*)((unsigned long)sp & -16L);
coctx_param_t* param = (coctx_param_t*)sp ;
param->s1 = s;
param->s2 = s1;
memset(ctx->regs, 0, sizeof(ctx->regs));
ctx->regs[ kESP ] = (char*)(sp) - sizeof(void*);
ctx->regs[ kEIP ] = (char*)pfn;
return 0;
}
而這個設置的信息在coctx_swap時會從棧中把這個信息一次性消耗掉,因此這也意味着若是協程函數不調用co_resume將會崩潰。簡單試了下,居然沒有崩潰。看了下發如今協程上下文初始化的時候在外面封裝了一層接口,若是協程函數返回,則會替用戶的協程函數調用co_yield_env( env ),從而保證協程函數return以後不會崩潰。
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->cStart = 1;
}
env->pCallStack[ env->iCallStackSize++ ] = co;
co_swap( lpCurrRoutine, co );
}
static int CoRoutineFunc( stCoRoutine_t *co,void * )
{
if( co->pfn )
{
co->pfn( co->arg );
}
co->cEnd = 1;
stCoRoutineEnv_t *env = co->env;
co_yield_env( env );
return 0;}