首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

libevent 源碼淺析 (三):信號處理機制——Socketpair 統一事件模型

     編輯:關於C++

核心設計:Socketpair 統一信號與 I/O 事件

libevent 利用 socketpair(AF_UNIX, SOCK_STREAM, 0) 建立全雙工管道,將非同步信號轉換為可由事件循環統一處理的讀取事件。信號處理函式僅向管道寫入一個位元組,喚醒事件循環在安全上下文中處理信號。

evsignal_info 結構體

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;                   // 備份陣列大小
};

初始化:evsignal_init()

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

新增信號事件:evsignal_add()

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

系統信號處理函式:evsignal_handler()

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(),不執行複雜邏輯。

事件循環中處理信號:evsignal_process()

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),將待處理信號事件轉為活躍事件統一分發。

設計優勢總結

  1. 非同步信號安全:信號處理函式僅呼叫 send() (async-signal-safe),避免死鎖與未定義行為。
  2. 統一事件模型:信號變為普通讀事件,與 I/O、定時器同一調度邏輯,簡化核心複雜度。
  3. 支援多事件註冊同一信號:evsigevents[NSIG] 隊列允許多個事件關注同一信號。
  4. 計數合併處理:evsigcaught 記錄捕獲次數,event_active(..., ncalls) 一次分發合併處理,減少回調開銷。
  5. 跨平台相容:支援 sigaction (POSIX) 與舊式 signal(),自動處理重新註冊。

此機制是 libevent 高可靠性、可移植性的基石之一,被廣泛應用於高效能網路伺服器與事件驅動框架中。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved