libco浅读学习笔记

一、协程的优点

    在传统网络编程中,同步IO由于需要线程等待返回,无法最大使用CPU。而异步IO解决了性能问题,但是管理回调函数,IO返回时该事件的状态,使编程更加复杂,代码也不易阅读。协程的存在,让我们可以以同步方式编程,且最大发挥CPU的性能

二、libco协程实现

     协程切换:libco自己实现了切换函数上下文的函数(coctx_swap,通过修改存在CPU寄存器的栈指针以及函数,函数参数指针)

     Hook系统函数:通过hook系统函数,应用层依然可以同步调用send,recv, poll等系统函数,但是底层通过加入epoll,主动让出当前CPU,实现了异步IO的方式。

     epoll+时间轮:管理网络事件以及超时事件

     共享栈模式:节约系统内存,从而支持更多的连接,共享栈切换时需要保存栈内容

三、协程切换部分代码

struct stCoRoutineEnv_t/*每个进程一个协程管理类*/
{
	stCoRoutine_t *pCallStack[ 128 ];/*数组实现的栈,先进后出,运行协程压入,协程主动放弃pop出*/
	int iCallStackSize;
	stCoEpoll_t *pEpoll;/*每个进程申请一个epoll实例*/

	//使用共享栈模式使用,后续
	stCoRoutine_t* pending_co;
	stCoRoutine_t* occupy_co;
};
static stCoRoutineEnv_t* g_arrCoEnvPerThread[ 204800 ] = { 0 };/*存着所有进程的协程管理类,可通过Pid下标访问*/

协程类

struct stCoRoutine_t
{
	stCoRoutineEnv_t *env;
	pfn_co_routine_t pfn;
	void *arg;
	coctx_t ctx;

	char cStart;
	char cEnd;
	char cIsMain;
	char cEnableSysHook;
	char cIsShareStack;

	void *pvEnv;

	//char sRunStack[ 1024 * 128 ];
	stStackMem_t* stack_mem;


	//save satck buffer while confilct on same stack_buffer;
	char* stack_sp; 
	unsigned int save_size;
	char* save_buffer;

	stCoSpec_t aSpec[1024];

};

协程提供了三个函数

co_create、创建协程

co_resume、主动切换某个协程

co_yield_ct、协程主动让出CPU

int co_create( stCoRoutine_t **ppco,const stCoRoutineAttr_t *attr,pfn_co_routine_t pfn,void *arg )
{
	if( !co_get_curr_thread_env() ) 
	{       /*如果该进程第一次调用,初始化该进程的协程管理类,包括初始化epoll实例,并把当前主协程类压入pCallStack*/
		co_init_curr_thread_env();
	}
	stCoRoutine_t *co = co_create_env( co_get_curr_thread_env(), attr, pfn,arg );
	*ppco = co;
	return 0;
}

void co_resume( stCoRoutine_t *co )
{ac
	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 );
}

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

四、系统函数hook以及事件管理

以example_echosvr.cpp来看看怎么处理网络IO

	for(int k=0;k<proccnt;k++)
	{
                /*开启proccnt个进程*/
		pid_t pid = fork();
		if( pid > 0 )
		{
			continue;
		}
		else if( pid < 0 )
		{
			break;
		}
		for(int i=0;i<cnt;i++)/*每个进程开cnt个协程处理io事件*/
		{
			task_t * task = (task_t*)calloc( 1,sizeof(task_t) );
			task->fd = -1;
                  
			co_create( &(task->co),NULL,readwrite_routine,task );
			co_resume( task->co );

		}
		stCoRoutine_t *accept_co = NULL;
		co_create( &accept_co,NULL,accept_routine,0 );
		co_resume( accept_co );
                /*主协程将执行事件循环,主要处理epoll返回的事件以及时间超时*/
		co_eventloop( co_get_epoll_ct(),0,0 );

		exit(0);
	}

    accept_routine 函数调用co_accept后,调用co_poll函数,在空闲协程队列中取出协程,并将这个套接字的读写交给这个协程处理,切换至这个协程

readwrite_routine函数调用co_poll后,再调用read,write函数,处理完再放入空闲协程队列

co_poll最后调用的是co_poll_inner函数

