1. 程式人生 > >淺析epoll-為何多路複用I/O要使用epoll

淺析epoll-為何多路複用I/O要使用epoll

本文轉自C++愛好者部落格 http://www.cppfans.org/author/eliteyang,順便記錄一下自己學習epoll的過程。

現如今,網路通訊中用epoll(linux)和IOCP(windows)幾乎是大家津津樂道的東西,不為別的,就因為高效,所以大家喜歡用。IOCP的基礎東西已經講過了,可翻閱《IOCP淺析》 《IOCP淺析[二]——IOCP出現的意義和函式介面》.

什麼是epoll

epoll是Linux下多路複用IO介面select/poll的增強版本,它能顯著提高程式在大量併發連線中只有少量活躍的情況下的系統CPU利用率,因為它會複用檔案描述符集 合來傳遞結果而不用迫使開發者每次等待事件之前都必須重新準備要被偵聽的檔案描述符集合,另一點原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符 集,只要遍歷那些被核心IO事件非同步喚醒而加入Ready佇列的描述符集合就行了。epoll除了提供select/poll那種IO事件的電平觸發 (Level Triggered)外,還提供了邊沿觸發(Edge Triggered),這就使得使用者空間程式有可能快取IO狀態,減少epoll_wait/epoll_pwait的呼叫,提高應用程式效率。

Linux2.6核心中對/dev/epoll裝置的訪問的封裝(system epoll)。

這個使我們開發網路應用程式更加簡單,並且更加高效。

為什麼要使用epoll?

同樣,我們在linux系統下,影響效率的依然是I/O操作,linux提供給我們select/poll/epoll等多路複用I/O方式(kqueue暫時沒研究過),為什麼我們對epoll情有獨鍾呢?原因如下:

1.檔案描述符數量的對比。

epoll並沒有fd(檔案描述符)的上限,它只跟系統記憶體有關,我的2G的ubuntu下檢視是20480個,輕鬆支援20W個fd。可使用如下命令檢視:

cat /proc/sys/fs/file-max

再來看select/poll,有一個限定的fd的數量,linux/posix_types.h標頭檔案中

#define __FD_SETSIZE    1024

2.效率對比。

當然了,你可以修改上述值,然後重新編譯核心,然後再次寫程式碼,這也是沒問題的,不過我先說說select/poll的機制,估計你馬上會作廢上面修改列舉值的想法。

select/poll會因為監聽fd的數量而導致效率低下,因為它是輪詢所有fd,有資料就處理,沒資料就跳過,所以fd的數量會降低效率;而epoll只處理就緒的fd,它有一個就緒裝置的佇列,每次只輪詢該佇列的資料,然後進行處理。(先簡單講一下,第二篇還會詳細講解)

3.記憶體處理方式對比。

不管是哪種I/O機制,都無法避免fd在操作過程中拷貝的問題,而epoll使用了mmap(是指檔案/物件的記憶體對映,被對映到多個記憶體頁上),所以同一塊記憶體就可以避免這個問題。

btw:TCP/IP協議棧使用記憶體池管理sk_buff結構,你還可以通過修改記憶體池pool的大小,畢竟linux支援各種微調核心。

epoll的工作方式

epoll分為兩種工作方式LT和ET。

LT(level triggered) 是預設/預設的工作方式,同時支援 block和no_block socket。這種工作方式下,核心會通知你一個fd是否就緒,然後才可以對這個就緒的fd進行I/O操作。就算你沒有任何操作,系統還是會繼續提示fd已經就緒,不過這種工作方式出錯會比較小,傳統的select/poll就是這種工作方式的代表。

ET(edge-triggered) 是高速工作方式,僅支援no_block socket,這種工作方式下,當fd從未就緒變為就緒時,核心會通知fd已經就緒,並且核心認為你知道該fd已經就緒,不會再次通知了,除非因為某些操作導致fd就緒狀態發生變化。如果一直不對這個fd進行I/O操作,導致fd變為未就緒時,核心同樣不會發送更多的通知,因為only once。所以這種方式下,出錯率比較高,需要增加一些檢測程式。

LT可以理解為水平觸發,只要有資料可以讀,不管怎樣都會通知。而ET為邊緣觸發,只有狀態發生變化時才會通知,可以理解為電平變化。

如何使用epoll?

使用epoll很簡單,只需要

#include <sys/epoll.h>

有三個關鍵函式:

int epoll_create(int size);

int epoll_ctl(int epfd, int op, int fd, struct epoll_events* event);

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

當然了,不要忘記關閉函式.

============分割線==============

這篇就講到這裡了,下面兩篇主要是函式介紹,效率分析,例子。