Windows XP Windows 7 Windows 2003 Windows Vista Windows 綜合教學 Linux 系統教學
Windows 10 Windows 8 Windows 2008 Windows NT Windows Server 電腦軟體教學
您现在的位置: Windows教程網 >> Windows教程 >  >> Linux 系統教學 >> Linux 教學

Linux poll 系統呼叫實現深度剖析:等待隊列與 poll_table 機制

編輯:Linux 教學

前言

poll 是 Linux 經典 I/O 多路複用介面,雖已被 epoll 取代於高併發場景,但其等待隊列掛載/喚醒機制是理解 epoll、select 等所有多路複用本質的基石。本文從資料結構、系統調用流程、關鍵函數調用關係三維度,深度解析 poll 核心實現。

一、select/poll 固有缺陷

  1. 每次調用需從使用者空間重複拷貝參數。
  2. 每次調用需掃描所有 fd,時間複雜度 O(n)。
  3. 每次調用將當前進程掛載到每個 fd 的等待隊列,結束再逐一移除,開銷大。

二、核心關鍵資料結構

1. 等待隊列頭 wait_queue_head_t

struct __wait_queue_head {
    spinlock_t   lock;           // 保護隊列
    struct list_head task_list;  // 掛載 wait_queue_t 的雙向鏈表
};
typedef struct __wait_queue_head wait_queue_head_t;

2. 等待隊列項 wait_queue_t

struct __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;

3. poll 專用結構

結構用途
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 陣列副本(分頁鏈接)

4. 回調函數原型

typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);
struct poll_table { poll_queue_proc qproc; };

核心預設回調為 __pollwait,負責將當前進程加入裝置的等待隊列。

三、poll 系統調用呼叫關係圖

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()                // 回傳就緒事件

四、關鍵流程細節

1. 掛載等待隊列(__pollwait)

  • 分配 poll_table_entry,填入 file、wait_address(裝置等待隊列頭)。
  • 初始化 wait_queue_t:private=current、func=default_wake_function。
  • add_wait_queue(wait_address, &entry->wait) 掛載。
  • 將 entry 鏈入 poll_wqueues->table 頁鏈表,便後續批量移除。

2. 裝置驅動 poll 方法(以 TCP 為例)

unsigned int tcp_poll(struct file *file, poll_table *wait) {
    sock_poll_wait(file, sk_sleep(sk), wait);  // 內部呼叫 __pollwait
    // 檢查接收/發送緩衝區狀態,回傳 POLLIN/POLLOUT 等事件掩碼
}

3. 睡眠與喚醒

  • poll_schedule_timeout() → schedule_timeout() 進入睡眠。
  • 資料到達 → 裝置驅動呼叫 wake_up_interruptible(&sk->sk_sleep) → 喚醒等待隊列上進程。
  • 進程被喚醒後,do_poll 循環檢查各 fd 就緒事件,構建回傳陣列。

4. 清理(poll_freewait)

遍歷 poll_wqueues->table 頁鏈表,對每個 entry 呼叫 remove_wait_queue() 移除。

五、總結:poll 為何被 epoll 取代?

維度pollepoll
fd 就緒通知水平觸發,每次掃描全量事件驅動,回調直接加入就緒鏈表
用戶/核心拷貝每次調用全量拷貝mmap 共享就緒環狀緩衝區
fd 數量限制受限於陣列大小無實質限制(紅黑樹管理)
時間複雜度O(n) 每次調用O(1) 取就緒事件

poll 的等待隊列機制雖經典,但「重複掛載/移除、全量掃描、全量拷貝」三大短板在萬級併發下致命。epoll 以事件驅動+就緒鏈表+mmap 徹底解決,成為高併發網路編程標配。

Copyright © Windows教程網 All Rights Reserved