libco source code reading (6): event registration poll

1. Register event poll

2. Related data structure

3. co_poll_inner function

4. AddTimeout function

5. OnPollPreparePfn and OnPollProcessEvent callback functions


    The poll function of libco can be used for event registration, including file events and time events . The file event refers to whether the network socket is readable or writable, and the time event is equivalent to a timer, which continues to execute after the time specified by the timer expires. In the hello word example, the producer coroutine calls poll to register a time event.

void* Producer(void* args)
{
	co_enable_hook_sys();
	stEnv_t* env=  (stEnv_t*)args;
	int id = 0;
	while (true)
	{
		stTask_t* task = (stTask_t*)calloc(1, sizeof(stTask_t));
		task->id = id++;
		env->task_queue.push(task);
		printf("%s:%d produce task %d\n", __func__, __LINE__, task->id);
		co_cond_signal(env->cond);
		poll(NULL, 0, 1000); // 注册时间事件,相当于sleep 1s
	}
	return NULL;
}

1. Register event poll

    The three function parameters of the poll function are exactly the same as the I/O multiplexing function poll in the system call API. Here, the poll function is called or epoll_ctl() is used according to whether the hook is enabled.

int poll(struct pollfd fds[], nfds_t nfds, int timeout)
{
	HOOK_SYS_FUNC( poll );

	if( !co_is_enable_sys_hook() )  // 如果没有使能hook,那么直接调用原生的API poll
	{
		return g_sys_poll_func( fds,nfds,timeout );
	}

	return co_poll_inner(co_get_epoll_ct(), fds, nfds, timeout, g_sys_poll_func);
}

    If NULL is passed in fds and 0 is passed in nfds, then this function is a timer function and will continue to execute after the timeout time.

2. Related data structure

typedef void (*OnPreparePfn_t)( stTimeoutItem_t *,struct epoll_event &ev, stTimeoutItemLink_t *active );
typedef void (*OnProcessPfn_t)( stTimeoutItem_t *);
struct stTimeoutItem_t
{
	enum
	{
		eMaxTimeout = 40 * 1000 // 最大超时为40s
	};
	stTimeoutItem_t *pPrev;
	stTimeoutItem_t *pNext;
	stTimeoutItemLink_t *pLink;

	unsigned long long ullExpireTime;

	OnPreparePfn_t pfnPrepare;
	OnProcessPfn_t pfnProcess;

	void *pArg; // 指向协程的结构体指针
	bool bTimeout;
};

struct stPollItem_t ;
struct stPoll_t : public stTimeoutItem_t 
{
	struct pollfd *fds; // poll中的事件
	nfds_t nfds; // typedef unsigned long int nfds_t;

	stPollItem_t *pPollItems; // 要加入epoll的事件 长度为nfds

	int iAllEventDetach; // 标识是否已经处理过了这个对象了

	int iEpollFd; // epoll fd

	int iRaiseCnt; // 此次触发的事件数
};

struct stPollItem_t : public stTimeoutItem_t
{
	struct pollfd *pSelf; // 对应的poll结构
	stPoll_t *pPoll;      // 所属的stPoll_t

	struct epoll_event stEvent; // poll结构所转换的epoll结构
};

3. co_poll_inner function

typedef int (*poll_pfn_t)(struct pollfd fds[], nfds_t nfds, int timeout);
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; // 最大超时时间为40s
	}
	int epfd = ctx->iEpollFd;
	stCoRoutine_t* self = co_self();

	/* 1. 初始化poll相关的数据结构 */
	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; // 正式回调函数
	arg.pArg = GetCurrCo(co_get_curr_thread_env() ); // 返回协程的实体
	
	
	/* 2. 把事件加入到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; // 预处理回调函数
		struct epoll_event &ev = arg.pPollItems[i].stEvent;

		if (fds[i].fd > -1 )
		{
			ev.data.ptr = arg.pPollItems + i; // ev.data为用户私有数据
			ev.events = PollEvent2Epoll( fds[i].events );

			int ret = co_epoll_ctl( epfd, EPOLL_CTL_ADD, fds[i].fd, &ev ); // 文件事件加入epoll的监听
			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. 给时间轮添加超时时间 */
	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__;
	}

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

4. AddTimeout function

    The function of the AddTimeout function is to add the timeout event to the corresponding time wheel according to the size of the time.

int AddTimeout(stTimeout_t *apTimeout, stTimeoutItem_t *apItem, unsigned long long allNow)
{
	if( apTimeout->ullStart == 0 )
	{
		apTimeout->ullStart = allNow; // 设置时间轮的最早时间是当前时间
		apTimeout->llStartIdx = 0;    // 设置最早时间对应的index 为 0
	}
    
    /* 当前时间小于初始时间出错返回 */
	if( allNow < apTimeout->ullStart )
	{
		co_log_err("CO_ERR: AddTimeout line %d allNow %llu apTimeout->ullStart %llu",
					__LINE__,allNow,apTimeout->ullStart);

		return __LINE__;
	}
    
    /* 当前时间大于超时时间出错返回 */
	if( apItem->ullExpireTime < allNow )
	{
		co_log_err("CO_ERR: AddTimeout line %d apItem->ullExpireTime %llu allNow %llu apTimeout->ullStart %llu",
					__LINE__,apItem->ullExpireTime,allNow,apTimeout->ullStart);

		return __LINE__;
	}

	int diff = apItem->ullExpireTime - apTimeout->ullStart; // 计算超时时间

    /* 超时时间大于时间轮的最长时间出错返回 */
	if (diff >= apTimeout->iItemSize )
	{
		co_log_err("CO_ERR: AddTimeout line %d diff %d",
					__LINE__,diff);

		return __LINE__;
	}

    /* 将时间时间加入到时间轮中 */
	AddTail( apTimeout->pItems + ( apTimeout->llStartIdx + diff ) % apTimeout->iItemSize , apItem );

	return 0;
}

/* 时间轮的粒度是1ms */
template <class TNode,class TLink>
void inline AddTail(TLink *apLink, TNode *ap)
{
	if (ap->pLink)
	{
		return ;
	}

    /* 插入链表尾部 */
	if (apLink->tail)
	{
		apLink->tail->pNext = (TNode *)ap;
		ap->pNext = NULL;
		ap->pPrev = apLink->tail;
		apLink->tail = ap;
	}
	else // 插入链表头部
	{
		apLink->head = apLink->tail = ap;
		ap->pNext = ap->pPrev = NULL;
	}
	ap->pLink = apLink;
}

5. OnPollPreparePfn and OnPollProcessEvent callback functions

    Two callback functions are registered in the time wheel, namely OnPollPreparePfn and OnPollProcessEvent, the former is the preprocessing callback function, and the latter is the timeout callback function. These two functions are called in the epoll_loop event loop.

/* 超时回调函数 */
void OnPollProcessEvent(stTimeoutItem_t * ap)
{
	stCoRoutine_t *co = (stCoRoutine_t*)ap->pArg;
	co_resume( co ); // 启动协程运行
}

/* 预处理回调函数 */
void OnPollPreparePfn(stTimeoutItem_t *ap, struct epoll_event &e, stTimeoutItemLink_t *active)
{
	stPollItem_t *lp = (stPollItem_t *)ap;
	lp->pSelf->revents = EpollEvent2Poll( e.events );


	stPoll_t *pPoll = lp->pPoll;
	pPoll->iRaiseCnt++; // 已经触发的事件数加一

	if( !pPoll->iAllEventDetach ) // 如果该对象未被处理过
	{
		pPoll->iAllEventDetach = 1;

		RemoveFromLink<stTimeoutItem_t,stTimeoutItemLink_t>( pPoll ); // 将该事件从时间轮中移除

		AddTail(active, pPoll); // 将该事件添加到active列表中
	}
}

 

Guess you like

Origin blog.csdn.net/MOU_IT/article/details/115033799