(八)事件主迴圈
前言
上一小節我們介紹了事件是如何註冊/登出的,在本小節中,我們將進一步探討事件從未註冊到處理的整個過程,即事件的主迴圈。
事件主迴圈
事件的主迴圈主要是通過event_base_loop
完成的。我們下面先來看看這個函式,再進行總結。
event_base_loop
int
event_base_loop(struct event_base *base, int flags)
{
const struct eventop *evsel = base->evsel;
void *evbase = base->evbase;
struct timeval tv;
struct timeval *tv_p;
int res, done;
/* clear time cache */
base->tv_cache.tv_sec = 0;
//如果有訊號事件,則將base賦給專門處理signal的evsignal_base(global var)
if (base->sig.ev_signal_added)
evsignal_base = base;
done = 0; //標識位
while (!done) {
/* Terminate the loop if we have been asked to */
//如果設定了退出標識位,則退出迴圈
if (base->event_gotterm) {
base->event_gotterm = 0;
break;
}
if (base->event_break) {
base->event_break = 0;
break;
}
/* You cannot use this interface for multi-threaded apps */
while (event_gotsig) {
event_gotsig = 0;
if (event_sigcb) {
res = (*event_sigcb)();
if (res == -1) {
errno = EINTR;
return (-1);
}
}
}
//校正系統時間
timeout_correct(base, &tv);
tv_p = &tv;
if (!base->event_count_active && !(flags & EVLOOP_NONBLOCK)) {
/* 沒有啟用的事件並且是非阻塞時,根據小根堆的最小超時時間(即堆頂),計算I/O多路複用最近的等待時間 */
timeout_next(base, &tv_p);
} else {
/*
* if we have active events, we just poll new events
* without waiting.
*/
//有啟用事件,則將tv賦為0(即立即返回,不必等待)
evutil_timerclear(&tv);
}
/* If we have no events, we just exit */
//沒有註冊的事件,直接退出了。該函式原型如下,很簡單
/*
int event_haveevents(struct event_base *base)
{
return (base->event_count > 0)
}
*/
if (!event_haveevents(base)) {
event_debug(("%s: no events registered.", __func__));
return (1);
}
/* update last old time */
gettime(base, &base->event_tv);
/* clear time cache */
base->tv_cache.tv_sec = 0;
//核心語句,呼叫的是某種具體的多路I/O機制的等待函式,如epoll中的epoll_wait。其中還會呼叫`event_active`等函式將事件插入到連結串列中。
res = evsel->dispatch(base, evbase, tv_p);
if (res == -1)
return (-1);
//將當前的時間快取設為當前時間
gettime(base, &base->tv_cache);
//將啟用的定時事件從小根堆上移出,插入到啟用連結串列中
timeout_process(base);
//如果有啟用事件,則呼叫`event_process_active`去處理。處理之後,如果已經沒有啟用事件了並且設定了只執行一次的標識,就將done置1,或者設定為非阻塞,也將done置1。否則會一直迴圈
if (base->event_count_active) {
event_process_active(base);
if (!base->event_count_active && (flags & EVLOOP_ONCE))
done = 1;
} else if (flags & EVLOOP_NONBLOCK)
done = 1;
}
/* clear time cache */
base->tv_cache.tv_sec = 0;
event_debug(("%s: asked to terminate loop.", __func__));
return (0);
}
我們簡單的看了一下event_base_loop
函式。預設情況下,它會一直執行到沒有註冊的事件為止,並且重複檢查是否有啟用事件可處理。
接下來梳理一下它的過程。
- 首先判斷退出迴圈的標記是否置位(event_gotterm和event_break),如果置位,直接退出迴圈
- 校正系統時間(將tv賦值)
- 根據最小超時時間,計算最近的等待時間,如果有未處理的啟用事件或者設定了非阻塞標誌,則無需等待(定時時間設為0)
- 如果沒有註冊事件,則退出;否則呼叫多路I/O機制的等待函式
- 之後便檢測小根堆中是否有超時事件,如果有,將其從小根堆移入啟用事件連結串列中
- 接下來,如果有啟用事件,則進行處理。並且如果要求執行到當前啟用連結串列中沒有事件可以執行就退出或者設定的是非阻塞,則下次就退出迴圈。
可以看到,要想退出主迴圈,除了done設定為1之外,還可以通過設定event_gotterm
和event_break
標識位來實現,之前在第3小節的時候沒講,接下來我們就來看看設定這兩個標識位的函式,明白這兩個標識位的區別。
event_base_loopbreak
int
event_base_loopbreak(struct event_base *event_base)
{
if (event_base == NULL)
return (-1);
event_base->event_break = 1;
return (0);
}
很簡單的一個設定標識位的函式,就置1這麼簡單。
和它相聯絡的還有一個函式。
int
event_loopbreak(void)
{
return (event_base_loopbreak(current_base));
}
可以看到只是做了一層簡單的封裝。
event_base_loopexit
int
event_base_loopexit(struct event_base *event_base, const struct timeval *tv)
{
return (event_base_once(event_base, -1, EV_TIMEOUT, event_loopexit_cb,
event_base, tv));
}
這裡牽扯到了另一個函式event_base_once
,既然如此,那我們就先看看event_base_once
,由於它涉及到了一個新的結構體event_once
,我們先來了解一下這個結構體。
struct event_once
strut event_once {
struct event ev;
void (*cb)(int, short, void *);
void *arg;
};
可以看到裡面除了有一個event之外,還涉及到回撥函式指標及它的引數。我們接著往下看。
event_base_once
/* Schedules an event once */
int
event_base_once(struct event_base *base, int fd, short events,
void (*callback)(int, short, void *), void *arg, const struct timeval *tv)
{
struct event_once *eonce;
struct timeval etv;
int res;
/* We cannot support signals that just fire once */
if (events & EV_SIGNAL)
return (-1);
//給event_once申請記憶體
if ((eonce = calloc(1, sizeof(struct event_once))) == NULL)
return (-1);
//將函式指標及引數根據傳入的引數賦值
eonce->cb = callback;
eonce->arg = arg;
//初始化event,分成定時事件或I/O事件
if (events == EV_TIMEOUT) {
if (tv == NULL) {
evutil_timerclear(&etv);
tv = &etv;
}
evtimer_set(&eonce->ev, event_once_cb, eonce);
} else if (events & (EV_READ|EV_WRITE)) {
events &= EV_READ|EV_WRITE;
//我們之前在第6小節講過event_set,其實就是給event設定一些初始值。如果忘記了可以回看一下。
event_set(&eonce->ev, fd, events, event_once_cb, eonce);
} else {
/* Bad event combination */
free(eonce);
return (-1);
}
//指定該事件註冊到的event_base以及修改對應的優先順序,這個也在第6小節講過
res = event_base_set(base, &eonce->ev);
//如果成功了,則將其註冊;失敗了則釋放資源
if (res == 0)
res = event_add(&eonce->ev, tv);
if (res != 0) {
free(eonce);
return (res);
}
return (0);
}
不知道有一點你注意到沒有,那就是呼叫evtimer_set
以及event_set
函式時,倒數第二個引數傳入的函式指標是event_once_cb
,對應的引數傳入的是event_once
結構體。
event_once_cb
其實是早就準備好的一個函式,程式碼如下:
static void
event_once_cb(int fd, short events, void *arg)
{
struct event_once *eonce = arg;
(*eonce->cb)(fd, events, eonce->arg);
free(eonce);
}
它內部呼叫了struct event_once
結構體內部的函式指標指向的函式,並且將該結構體釋放了。
再次回到開始的函式event_base_loopexit
int
event_base_loopexit(struct event_base *event_base, const struct timeval *tv)
{
return (event_base_once(event_base, -1, EV_TIMEOUT, event_loopexit_cb,
event_base, tv));
}
struct event_once
結構體中函式指標指向的就是event_loopexit_cb
。可以看出event_base_once
函式的作用就是事件被啟用排程一次後就刪除。
而event_base_loopexit
達到的效果就是,該事件被啟用後,先回調event_once_cb
函式,event_once_cb
函式裡面再回調eonce
中函式指標指向的函式(event_loopexit_cd),將event_base_loopexit
設定為1。
小結
在本節中,我們先了解了事件主迴圈event_base_loop
的整個過程。再通過event_base_loopexit
函數了解了event_base_once
的工作流程。接下來的一個小節,我們將把event.c
剩餘的一些主要函式分析一下。