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

libev 事件循環底層原理:從 ev_io_init 到 epoll_wait 的完整調用鏈分析

     編輯:關於C語言

最小化範例回顧

[c]
#include 
#include 

ev_io stdin_watcher;
ev_timer timeout_watcher;

static void stdin_cb(EV_P_ ev_io *w, int revents) {
    puts("stdin ready");
    ev_io_stop(EV_A_ w);
    ev_break(EV_A_ EVBREAK_ALL);
}
static void timeout_cb(EV_P_ ev_timer *w, int revents) {
    puts("timeout");
    ev_break(EV_A_ EVBREAK_ONE);
}
int main() {
    struct ev_loop *loop = EV_DEFAULT;
    ev_io_init(&stdin_watcher, stdin_cb, 0, EV_READ);
    ev_io_start(loop, &stdin_watcher);
    ev_timer_init(&timeout_watcher, timeout_cb, 5.5, 0.);
    ev_timer_start(loop, &timeout_watcher);
    ev_run(loop, 0);
    return 0;
}

關鍵宏展開與結構體初始化

[c]
// ev.h 關鍵宏 (簡化)
#define ev_io_init(ev, cb, fd, events) \
    do { ev_init((ev), (cb)); ev_io_set((ev), (fd), (events)); } while(0)

#define ev_init(ev, cb_) \
    do { \
        ((ev_watcher*)(ev))->active = 0; \
        ((ev_watcher*)(ev))->pending = 0; \
        ev_set_priority((ev), 0); \
        ev_set_cb((ev), cb_); \
    } while(0)

#define ev_io_set(ev, fd_, events_) \
    do { (ev)->fd = (fd_); (ev)->events = (events_) | EV__IOFDSET; } while(0)

ev_io_init 完成:active=0, pending=0, priority=0, cb=stdin_cb, fd=0, events=EV_READ|EV__IOFDSET。

ev_io_start 註冊流程

  1. 檢查 active 防重複註冊。
  2. 將 watcher 加入循環的 fdchanges 陣列 (標記該 fd 有變更)。
  3. 調用後端 backend_modify (epoll_ctl EPOLL_CTL_ADD/MOD) 向內核註冊事件。
  4. 設定 active = 1。

ev_run 事件循環核心

[c]
// 簡化邏輯
void ev_run(struct ev_loop *loop, int flags) {
    while (1) {
        // 1. 收集 fdchanges,批次調用 backend_modify 同步內核
        // 2. 計算最近 timer 到期時間 → timeout
        // 3. backend_poll(timeout) → epoll_wait/poll/select/kqueue
        // 4. 遍歷就緒事件,設定 watcher->pending, revents
        // 5. 調用 watcher->cb(loop, watcher, revents)
        // 6. 處理 pending timer / idle / prepare / check / async
        // 7. 若 flags == EVRUN_ONCE 或無 active watcher 或 ev_break 被調用 → 退出
    }
}

後端抽象與 epoll 具體實現

libev 透過 struct ev_method 統一不同 OS 的 I/O 多路複用介面:

[c]
static struct ev_method {
    const char *name;
    void (*init)(EV_P);
    void (*destroy)(EV_P);
    void (*modify)(EV_P_ int fd, int oev, int nev);
    void (*poll)(EV_P_ ev_tstamp timeout);
} *backend;

Linux 下 backend = &epoll_method:

  • init:epoll_create1(EPOLL_CLOEXEC) 建立 epfd。
  • modify:epoll_ctl(epfd, op, fd, &ev),op 為 ADD/MOD/DEL。
  • poll:epoll_wait(epfd, events, maxevents, timeout_ms),將就緒事件轉為 libev 內部 revents 並喚醒對應 watcher。

完整調用鏈圖解 (文字版)

main()
 └─ ev_run(loop, 0)
      ├─ backend_modify()  [同步 fdchanges 到 epoll]
      ├─ backend_poll()    [epoll_wait()]
      │    └─ 內核返回就緒 fd 列表
      ├─ 遍歷就緒事件
      │    ├─ 找到對應 watcher
      │    ├─ watcher->pending = 1; watcher->revents = EV_READ/WRITE
      │    └─ watcher->cb(loop, watcher, revents)  ← 用戶回調 (stdin_cb)
      ├─ 處理 timer (timeout_watcher)
      │    └─ timeout_cb()
      └─ 檢查 break 條件 → 循環或退出

圖片引用

libev 函數調用圖

核心優勢總結

  • 統一介面:同一代碼跑 epoll/kqueue/IOCP/port/poll/select。
  • 高效 Timer:最小堆 O(log n) 插入/刪除,O(1) 取最小,無需輪詢。
  • 批次修改:fdchanges 合併同一循環內多次 ev_io_start/stop,單次 epoll_ctl 系統調用。
  • 嵌入式友好:無依賴、代碼精簡、可裁剪後端。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved