微信协程库libco简单分析

1、进程的等待以及对CPU资源的释放

在整个框架下,系统将经过co_eventloop阻塞进入系统调用。这个很容易理解,一个进程不可能一直在空跑,因此在不须要系统信息的时候就可让操做系统把本身挂起来。或者反过来讲,当进程没法运行的时候,它必定是在等待一个异步事件,此时就能够在这个等待资源上把本身的运行权返回给操做系统。在libco中,这个等待在linux下就是经过epoll_wait系统调用完成。linux

2、超时的问题

在进入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资源有些浪费,可是优势就是实现简单,不用每次都计算下次最先触发的定时器时间。数据结构

3、主线程的coroutine如何表示

在每一个线程初始化的时候,保证主线程使用协程栈的第一个槽位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 );
}

4、被切出线程的返回地址及栈顶如何保存

从实现上来看,每一个协程私有的被切出进程的堆栈上。因此,关键的信息是要找到切换目标协程的栈顶信息,而这个信息

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 : &regs[7] + sizeof(void*) 因为栈是从上向下增长,而数据结构按照定义顺序从下到上布局,因此这里先跳到&regs[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 -> &regs[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

5、epoll_wait返回和协程的对应关系

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,有了这个指针,就能够指向协程相关的信息。

6、co_yield返回到哪里

返回到切换到这个过来的那个协程。

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);
}

7、若是协程函数返回怎么办

从代码上看,若是协程处理函数返回,将会出现程序崩溃。在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;}

相关文章
相关标签/搜索