epoll是Linux中用於IO多路複用的機制,在nginx和redis等軟體中都有應用,redis的效能好的原因之一也就是使用了epoll進行IO多路複用,同時epoll也是各大公司面試的熱點問題。
IO多路複用
IO多路複用是一種同步IO模型,使得一個執行緒就可以對多個檔案描述符進行監聽。當有檔案描述符準備就緒時,函式就會返回,從而通知應用進行相應的處理;當沒有描述符就緒時,函式就會阻塞。
IO多路複用對於網路應用來說是非常重要的,在沒有IO多路複用時,應用一般通過同步阻塞(每個socket連線建立一個新執行緒,這將十分耗費系統效能)或者同步非阻塞(對所有socket進行反覆遍歷,當沒有就緒描述符時就會做無用功)來實現,而這些方法的效能都不太好。
在Linux中,IO多路複用主要有三種方法select、poll和epoll。
select
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select
是通過傳遞檔案描述符陣列fd_set*
來實現的。當沒有描述符準備就緒時,函式就會阻塞;當有一個或多個檔案描述符準備就緒時就會返回,之後通過遍歷陣列找到準備就緒的描述符進行處理。select
函式一般在所有作業系統中都會實現,因此具有良好的可移植性。
fd_set
的大小是固定的,在Linux中一般為1024,本質是一個bitmap,通過FD_SET
將描述符加入fd_set
,通過對所有檔案描述符依次呼叫FD_ISSET
來判斷是否準備就緒。
因此,select
就有著以下的缺點:
select
的檔案描述符最大隻能支援1024個select
需要通過遍歷來判斷是否準備就緒,因此時間複雜度為O(n)- 當監聽檔案描述符數量增加時,效能會明顯下降
select
核心態中通過輪詢來判斷檔案描述符是否就緒select
每次呼叫都需要將fd_set
從使用者地址空間拷貝到核心地址空間中,函式返回時又要拷貝回來
poll
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; // 檔案描述符
short events; // 等待的事件
short revents; // 發生的事件
};
poll
對select
的主要改進就是沒有了描述符陣列的大小限制,沒有最大連線數的限制。但是poll
仍然需要進行遍歷才能知道哪些檔案描述符準備就緒,因此,select
的缺點poll
也有。
epoll
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epoll
使用了三個系統呼叫來實現,epoll_create
建立一個控制程式碼,epoll_ctl
向控制程式碼中新增、刪除或修改檔案描述符,epoll_wait
對控制程式碼進行監聽,當有檔案描述符準備就緒後,就會通過events
引數返回。返回的引數中僅包含準備就緒的檔案描述符,也就是說不再需要通過遍歷來進行判斷。epoll
通過回撥機制來快速將檔案描述符加入就緒連結串列,避免輪詢;同時epoll
內部使用紅黑樹來儲存所有監聽的檔案描述符。
epoll
有著以下的優點:
- 沒有最大檔案描述符數量限制
- 使用mmap,避免了每次
wait
都要將陣列進行拷貝 - 直接返回就緒的檔案描述符,避免了遍歷,時間複雜度為O(k),k為就緒檔案描述符
- 使用回撥機制,當檔案描述符就緒時會觸發回撥函式,將描述符加入到就緒連結串列,避免輪詢
- 監聽的檔案描述符數量對效能影響不大
但是epoll
也不是一定比select
和poll
好,當就緒的檔案描述符很多時,即O(k)中的k接近n時,兩者效能就比較接近了;當檔案描述符數量較少時,兩者效能也差不多;epoll
的回撥函式註冊也會帶來一定的效能開銷。
觸發方式
epoll
有兩種觸發方式,水平觸發(LT, level-triggered)和邊緣觸發(ET, edge-triggered)。通過一個例子來理解兩種方式:
當描述符a中到達2kb資料,呼叫epoll_wait
會返回a,之後從描述符中讀取1kb資料,此時該描述符中仍有1kb資料,仍為就緒狀態;第二次呼叫epoll_wait
時,如果是LT,那麼返回的描述符中仍包含a,如果為ET,那麼就不包含a。
即ET只會在狀態發生改變時觸發,只返回一次,類似於上升沿觸發;而LT只要處於就緒狀態就會一直返回,類似於電平觸發。
理論上ET的效能會比LT要好,但是ET要保證每次都要把資料全部處理完成,而LT使用起來就更加方便,不易出現bug。在實際當中兩種的效能區別可以忽略,redis使用的就是LT方式。