libevent 利用 socketpair(AF_UNIX, SOCK_STREAM, 0) 建立全雙工管道,將非同步信號轉換為可由事件循環統一處理的讀取事件。信號處理函式僅向管道寫入一個位元組,喚醒事件循環在安全上下文中處理信號。
struct evsignal_info {
struct event ev_signal; // 內部事件:監聽管道讀端
int ev_signal_pair[2]; // socketpair 兩端 [0]=寫入端, [1]=讀取端
int ev_signal_added; // 內部事件是否已加入事件循環
volatile sig_atomic_t evsignal_caught; // 信號捕獲旗標 (1=有信號待處理)
struct event_list evsigevents[NSIG]; // 每個信號對應的事件隊列 (TAILQ)
sig_atomic_t evsigcaught[NSIG]; // 每個信號被捕獲次數
#ifdef HAVE_SIGACTION
struct sigaction **sh_old; // 舊信號處理函式備份
#else
ev_sighandler_t **sh_old;
#endif
int sh_old_max; // 備份陣列大小
};
void evsignal_init(struct event_base *base) {
// 1. 建立 socketpair
evutil_socketpair(AF_UNIX, SOCK_STREAM, 0, base->sig.ev_signal_pair);
FD_CLOSEONEXEC(base->sig.ev_signal_pair[0]);
FD_CLOSEONEXEC(base->sig.ev_signal_pair[1]);
// 2. 初始化資料結構
base->sig.sh_old = NULL;
base->sig.sh_old_max = 0;
base->sig.evsignal_caught = 0;
memset(&base->sig.evsigcaught, 0, sizeof(sig_atomic_t)*NSIG);
for (int i = 0; i < NSIG; ++i)
TAILQ_INIT(&base->sig.evsigevents[i]);
// 3. 設定非阻塞
evutil_make_socket_nonblocking(base->sig.ev_signal_pair[0]);
// 4. 初始化內部事件:監聽管道讀端 (pair[1]),持久、讀事件
event_set(&base->sig.ev_signal, base->sig.ev_signal_pair[1],
EV_READ | EV_PERSIST, evsignal_cb, &base->sig.ev_signal);
base->sig.ev_signal.ev_base = base;
base->sig.ev_signal.ev_flags |= EVLIST_INTERNAL;
}
int evsignal_add(struct event *ev) {
int evsignal = EVENT_SIGNAL(ev); // 取得信號編號
struct evsignal_info *sig = &ev->ev_base->sig;
// 1. 驗證:信號事件不可設 EV_READ/EV_WRITE
if (ev->ev_events & (EV_READ|EV_WRITE))
event_errx(1, "EV_SIGNAL incompatible use");
// 2. 首次註冊此信號:設定系統信號處理函式
if (TAILQ_EMPTY(&sig->evsigevents[evsignal])) {
if (_evsignal_set_handler(base, evsignal, evsignal_handler) == -1)
return -1;
evsignal_base = base; // 全域指標供信號處理函式使用
// 3. 將內部管道事件加入事件循環
if (!sig->ev_signal_added) {
if (event_add(&sig->ev_signal, NULL)) return -1;
sig->ev_signal_added = 1;
}
}
// 4. 將使用者事件加入此信號隊列
TAILQ_INSERT_TAIL(&sig->evsigevents[evsignal], ev, ev_signal_next);
return 0;
}
static void evsignal_handler(int sig) {
int save_errno = errno;
if (!evsignal_base) return; // 尚未初始化
// 1. 記錄捕獲次數與旗標
evsignal_base->sig.evsigcaught[sig]++;
evsignal_base->sig.evsignal_caught = 1;
#ifndef HAVE_SIGACTION
signal(sig, evsignal_handler); // 重新註冊 (舊式 signal 語意)
#endif
// 2. 向管道寫入一位元組喚醒事件循環
send(evsignal_base->sig.ev_signal_pair[0], "a", 1, 0);
errno = save_errno;
}
關鍵點:信號處理函式必須極簡、非阻塞、可重入。僅做計數與 send(),不執行複雜邏輯。
void evsignal_process(struct event_base *base) {
struct evsignal_info *sig = &base->sig;
base->sig.evsignal_caught = 0; // 清除旗標
for (int i = 1; i < NSIG; ++i) {
sig_atomic_t ncalls = sig->evsigcaught[i];
if (ncalls == 0) continue;
// 遍歷此信號所有註冊事件
for (struct event *ev = TAILQ_FIRST(&sig->evsigevents[i]); ev; 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); // 加入活躍隊列
}
sig->evsigcaught[i] = 0; // 重置計數
}
}
在事件循環 dispatch() 返回後、timeout_process() 前呼叫 (見 event_base_loop),將待處理信號事件轉為活躍事件統一分發。
send() (async-signal-safe),避免死鎖與未定義行為。evsigevents[NSIG] 隊列允許多個事件關注同一信號。evsigcaught 記錄捕獲次數,event_active(..., ncalls) 一次分發合併處理,減少回調開銷。sigaction (POSIX) 與舊式 signal(),自動處理重新註冊。此機制是 libevent 高可靠性、可移植性的基石之一,被廣泛應用於高效能網路伺服器與事件驅動框架中。