epoll,位於標頭檔案sys/epoll.h,是linux系統上的i/o事件通知基礎設施。epoll api為linux系統專有,於核心2.5.44中首次引入,glibc於2.3.2版本加入支援。其它提供類似的功能的系統,包括freebsd kqueue,solaris /dev/poll等。
epoll api
epoll api實現了與poll類似的功能:監測多個檔案描述符上是否可以執行i/o操作。支援邊緣觸發et和水平觸發lt,相比poll支援監測數量更多的檔案描述符。
以下api用於建立和管理epoll例項:
epoll_create
:建立epoll例項,並返回epoll例項關聯的檔案描述符。(最新的epoll_create1擴充套件了epoll_create的功能)
create_ctl
:註冊關注的檔案描述符。註冊於同一epoll例項的一組檔案描述符被稱為epoll set,可以通過程序對應的/proc/[pid]/fdinfo目錄檢視。
epoll_wait
:等待i/o事件,如果當前沒有任何註冊事件處於可用狀態,呼叫執行緒會被阻塞。
水平觸發lt與邊緣觸發et
epoll事件分發介面可以使用et和lt兩種模式。兩種模式的差別描述如下。
典型場景:
1 管道(pipe)讀端的檔案描述符(rfd)註冊於epoll例項。
2 寫者(writer)向管道(pipe)寫端寫2kb的資料。
3 epoll_wait呼叫結束,返回rfd作為就緒的檔案描述符。
4 管道讀者(pipe reader) 從rfd讀1kb的資料。
5 下一次epoll_wait呼叫。
如果rfd檔案描述符使用epollet(邊緣觸發)標記加入epoll介面,第5步對epoll_wait的呼叫可能會掛住,儘管檔案輸入緩衝區中仍然有可用資料;與此同時,遠端實體由於已經傳送資料,可能正在等待回應。其原因是邊緣觸發模式僅在所監控的檔案描述符狀態發生變化時才投遞事件。所以,第5步的呼叫方可能最終一直在等待資料到來,但資料其實已經在輸入快取區。經過第2步的寫操作和第3步的事件處理,rfd上只會產生一次事件。由於第4步的讀操作沒有讀完全部的緩衝區資料,第5步對epoll_wait的呼叫可能會永遠阻塞。
使用epollet標記時,應該設定檔案描述符為非阻塞,以避免阻塞讀寫,使處理多個檔案描述符的任務餓死。
因此,使用epoll 邊緣觸發(epollet)模式的介面,以下有兩點建議:
1 使用非阻塞的檔案描述符
2 只有在read或write返回eagain之後,才繼續等待事件(呼叫epoll_wait)
相比之下,當epoll作為水平觸發介面(lt,預設模式)使用時,epoll相當於乙個更快的poll,可以用於poll適用的任何場景,因為二者語義相同。
在邊緣觸發模式下,當收到多個資料塊時也可能會產生多個事件,呼叫方可以通過設定epolloneshot標記,告訴epoll當通過epoll_wait收到事件時,取消關聯的檔案描述符。當給epoll設定epolloneshot標記時,呼叫方需要通過epoll_ctl對檔案描述符設定epoll_ctl_mod標記。
使用範例
當epoll作為水平觸發介面使用時與poll語義相同,而作為邊緣觸發介面使用時需要注意應用層事件迴圈的細節,以避免錯誤。以下舉例。設想乙個非阻塞的socket為監聽者,可以在該socket上呼叫listen。函式do_use_fd()處理新就緒的檔案描述符,直到遇到讀(read)或寫(write)返回eagain。事件驅動的狀態機應用,應該在收到eagain之後記錄當前狀態,以便在下次呼叫do_use_fd時,能夠繼續從之前停止讀寫資料的地方繼續讀寫(read / write)。
#define max_events 10structepoll_event ev, events[max_events];
intlisten_sock, conn_sock, nfds, epollfd;
/*code to set up listening socket, 'listen_sock',
(socket(), bind(), listen()) omitted */
epollfd= epoll_create1(0);
if(epollfd == -1)
ev.events= epollin;
ev.data.fd= listen_sock;
if(epoll_ctl(epollfd, epoll_ctl_add, listen_sock, &ev) == -1)
for(;;)
for (n = 0; n < nfds; ++n)
setnonblocking(conn_sock);
ev.events = epollin | epollet;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, epoll_ctl_add,conn_sock,
&ev) == -1)
} else }}
當作為邊緣觸發介面使用時,為效能考慮,可以通過設定epollin|epollout,一次加入檔案描述符到epoll介面(epoll_ctl_add)。這樣可以避免在epollin和epollout之間通過呼叫epoll_ctl(epoll_ctl_mod)切換。
自動休眠問題
如果系統設定了自動休眠模式(通過/sys/power/autosleep),當喚醒裝置的事件發生時,裝置驅動會保持喚醒狀態,直到事件進入排隊狀態。為了保持裝置喚醒直到事件處理完成,必須使用epoll epollwakeup 標記。
一旦給structe poll_event中的events欄位設定了epollwakeup標記,系統會在事件排隊時就保持喚醒,從epoll_wait呼叫開始,持續要下一次epoll_wait呼叫。
監測數量限制
以下檔案可以用來限制epoll使用的核心態記憶體空間大小(linux 2.6.28 開始):
/proc/sys/fs/epoll/max_user_watches
max_user_watches檔案用來設定使用者在所有epoll例項中註冊的檔案描述符數量上限,作用於每個使用者id。單個註冊檔案描述符在32位核心上消耗90位元組,在64位核心上消耗160位元組。max_user_watches的預設值是可用核心記憶體空間的1/25(4%)除以單個註冊檔案描述符消耗的位元組數。
避免飢餓(邊緣觸發)
如果i/o資料量很大,可能在讀取資料的過程中其他檔案得不到處理,造成飢餓。解決方法是維護乙個就緒列表,在關聯資料結構中標記檔案描述符為就緒狀態,由此可以記住哪些檔案在等待,並對所有就緒檔案作輪轉處理。
事件快取陷阱
如果使用事件快取,或者儲存epoll_wait返回的所有檔案描述符,就需要提供方法動態標記關閉狀態(比如,由於其他事件處理造成檔案描述符關閉),假設從epoll_wait收到100個事件,a事件造成b事件關閉,如果移除b事件結構並關閉檔案描述符,事件快取仍然認為有事件在等待檔案描述符,從而造成混亂。
解決方法是,在a事件處理過程中,呼叫epoll_ctl(epoll_ctl_del)來移除b檔案描述符並關閉,然後標記關聯的資料結構為已移除,並關聯到移除列表。在後續事件處理過程中,當發現b檔案描述符的新事件時,可以通過檢查標記發現檔案描述符已移除,避免產生混亂。
關於GDPR 你應該知道的那些事兒
gdpr是什麼?什麼時候施行?應該採取什麼措施來遵從?距離 通用資料保護規範 gdpr 正式實施,還有不到18個月的時間,但如果要具體實現這些要求,這個時間就不算長了。而且你如果覺得自己沒在歐盟範圍內,不用遵從gdpr,那你最好還是花點時間看完這篇文章吧。儘管gdpr背後的原則前景光明,但該新規定無...
那些關於SaaS的利弊,你應該知道的事
現在,一些大型且最古老的軟體公司,例如adobe 微軟和oracle都在使用軟體即服務的訂閱版本,並且這成為他們收入的乙個比較大的一部分。但是在部署之前,企業應該權衡一下軟體即服務的利弊,並且決定這種改變會給他們的it圖景帶來一些怎樣的影響,因為可能會涉及一些混合的自主託管的資料中心硬體。saas被...
關於質粒,學生物的你應該知道的那些事兒
作為乙個實驗新手,很多時候提質粒提出來的濃度都有很大差異,有的高達500 600ng ul,有的卻只有幾十。用的都是相同的菌株和試劑盒,為什麼小提濃度會有這麼大的差別呢?要解決這個問題,我們得先來了解下質粒。拷貝數 對於質粒載體,拷貝數是我們最關心的特性之一。實際上,每個細菌中的質粒的拷貝數主要決定...