前言
在上一小節中我們主要講解了信號事件是如何合併到多路I/O複用機制中的以及信號事件的初始化。在本小節中,我們將看到有關信號事件的主要操作。
信號事件的註冊
前面我們看到了信號事件是在何時何地如何被初始化的,一個事件無非就是初始化、註冊、激活、回調、註銷這幾個重要的操作。接下來我們看看信號事件註冊相關的,即evsignal_add
函數,它在signal.c
文件中定義。
int
evsignal_add(struct event *ev)
{
int evsignal;
struct event_base *base = ev->ev_base;
struct evsignal_info *sig = &ev->ev_base->sig;
//讀/寫事件不屬於這兒
if (ev->ev_events & (EV_READ|EV_WRITE))
event_errx(1, "%s: EV_SIGNAL incompatible use", __func__);
/*
* 這裏是個宏函數,在event.h中定義如下
* #define EVENT_SIGNAL(ev) (int)(ev)->ev_fd
* 作用就是取得信號值
*/
evsignal = EVENT_SIGNAL(ev);
assert(evsignal >= 0 && evsignal < NSIG);
//沒有事件註冊到該evsignal信號上
if (TAILQ_EMPTY(&sig->evsigevents[evsignal])) {
event_debug(("%s: %p: changing signal handler", __func__, ev));
/* 如有必要,擴大給sh_old分配的內存
* 給該信號設置新的信號捕捉函數
*/
if (_evsignal_set_handler(
base, evsignal, evsignal_handler) == -1)
return (-1);
/* catch signals if they happen quickly */
evsignal_base = base;
//該信號事件未註冊過
if (!sig->ev_signal_added) {
//註冊該事件
if (event_add(&sig->ev_signal, NULL))
return (-1);
sig->ev_signal_added = 1;
}
}
/* multiple events may listen to the same signal */
//將信號事件添加到信號evsignal對應的事件鏈表中
TAILQ_INSERT_TAIL(&sig->evsigevents[evsignal], ev, ev_signal_next);
return (0);
}
該函數主要邏輯就是:
- 判斷信號有無對應的註冊事件鏈表
- 無,判斷是否需要重新分配內存,接着便註冊該事件
- 有,直接將該事件添加到信號對應的事件鏈表中
這裏初看的話可能你會覺得有點迷糊,因爲有兩個註冊事件的操作,而且不一樣。
- event_add
註冊的事件,是到時候信號事件被激活了,真正執行的操作。
- TAILQ_INSERT_TAIL
這個執行的操作雖然也是將信號事件加入鏈表中,但是是不同的鏈表。它是收到信號之後,會執行的操作,可以看到_evsignal_set_handler
設置的都是evsignal_handler
捕捉函數,不管是哪個信號。等下我們會講這個函數。
總的來說,evsignal_handler
,主要是執行寫socket這邊寫數據觸發讀socket事件,然後多路I/O機制就可以成功的知道信號發生了,從而將event_add
註冊的信號事件激活。
evsignal_handler
代碼如下:
static void
evsignal_handler(int sig)
{
int save_errno = errno;
if (evsignal_base == NULL) {
event_warn(
"%s: received signal %d, but have no base configured",
__func__, sig);
return;
}
evsignal_base->sig.evsigcaught[sig]++;
evsignal_base->sig.evsignal_caught = 1;
#ifndef HAVE_SIGACTION
signal(sig, evsignal_handler);
#endif
/* Wake up our notification mechanism */
send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);
errno = save_errno;
}
裏面完成的就主要是註冊信號捕捉函數以及給讀socket發送數據。這樣就可以理解前面那個可能導致你迷惑的知識了。
激活信號事件
我們最後再來看看是如何激活一個信號事件的:
void
evsignal_process(struct event_base *base)
{
struct evsignal_info *sig = &base->sig;
struct event *ev, *next_ev;
sig_atomic_t ncalls;
int i;
base->sig.evsignal_caught = 0;
/*
* NSIG是個宏定義,代碼支持的信號最大值
* 遍歷該信號的事件鏈表
*/
for (i = 1; i < NSIG; ++i) {
ncalls = sig->evsigcaught[i];
//ncalls代表信號接收到的次數,爲0則不需要進行激活了
if (ncalls == 0)
continue;
sig->evsigcaught[i] -= ncalls;
//處理該信號對應事件鏈表上的每個事件
for (ev = TAILQ_FIRST(&sig->evsigevents[i]);
ev != NULL; ev = next_ev) {
next_ev = TAILQ_NEXT(ev, ev_signal_next);
//如果不是永久事件,則激活了將其刪除
if (!(ev->ev_events & EV_PERSIST))
event_del(ev);
//進行激活操作
event_active(ev, EV_SIGNAL, ncalls);
}
}
}
小結
關於信號事件管理部分,還有註銷等操作,這些和註冊大致相同,就不一一講解了。主要是要理解socket pair以及它們各自對應的事件是怎樣的。關於寫socket這邊,信號發生之後,會調用send
函數給讀socket發數據從而觸發讀事件,導致該事件被激活,最後被調度。
接下來,我們會取看一看關於定時事件方面的管理。