.\" Copyright (C) 2003 Davide Libenzi .\" .\" %%%LICENSE_START(GPLv2+_SW_3_PARA) .\" This program is free software; you can redistribute it and/or modify .\" it under the terms of the GNU General Public License as published by .\" the Free Software Foundation; either version 2 of the License, or .\" (at your option) any later version. .\" .\" This program is distributed in the hope that it will be useful, .\" but WITHOUT ANY WARRANTY; without even the implied warranty of .\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the .\" GNU General Public License for more details. .\" .\" You should have received a copy of the GNU General Public .\" License along with this manual; if not, see .\" . .\" %%%LICENSE_END .\" .\" Davide Libenzi .\" .\"******************************************************************* .\" .\" This file was generated with po4a. Translate the source file. .\" .\"******************************************************************* .TH EPOLL 7 2021\-03\-22 Linux "Linux Programmer's Manual" .SH 名稱 epoll \- I/O 事件通知設施 .SH 概要 .nf \fB#include \fP .fi .SH 說明 \fBepoll\fP API 的任務與 \fBpoll\fP(2) 類似:監控多個檔案描述符,找出其中可以進行I/O 的檔案描述符。 \fBepoll\fP API 既可以作為邊緣觸發(edge\-triggered)的介面使用,也可以作為水平觸發(level\-triggered)的介面使用,並能很好地擴充套件,監視大量檔案描述符。 .PP \fBepoll\fP API 的核心概念是 \fBepoll\fP \fI例項\fP(\fBepoll\fP \fIinstance\fP),這是核心的一個內部資料結構,從使用者空間的角度看,它可以被看作一個內含兩個列表的容器: .IP \(bu 2 \fI興趣\fP列表(\fIinterest\fP list,有時也稱為 \fBepoll\fP 集(\fBepoll\fP set)):程序註冊了“監控興趣”的檔案描述符的集合。 .IP \(bu \fI就緒\fP列表(\fIready\fP list):“準備好”進行 I/O 的檔案描述符的集合。就緒列表是興趣列表中的檔案描述符的子集(或者更準確地說,是其引用的集合)。核心會根據這些檔案描述符上的 I/O 活動動態地填充就緒列表。 .PP 下列系統呼叫可用於建立和管理 \fBepoll\fP 例項: .IP \(bu 2 \fBepoll_create\fP(2) 會建立一個新的 \fBepoll\fP 例項,並返回一個指向該例項的檔案描述符。(最新的 \fBepoll_create1\fP(2) 擴充套件了 \fBepoll_create\fP(2) 的功能。) .IP \(bu \fBepoll_ctl\fP(2) 能向 \fBepoll\fP 例項的興趣列表中新增專案,註冊對特定檔案描述符的興趣。 .IP \(bu .\" \fBepoll_wait\fP(2) 會等待 I/O 事件,如果當前沒有事件可用,則阻塞呼叫它的執行緒。(此係統呼叫可被看作從 \fBepoll\fP 例項的就緒列表中獲取專案。) .SS 水平觸發與邊緣觸發 \fBepoll\fP 事件的分發介面既可以表現為邊緣觸發(ET),也可以表現為水平觸發(LT)。這兩種機制的區別描述如下。假設發生下列情況: .IP 1. 3 讀取方在 \fBepoll\fP 例項中註冊代表管道讀取端(\fIrfd\fP)的檔案描述符。 .IP 2. 寫入方在管道的寫入端寫入 2 kB 的資料。 .IP 3. 讀取方呼叫 \fBepoll_wait\fP(2), \fIrfd\fP 作為一個就緒的檔案描述符被返回。 .IP 4. 讀取方只從 \fIrfd\fP 中讀取 1 kB 的資料。 .IP 5. 讀取方再次呼叫 \fBepoll_wait\fP(2)。 .PP 如果讀取方新增 \fIrfd\fP 到 \fBepoll\fP 介面時使用了 \fBEPOLLET\fP (邊緣觸發)標誌位,那麼縱使此刻檔案輸入緩衝區中仍有可用的資料(剩餘的1 KB 資料),步驟\fB5\fP中的\fBepoll_wait\fP(2) 呼叫仍可能會掛起;與此同時,寫入方可能在等待讀取方對它傳送的資料的響應。造成這種互相等待的情形的原因是邊緣觸發模式只有在被監控的檔案描述符發生變化時才會遞送事件。因此,在步驟\fB5\fP中,讀取方最終可能會為一些已經存在於自己輸入緩衝區內的資料一直等下去。在上面的例子中,由於寫入方在第\fB2\fP步中進行了寫操作, \fIrfd\fP 上產生了一個事件,這個事件在第\fB3\fP步中被讀取方消耗了。但讀取方在第\fB4\fP步中進行的讀操作卻沒有消耗完整個緩衝區的資料,因此在第\fB5\fP步中對\fBepoll_wait\fP(2) 的呼叫可能會無限期地阻塞。 .PP 使用 \fBEPOLLET\fP 標誌位的應用程式應當使用非阻塞的檔案描述符,以避免(因事件被消耗而)使正在處理多個檔案描述符的任務因阻塞的讀或寫而出現飢餓。將 \fBepoll\fP用作邊緣觸發(\fBEPOLLET\fP)的介面,建議的使用方法如下: .IP a) 3 使用非阻塞的檔案描述符; .IP b) 只在 \fBread\fP(2) 或 \fBwrite\fP(2) 返回 \fBEAGAIN\fP 後再等待新的事件。 .PP 相較而言,當作為水平觸發的介面使用時(預設情況,沒有指定 \fBEPOLLET\fP), \fBepoll\fP只是一個更快的 \fBpoll\fP(2),可以用在任何能使用 \fBpoll\fP(2) 的地方,因為此時兩者的語義相同。 .PP 即使是邊緣觸發的 \fBepoll\fP,在收到多個數據塊時也可能產生多個事件,因此呼叫者可以指定 \fBEPOLLONESHOT\fP 標誌位,告訴 \fBepoll\fP 在自己用 \fBepoll_wait\fP(2)收到事件後禁用相關的檔案描述符。當指定了 \fBEPOLLONESHOT\fP 標誌位時,呼叫者可使用\fBepoll_ctl\fP(2) 與 \fBEPOLL_CTL_MOD\fP 標誌位重灌(rearm)一個被禁用的檔案描述符,這是呼叫者而不是 \fBepoll\fP 的責任。 .PP .\" 如果多個執行緒(或程序,如果子程序透過 \fBfork\fP(2) 繼承了 \fBepoll\fP 檔案描述符)等待同一個 epoll 檔案描述符,且同時在 \fBepoll_wait\fP(2) 中被阻塞,那麼當興趣列表中某個標記為邊緣觸發 (\fBEPOLLET\fP) 通知的檔案描述符準備就緒,這些執行緒(或程序)中只會有一個執行緒(或程序)從 \fBepoll_wait\fP(2) 中被喚醒。這為避免某些場景下的“驚群”(thundering herd)喚醒提供了有用的最佳化。 .SS 系統自動睡眠的處理 如果系統透過 \fI/sys/power/autosleep\fP 處於 \fBautosleep\fP 模式,那麼當某個事件的發生將裝置從睡眠中喚醒時,裝置驅動程式僅會保持裝置喚醒直到該事件入隊為止。若想保持裝置喚醒直到事件被處理完畢,則需使用 \fBepoll_ctl\fP(2) 的 \fBEPOLLWAKEUP\fP標誌位。 .PP 當在 \fIstruct epoll_event\fP 結構體的 \fBevents\fP 段中設定 \fBEPOLLWAKEUP\fP標誌位時,從事件入隊的那一刻起,到 \fBepoll_wait\fP(2) 呼叫返回事件,再一直到下一次 \fBepoll_wait\fP(2) 呼叫之前,系統會一直保持喚醒。若要讓事件保持系統喚醒的時間超過這個時間,那麼在第二次 \fBepoll_wait\fP(2) 呼叫之前,應當設定一個單獨的\fIwake_lock\fP。 .SS "/proc 介面" .\" Following was added in 2.6.28, but them removed in 2.6.29 .\" .TP .\" .IR /proc/sys/fs/epoll/max_user_instances " (since Linux 2.6.28)" .\" This specifies an upper limit on the number of epoll instances .\" that can be created per real user ID. 以下介面可以用來限制 epoll 消耗的核心記憶體的量。 .TP \fI/proc/sys/fs/epoll/max_user_watches\fP (從 Linux 2.6.28 開始) .\" 2.6.29 (in 2.6.28, the default was 1/32 of lowmem) 此介面指定了單個使用者在系統內所有 epoll 例項中可以註冊的檔案描述符的總數限制。這個限制是針對每個真實使用者ID的。每個註冊的檔案描述符在32位核心上大約需要90個位元組,在64位核心上大約需要160個位元組。目前, \fImax_user_watches\fP 的預設值是可用低記憶體的1/25(4%)除以註冊的空間成本(以位元組計)。 .SS "示例:建議的使用 epoll 的方式" \fBepoll\fP 作為水平觸發介面的用法與 \fBpoll\fP(2) 具有相同的語義,但邊緣觸發的用法需要更多的說明,以避免應用程式事件迴圈的停滯。在下面的例子中,呼叫了 \fBlisten\fP(2)來監聽 listener,一個非阻塞的套接字。函式 \fIdo_use_fd()\fP 使用新就緒的檔案描述符,直到 \fBread\fP(2) 或 \fBwrite\fP(2) 返回 \fBEAGAIN\fP。一個事件驅動的狀態機應用程式在接收到 \fBEAGAIN\fP 後,應該記錄它的當前狀態,這樣在下一次呼叫\fIdo_use_fd()\fP 時,它就能從之前停下的地方繼續 \fBread\fP(2) 或 \fBwrite\fP(2)。 .PP .in +4n .EX #define MAX_EVENTS 10 struct epoll_event ev, events[MAX_EVENTS]; int listen_sock, conn_sock, nfds, epollfd; /* Code to set up listening socket, \(aqlisten_sock\(aq, (socket(), bind(), listen()) omitted. */ epollfd = epoll_create1(0); if (epollfd == \-1) { perror("epoll_create1"); exit(EXIT_FAILURE); } ev.events = EPOLLIN; ev.data.fd = listen_sock; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == \-1) { perror("epoll_ctl: listen_sock"); exit(EXIT_FAILURE); } for (;;) { nfds = epoll_wait(epollfd, events, MAX_EVENTS, \-1); if (nfds == \-1) { perror("epoll_wait"); exit(EXIT_FAILURE); } for (n = 0; n < nfds; ++n) { if (events[n].data.fd == listen_sock) { conn_sock = accept(listen_sock, (struct sockaddr *) &addr, &addrlen); if (conn_sock == \-1) { perror("accept"); exit(EXIT_FAILURE); } setnonblocking(conn_sock); ev.events = EPOLLIN | EPOLLET; ev.data.fd = conn_sock; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) == \-1) { perror("epoll_ctl: conn_sock"); exit(EXIT_FAILURE); } } else { do_use_fd(events[n].data.fd); } } } .EE .in .PP 當作為邊緣觸發的介面使用時,出於效能考慮,可在新增檔案描述符(\fBEPOLL_CTL_ADD\fP)時指定 (\fBEPOLLIN\fP|\fBEPOLLOUT\fP)。這樣可以避免反覆呼叫 \fBepoll_ctl\fP(2) 與\fBEPOLL_CTL_MOD\fP 在 \fBEPOLLIN\fP 和 \fBEPOLLOUT\fP 之間來回切換。 .SS "epoll 十問" .IP 0. 4 用什麼區分興趣列表中註冊的檔案描述符? .IP 檔案描述符的數值和開啟檔案描述(open file description,又稱“open file handle”,核心對開啟的檔案的內部表示)的組合。 .IP 1. 如果在同一個 \fBepoll\fP 例項上多次註冊相同的檔案描述符會怎樣? .IP .\" But a file descriptor duplicated by fork(2) can't be added to the .\" set, because the [file *, fd] pair is already in the epoll set. .\" That is a somewhat ugly inconsistency. On the one hand, a child process .\" cannot add the duplicate file descriptor to the epoll set. (In every .\" other case that I can think of, file descriptors duplicated by fork have .\" similar semantics to file descriptors duplicated by dup() and friends.) On .\" the other hand, the very fact that the child has a duplicate of the .\" file descriptor means that even if the parent closes its file descriptor, .\" then epoll_wait() in the parent will continue to receive notifications for .\" that file descriptor because of the duplicated file descriptor in the child. .\" .\" See http://thread.gmane.org/gmane.linux.kernel/596462/ .\" "epoll design problems with common fork/exec patterns" .\" .\" mtk, Feb 2008 你可能會得到 \fBEEXIST\fP。然而,在同一個epoll例項上新增重複的(\fBdup\fP(2),\fBdup2\fP(2), \fBfcntl\fP(2) \fBF_DUPFD\fP)檔案描述符是可能的。如果重複的檔案描述符是用不同的事件掩碼(\fIevents\fP mask)註冊的,那麼這會成為過濾事件的一個實用技巧。 .IP 2. 多個 \fBepoll\fP 例項能等待同一個檔案描述符嗎?如果可以,事件會被報告給所有的這些\fBepoll\fP 檔案描述符嗎? .IP 能,而且事件會被報告給所有的例項。但你可能需要小心仔細地程式設計才能正確地實現這一點。 .IP 3. \fBepoll\fP 檔案描述符本身 poll/epoll/selectable 嗎? .IP 是的,如果一個 \fBepoll\fP 檔案描述符有事件在等待,那麼它將顯示為可讀。 .IP 4. 如果試圖把 \fBepoll\fP 檔案描述符放到它自己的檔案描述符集合中會發生什麼? .IP \fBepoll_ctl\fP(2) 呼叫會失敗(\fBEINVAL\fP)。但你可以將一個 \fBepoll\fP 檔案描述符新增到另一個 \fBepoll\fP 檔案描述符集合中。 .IP 5. 我可以透過 UNIX 域套接字傳送一個 \fBepoll\fP 檔案描述符到另一個程序嗎? .IP 可以,但這樣做是沒有意義的,因為接收程序不會得到興趣列表中檔案描述符的副本。 .IP 6. 關閉一個檔案描述符會將它從所有 \fBepoll\fP 興趣列表中移除嗎? .IP 會,但要注意幾點。檔案描述符是對開啟檔案描述(open file description)的引用(見 \fBopen\fP(2))。每當透過 \fBdup\fP(2), \fBdup2\fP(2), \fBfcntl\fP(2) \fBF_DUPFD\fP,或 \fBfork\fP(2) 複製某個檔案描述符時,都會建立一個新的檔案描述符,引用同一個開啟檔案描述。一個開啟檔案描述會在所有引用它的檔案描述符被關閉之前一直存在。 .IP 一個檔案描述符只有在所有指向其依賴的開啟檔案描述的檔案描述符都被關閉後才會從興趣列表中移除。這意味著,即使興趣列表內的某個檔案描述符被關閉了,如果引用同一檔案描述的其他檔案描述符仍然開著,則該檔案描述符的事件仍可能會通知。為了防止這種情況發生,在複製檔案描述符前,必須顯式地將其從興趣列表中移除(使用\fBepoll_ctl\fP(2) \fBEPOLL_CTL_DEL\fP)。或者應用程式必須能確保所有的檔案描述符都被關閉(如果檔案描述符是被使用 \fBdup\fP(2) 或 \fBfork\fP(2) 的庫函式隱式複製的,這一點可能會很難保證)。 .IP 7. 如果在兩次 \fBepoll_wait\fP(2) 呼叫之間發生了不止一個事件,它們是會一起報告還是會分開報告? .IP 它們會一起報告。 .IP 8. 對檔案描述符的操作會影響已經收集到但尚未報告的事件嗎? .IP 你可以對某個現有的檔案描述符做刪除和修改兩種操作:刪除,對這種情況沒有意義;修改,將重新讀取可用的 I/O。 .IP 9. 當使用 \fBEPOLLET\fP 標誌位(邊緣觸發行為)時,我需要持續讀/寫檔案描述符,直到\fBEAGAIN\fP 嗎? .IP 從 \fBepoll_wait\fP(2) 收到的事件會提示你,對應的檔案描述符已經準備好進行所要求的I/O 操作。直到下一次(非阻塞的)讀/寫產生 \fBEAGAIN\fP 之前,此檔案描述符都應被認為是就緒的。何時及如何使用該檔案描述符完全取決於你。 .IP 對於面向資料包/令牌的檔案(如資料報套接字、典型模式(canonical mode)下的終端),感知讀/寫 I/O 空間盡頭的唯一方法是持續讀/寫直到 \fBEAGAIN\fP。 .IP 對於面向流的檔案(如管道、FIFO、流套接字),也可透過檢查從目標檔案描述符讀/寫的資料量來檢測讀/寫 I/O 空間消費完的情況。例如,如果你在呼叫 \fBread\fP(2) 時指定了期望讀取的位元組數,但 \fBread\fP(2) 返回的實際讀取位元組數較少,你就可以確定檔案描述符的讀 I/O 空間已經消費完了。在使用 \fBwrite\fP(2) 寫入時同理。(但如果你不能保證被監視的檔案描述符總是指向一個面向流的檔案,那麼就應當避免使用這一技巧) .SS 可能的陷阱和避免的方法 .TP \fBo 邊緣觸發下的飢餓\fP .PP 如果某個就緒的檔案可用的 I/O 空間很大,試圖窮盡它可能會導致其他檔案得不到處理,造成飢餓。(但這個問題並不是 \fBepoll\fP 特有的)。 .PP 解決方案是維護一個就緒列表,並在其關聯的資料結構中將此檔案描述符標記為就緒,從而使應用程式在記住哪些檔案需要被處理的同時仍能迴圈遍歷所有就緒的檔案。這也使你可以忽略收到的已經就緒的檔案描述符的後續事件。 .TP \fBo 如果使用了事件快取...\fP .PP 如果你使用了事件快取或暫存了所有從 \fBepoll_wait\fP(2) 返回的檔案描述符,那麼一定要有某種方法來動態地標記這些檔案描述符的關閉(例如因先前的事件處理引起的檔案描述符關閉)。假設你從 \fBepoll_wait\fP(2) 收到了100個事件,在事件#47中,某個條件導致事件#13被關閉。如果你刪除資料結構並關閉(\fBclose\fP(2))事件#13的檔案描述符,那麼你的事件快取可能仍然會說事件#13的檔案描述符有事件在等待而造成迷惑。 .PP 對應的一個解決方案是,在處理事件47的過程中,呼叫 \fBepoll_ctl\fP(\fBEPOLL_CTL_DEL\fP)來刪除並關閉(\fBclose\fP(2))檔案描述符13,然後將其相關的資料結構標記為已刪除,並將其連結到一個清理列表。如果你在批處理中發現了檔案描述符13的另一個事件,你會發現檔案描述符13先前已被刪除,這樣就不會有任何混淆。 .SH 版本 .\" Its interface should be finalized in Linux kernel 2.5.66. \fBepoll\fP API 在 Linux 核心2.5.44中引入。2.3.2版本的 glibc 加入了對其的支援。 .SH 適用於 \fBepoll\fP API 是 Linux 特有的。其他的一些系統也提供類似的機制,例如 FreeBSD有 \fIkqueue\fP, Solaris 有 \fI/dev/poll\fP。 .SH 注 可以透過程序對應的 \fI/proc/[pid]/fdinfo\fP 目錄下的 epoll 檔案描述符條目檢視epoll 檔案描述符所監視的檔案描述符的集合。詳情見 \fBproc\fP(5)。 .PP \fBkcmp\fP(2) 的 \fBKCMP_EPOLL_TFD\fP 操作可以用來檢查一個 epoll 例項中是否存在某個檔案描述符。 .SH 另請參閱 \fBepoll_create\fP(2), \fBepoll_create1\fP(2), \fBepoll_ctl\fP(2), \fBepoll_wait\fP(2), \fBpoll\fP(2), \fBselect\fP(2) .SH "跋" .br 本頁面中文版由中文 man 手冊頁計劃提供。 .br 中文 man 手冊頁計劃:\fBhttps://github.com/man-pages-zh/manpages-zh\fR