int co_poll_inner( stCoEpoll_t *ctx,struct pollfd fds[], nfds_t nfds, int timeout, poll_pfn_t pollfunc)
{
	
	if( timeout > stTimeoutItem_t::eMaxTimeout )
	{
		timeout = stTimeoutItem_t::eMaxTimeout;
	}
	int epfd = ctx->iEpollFd;
	stCoRoutine_t* self = co_self();

	//1.struct change
	stPoll_t& arg = *((stPoll_t*)malloc(sizeof(stPoll_t)));
	memset( &arg,0,sizeof(arg) );

	arg.iEpollFd = epfd;
	arg.fds = (pollfd*)calloc(nfds, sizeof(pollfd));
	arg.nfds = nfds;

	stPollItem_t arr[2];
	if( nfds < sizeof(arr) / sizeof(arr[0]) && !self->cIsShareStack)
	{
		arg.pPollItems = arr;
	}	
	else
	{
		arg.pPollItems = (stPollItem_t*)malloc( nfds * sizeof( stPollItem_t ) );
	}
	memset( arg.pPollItems,0,nfds * sizeof(stPollItem_t) );
        
	arg.pfnProcess = OnPollProcessEvent;/*在epoll_wait返回调用的函数,实际上是切换到属于该套接字的协程*/
	arg.pArg = GetCurrCo( co_get_curr_thread_env() );
	
	
	//2. add epoll
	for(nfds_t i=0;i<nfds;i++)
	{
		arg.pPollItems[i].pSelf = arg.fds + i;
		arg.pPollItems[i].pPoll = &arg;

		arg.pPollItems[i].pfnPrepare = OnPollPreparePfn;/*epollwait返回预处理函数,从时间轮里取出该事件*/
		struct epoll_event &ev = arg.pPollItems[i].stEvent;

		if( fds[i].fd > -1 )
		{
			ev.data.ptr = arg.pPollItems + i;
			ev.events = PollEvent2Epoll( fds[i].events );

			int ret = co_epoll_ctl( epfd,EPOLL_CTL_ADD, fds[i].fd, &ev );
			if (ret < 0 && errno == EPERM && nfds == 1 && pollfunc != NULL)
			{
				if( arg.pPollItems != arr )
				{
					free( arg.pPollItems );
					arg.pPollItems = NULL;
				}
				free(arg.fds);
				free(&arg);
				return pollfunc(fds, nfds, timeout);
			}
		}
		//if fail,the timeout would work
	}

	//3.add timeout
        /*加入时间轮*/
	unsigned long long now = GetTickMS();
	arg.ullExpireTime = now + timeout;
	int ret = AddTimeout( ctx->pTimeout,&arg,now );
	if( ret != 0 )
	{
		co_log_err("CO_ERR: AddTimeout ret %d now %lld timeout %d arg.ullExpireTime %lld",
				ret,now,timeout,arg.ullExpireTime);
		errno = EINVAL;

		if( arg.pPollItems != arr )
		{
			free( arg.pPollItems );
			arg.pPollItems = NULL;
		}
		free(arg.fds);
		free(&arg);

		return -__LINE__;
	}
        /*加入epoll和时间轮后,主动放弃CPU,等epoll返回或者超时后,在从这里开始执行代码*/
	co_yield_env( co_get_curr_thread_env() );

	RemoveFromLink<stTimeoutItem_t,stTimeoutItemLink_t>( &arg );
	for(nfds_t i = 0;i < nfds;i++)
	{
		int fd = fds[i].fd;
		if( fd > -1 )
		{       /*从epoll删除*/
			co_epoll_ctl( epfd,EPOLL_CTL_DEL,fd,&arg.pPollItems[i].stEvent );
		}
		fds[i].revents = arg.fds[i].revents;
	}


	int iRaiseCnt = arg.iRaiseCnt;
	if( arg.pPollItems != arr )
	{
		free( arg.pPollItems );
		arg.pPollItems = NULL;
	}

	free(arg.fds);
	free(&arg);

	return iRaiseCnt;
}

如果套接字是阻塞的,hook的read和write函数实际上会先调用co_poll函数,等返回时再执行系统的read和write,这样达到了用同步编程的方式,实际上是异步执行的效果。例子给的代码是用非阻塞的。


还有共享栈模式,这个以后有时间再详细写吧

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值