天天看點

[譯] Async IO on Linux: select, poll, and epoll

原文位址:Async IO on Linux: select, poll, and epoll

作者:Julia Evans

雖然一直是個 Java 程式員,但是

select

poll

epoll

這些詞彙還是經常聽見的,上次寫完 UNIX I/O 之後又去再看了一下這部分内容,遇到了這篇文章,感覺不錯特此翻譯下來,下面是正文。

今天講一講我從這本書《The Linux Programming Interface》上學到的三個系統調用:

select

poll

epoll

Chapter63:Alternative I/O models

章節内容主要關于當新的資料輸入/輸出到來時,如何監聽如此多的檔案描述符呢?誰需要同時關注這麼多的檔案描述符呢?答案是

Server

例如,你在 Linux 上用 node.js 寫一個 web server,實際上它會使用 epoll 系統調用。讓我們談談

epoll

select

poll

的差別在哪裡,和它們是如何工作的。

Servers need to watch a lot of file descriptors

假設你是一個 web server,每次你使用

accept

系統調用接收一個連接配接時,你會得到一個新的檔案描述符來表示那個連接配接。

作為一個 web server,同一時間你可能有成千上萬的連接配接。你需要知道何時某個連接配接有新的資料需要發給你,這樣你才能處理請求并傳回響應。

怎樣監聽這些檔案描述符呢?你可能會用下面的循環方式:

for x in open_connections:
    if has_new_input(x):
        process_input(x)
           

上述代碼的問題是,它會浪費許多 CPU。與其消耗所有 CPU 時間去詢問:“有資料更新麼?現在呢?現在呢?現在有麼?”,我們還不如直接告訴核心,“現在有 100 個檔案描述符,當其中一個有資料更新時通知我。”

有三個系統調用方法可以讓你達到告知 Linux 核心去監聽檔案描述符的目的,它們分别是

poll

epoll

select

,讓我們先從

poll

select

開始,因為章節内容就是從他倆先開始的。

First way: select & poll

這兩個系統調用在任何 UNIX 系統中都有,而 epoll 是 Linux 獨占的。他倆的工作原理是:

  1. 傳給它們一堆等待資料的檔案描述符
  2. 它們會回答你,其中哪個檔案描述符對應的資料準備好,可以讀寫了它們會回答你,其中哪個檔案描述符對應的資料準備好,可以讀寫了

我從書裡學到的第一個令人驚訝的事實是,

poll

select

的代碼幾乎是相同的!我去看了一下 Linux 核心源碼中關于

poll

select

的定義之後确信這是真的。

它倆都調用了很多相同的函數,書裡特别提到的是 poll 傳回了一堆可能的 fd 集合例如

POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR
           

select

僅僅告知你

there’s input / there’s output / there’s an error
           

相比于

poll

傳回的更具體的結果,例如 fd 集合,

select

僅僅傳回粗粒度的資訊,例如“你可以讀取資訊了”。你可以自己閱讀這部分功能的具體代碼。

我從書中學習到的另一個事實是,在檔案描述符稀少的情況下,

poll

的性能比

select

更好。為了證明這點,你可以看看

poll

select

的方法簽名:

int ppoll(struct pollfd *fds, nfds_t nfds,
          const struct timespec *tmo_p, const sigset_t
          *sigmask)`
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, const struct timespec *timeout,
            const sigset_t *sigmask);
           

poll

方法中,你告訴它 “這是我想監聽的檔案描述符:1,3,8,19 等等” (即是

pollfd

參數)。select 方法中,你告訴它 “我希望監聽 19 個檔案描述符,我關心其中某個fd的三種(read/write/exception)狀态變更(select 使用三個位圖來表示三個

fdset

)” 是以當 select 運作時,它會輪詢這 19 個檔案描述符,即使你隻關心其中幾個。

書中還有許多

poll

select

不同的細節,但是這兩點是我學到的最主要的。

why don’t we use poll and select ?

但是,我們說了你的 nods.js web 伺服器不會使用

select

或者

poll

,而是使用

epoll

,這是為什麼呢?

從書中可得:

每次調用 select 或者 poll,核心必須檢查所有上述的檔案描述符來發現它們是否準備好了。當監聽的檔案描述符數量非常多、範圍非常大時,耗時就會很誇張、性能自然也不好。

總結看就是核心不會記錄它應該監聽的檔案描述符清單。

Signal-driven I/O (is this a thing people use ?)

書中描述了兩種通知核心記錄監聽檔案描述符清單的方式:信号驅動式 I/O 和 epoll。信号驅動式 I/O 讓核心在一個檔案描述符更新資料時,通過調用 fcntl 傳回一個信号給你。我從沒聽過任何人使用這個,書中叙述看上去就認為 epoll 是更好的,是以我們幹脆就直接忽略了,來談談 epoll 吧。

level-triggered vs edge-triggered

在我們談論 epoll 時,我們先來讨論一下

“level-triggered”

“edge-triggered”

兩種檔案描述符通知模式。我之前從沒聽過這種專業術語(可能來自于電子工程界?)總結起來,接受通知有兩種方式:

  1. 拿到每個可讀的且是你感興趣的 fd 的清單(

    level-triggered

  2. 每當一個 fd 可讀時就收到一個通知(

    edge-triggered

what’s epoll ?

好,我們可以來講講 epoll 了。我很興奮,因為之前我浏覽代碼經常見到

epoll_wait

,我經常困惑它到底有什麼作用。

epoll 類的系統調用(

epoll_create

,

epoll_ctl

,

epoll_wait

)給予了 Linux 核心檔案描述符來跟蹤和檢查資料更新的功能。

下面是使用

epoll

的步驟:

  1. 調用

    epoll_create

    告訴核心你将要 epolling 了!它會傳回你一個 id
  2. 調用

    epoll_ctl

    來告訴核心你關心哪些檔案描述符。有趣的是,你可以傳進許多檔案描述符(pipes,FIFOs,sockets,POSIX message queues,inotify instances,devices & more),但不是有規律的檔案。我覺得是合理的 —— pipes & sockets 的 API 很簡單(一個處理對 pipe 的寫,一個處理讀),是以可以說 “這個 pipe 有新的資料可以讀” 。但檔案是另類的,你可以朝一個檔案的中間寫入資料!是以你不能簡單的說 “該檔案有新的資料可以讀取”。
  3. 調用

    epoll_wait

    來等待你關心的檔案有資料更新

performance: select & poll vs epoll

書中有個表格比較了監聽十萬個操作下的性能優劣:

[譯] Async IO on Linux: select, poll, and epoll

是以當你需要監聽大于 10 個 fd 時,使用 epoll 确實會快很多。

License

  • 本文遵守創作共享 CC BY-NC-SA 3.0協定

繼續閱讀