poll 是 Linux 經典 I/O 多路複用介面,雖已被 epoll 取代於高併發場景,但其等待隊列掛載/喚醒機制是理解 epoll、select 等所有多路複用本質的基石。本文從資料結構、系統調用流程、關鍵函數調用關係三維度,深度解析 poll 核心實現。
wait_queue_head_tstruct __wait_queue_head {
spinlock_t lock; // 保護隊列
struct list_head task_list; // 掛載 wait_queue_t 的雙向鏈表
};
typedef struct __wait_queue_head wait_queue_head_t;
wait_queue_tstruct __wait_queue {
unsigned int flags; // WQ_FLAG_EXCLUSIVE 等
void *private; // 通常指向 task_struct
wait_queue_func_t func; // 喚醒回調函數
struct list_head task_list; // 掛載到 head 的 task_list
};
typedef struct __wait_queue wait_queue_t;
| 結構 | 用途 |
|---|---|
poll_table_entry | 單一 fd 的等待資訊:file*、wait_queue_t、wait_queue_head_t* |
poll_table_page | 記憶體頁管理,鏈接多個 entry(kmalloc 有大小限制) |
poll_wqueues | 全域掛載管理:poll_table 回調指標、table 頁鏈表、錯誤碼 |
poll_list | 使用者空間傳入的 pollfd 陣列副本(分頁鏈接) |
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
struct poll_table { poll_queue_proc qproc; };
核心預設回調為 __pollwait,負責將當前進程加入裝置的等待隊列。
sys_poll()
→ do_sys_poll()
→ poll_initwait() // 初始化 poll_wqueues,設定 qproc = __pollwait
→ do_poll()
→ file->f_op->poll() // 裝置驅動 poll 方法(如 tcp_poll)
→ __pollwait() // 將當前進程掛載到裝置等待隊列
→ poll_schedule_timeout() // schedule_timeout() 睡眠等待
→ 被喚醒後檢查各 fd 就緒事件
→ poll_freewait() // 從所有等待隊列移除當前進程
→ copy_to_user() // 回傳就緒事件
__pollwait)poll_table_entry,填入 file、wait_address(裝置等待隊列頭)。wait_queue_t:private=current、func=default_wake_function。add_wait_queue(wait_address, &entry->wait) 掛載。poll_wqueues->table 頁鏈表,便後續批量移除。unsigned int tcp_poll(struct file *file, poll_table *wait) {
sock_poll_wait(file, sk_sleep(sk), wait); // 內部呼叫 __pollwait
// 檢查接收/發送緩衝區狀態,回傳 POLLIN/POLLOUT 等事件掩碼
}
poll_schedule_timeout() → schedule_timeout() 進入睡眠。wake_up_interruptible(&sk->sk_sleep) → 喚醒等待隊列上進程。do_poll 循環檢查各 fd 就緒事件,構建回傳陣列。poll_freewait)遍歷 poll_wqueues->table 頁鏈表,對每個 entry 呼叫 remove_wait_queue() 移除。
| 維度 | poll | epoll |
|---|---|---|
| fd 就緒通知 | 水平觸發,每次掃描全量 | 事件驅動,回調直接加入就緒鏈表 |
| 用戶/核心拷貝 | 每次調用全量拷貝 | mmap 共享就緒環狀緩衝區 |
| fd 數量限制 | 受限於陣列大小 | 無實質限制(紅黑樹管理) |
| 時間複雜度 | O(n) 每次調用 | O(1) 取就緒事件 |
poll 的等待隊列機制雖經典,但「重複掛載/移除、全量掃描、全量拷貝」三大短板在萬級併發下致命。epoll 以事件驅動+就緒鏈表+mmap 徹底解決,成為高併發網路編程標配。