libcoソースコードの読み取り(6):イベント登録投票

1.イベント投票を登録する

2.関連するデータ構造

3.co_poll_inner関数

4.AddTimeout関数

5.OnPollPreparePfnおよびOnPollProcessEventコールバック関数


    libcoのポーリング機能は、ファイルイベントタイムイベントなどのイベント登録に使用できますファイルイベントは、ネットワークソケットが読み取り可能か書き込み可能かを示し、時間イベントはタイマーと同等であり、タイマーで指定された時間が経過した後も実行を継続します。ハローワードの例では、プロデューサーコルーチンがpollを呼び出してタイムイベントを登録します。

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.イベント投票を登録する

    poll関数の3つの関数パラメーターは、システムコールAPIのI / O多重化関数pollとまったく同じです。ここでは、フックが有効になっているか、epoll_ctl()が使用されているかに応じて、poll関数が呼び出されます。

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

    NULLがfdsで渡され、0がnfdsで渡される場合、この関数はタイマー関数であり、タイムアウト時間後も実行を継続します。

2.関連するデータ構造

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関数

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関数

    AddTimeout関数の機能は、時間のサイズに応じて、対応するタイムホイールにタイムアウトイベントを追加することです。

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およびOnPollProcessEventコールバック関数

    タイムホイールには、OnPollPreparePfnとOnPollProcessEventの2つのコールバック関数が登録されています。前者は前処理コールバック関数で、後者はタイムアウトコールバック関数です。これらの2つの関数は、epoll_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列表中
	}
}

 

おすすめ

転載: blog.csdn.net/MOU_IT/article/details/115033799