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

Linux I/O 多路復用深度解析:select、poll 核心原理與內核實現對比

     編輯:關於C語言

前言:從阻塞到多路復用

傳統阻塞 I/O:單執行緒只能服務一個連線。
非阻塞 + 輪詢:CPU 佔用高、延遲不可控。
I/O 多路復用:單執行緒同時監控多個檔案描述符(fd),有事件時通知處理,無事件時睡眠釋放 CPU。select、poll、epoll 是 Linux 三大經典實現。

基礎設施:等待隊列

內核將進程掛起至等待隊列(wait_queue_head_t),事件發生時喚醒。核心 API:

  • wait_event(queue, condition):條件滿足前睡眠(不可中斷)。
  • wait_event_interruptible(queue, condition):可被信號中斷。
  • wake_up(queue):喚醒隊列上所有進程。

內部流程:wait_event 定義 wait_queue_t 節點(含 current 指標與 autoremove_wake_function),加入隊列,設置 TASK_UNINTERRUPTIBLE,呼叫 schedule() 切換進程。喚醒時遍歷隊列調用節點 func,最終 try_to_wake_up 設置 TASK_RUNNING。

select 內核實現深度剖析

系統調用路徑

sys_select → core_sys_select → do_select

核心資料結構

  • 用戶空間 fd_set:1024 位位圖(FD_SETSIZE),每位對應一個 fd。
  • 內核空間:分配 6 塊動態位圖(in/out/ex 輸入、res_in/res_out/res_ex 輸出),長度 = 實際最大 fd 數。

do_select 核心邏輯

  1. 設置進程狀態 TASK_INTERRUPTIBLE。
  2. 掃描 in/out/ex,對每個關注的 fd 呼叫驅動的 poll 方法(file_operations->poll)。
  3. 驅動 poll 內部:檢查硬體狀態 → 若無事件,呼叫 poll_wait(filp, wait_addr, pt) 將當前進程掛入驅動的等待隊列。
  4. poll_wait → __pollwait → 分配 poll_table_entry,設置 wait.func = pollwake,加入 wait_addr 隊列。
  5. 掃描結束:若有事件 → 設置結果位圖返回;若無事件 → schedule_timeout 睡眠。
  6. 事件到達 → 驅動中斷處理 → wake_up(wait_addr) → 遍歷隊列 → pollwake → try_to_wake_up → 進程喚醒 → 重新掃描 → 返回結果。

select 缺陷

  • fd 數上限 1024(需重編內核修改)。
  • 每次調用需全量複製 fd_set 進/出內核,開銷大。
  • 結果覆蓋輸入,每次調用前需重置 fd_set。
  • 線性掃描所有 fd,O(n) 複雜度。

poll 內核實現:解決 fd 上限

核心結構

struct pollfd {
    int fd;
    short events;  // 輸入:關注事件
    short revents; // 輸出:就緒事件
};
  • 使用 pollfd 陣列,無 fd 數上限(受限於記憶體)。
  • events/revents 分離,無需每次重置輸入事件。

調用路徑

sys_poll → do_sys_poll → do_poll → do_pollfd

邏輯與 select 高度相似,僅資料結構不同:do_pollfd 遍歷 pollfd 陣列呼叫驅動 poll,結果寫入 revents。

select vs poll 詳細對比

維度selectpoll
fd 上限1024 (FD_SETSIZE)無硬性上限
資料結構位圖 fd_setpollfd 結構體陣列
輸入/輸出同一 fd_set 覆蓋events/revents 分離
複製開銷每次全量複製 3×fd_set每次複製 pollfd 陣列
時間複雜度O(n) 線性掃描O(n) 線性掃描
可移植性POSIX 標準,廣泛支援POSIX 標準,廣泛支援

使用範例:select

fd_set readfds;
FD_ZERO(&readfds);
FD_SET(sock1, &readfds);
FD_SET(sock2, &readfds);
int maxfd = (sock1 > sock2) ? sock1 : sock2;

struct timeval tv = {5, 0}; // 5 秒超時
int ret = select(maxfd + 1, &readfds, NULL, NULL, &tv);
if (ret > 0) {
    if (FD_ISSET(sock1, &readfds)) handle(sock1);
    if (FD_ISSET(sock2, &readfds)) handle(sock2);
}

使用範例:poll

struct pollfd fds[2] = {
    {sock1, POLLIN, 0},
    {sock2, POLLIN, 0}
};
int ret = poll(fds, 2, 5000); // 5000ms 超時
if (ret > 0) {
    if (fds[0].revents & POLLIN) handle(sock1);
    if (fds[1].revents & POLLIN) handle(sock2);
}

總結與下一步

select/poll 同屬「水平觸發、線性掃描、用戶態/內核態全量複製」模型,適合 fd 數較少(<1000)場景。高併發(萬級 fd)需使用 epoll(邊緣觸發、就緒隊列、零拷貝、紅黑樹管理 fd)。下一篇將深入 epoll 內核實現與 LT/ET 模式實戰。

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