I/O復用使得程序能夠同時監聽多個文件描述符,對於提高程序性能至關重要。I/O復用不僅僅在網絡程序中使用,但是我接觸到的例子中,TCP網絡編程那塊使用I/O復用比較多,例如,TCP服務器同時處理監聽socket和連接socket. 在了解I/O復用之前,我們需要先了解幾個概念。 1,同步I/O與異步I/O 2,LT(水平觸發)和ET(邊緣觸發)
POSIX把兩個術語定義如下: 同步I/O:導致請求進程阻塞,直到I/O操作完成 異步I/O: 不導致請求進程阻塞
阻塞是進程在等待某種資源,但是不能馬上得到,必須等待別的進程釋放資源才能繼續,屬於被動無法得到時間片,內核就切換其它進程運行。 它與休眠和掛起的區別:休眠一般為主動式的放棄一段CPU時間。 掛起是運行時間片到了,內核要調度其它進程運行,被動式的失去CPU。(掛起可以被別的進程給搶占導致掛起,也可以自己主動掛起自己。) Unix下可用的5種I/O模型:1,阻塞式I/O 2,非阻塞式I/O 3,I/O 復用 4,信號驅動I/O(SIGIO) 5,異步I/O 1--4為同步I/O,5為異步I/O。 我們關注的I/O復用屬於同步I/O,會導致進程阻塞。
在linux的IO多路復用中有水平觸發,邊緣觸發兩種模式,這兩種模式的區別如下: 水平觸發(LT,level-triggered,也被稱為條件觸發):只要滿足條件,就觸發一個事件(只要有數據沒有被獲取,內核就不斷通知你).如果文件描述符已經就緒可以非阻塞的執行IO操作了,此時會觸發通知.允許在任意時刻重復檢測IO的狀態,沒有必要每次描述符就緒後盡可能多的執行IO.select,poll就屬於水平觸發. 邊緣觸發(ET,edge-triggered)每當狀態變化時,觸發一個事件.如果文件描述符自上次狀態改變後有新的IO活動到來,此時會觸發通知.在收到一個IO事件通知後要盡可能多的執行IO操作,因為如果在一次通知中沒有執行完IO那麼就需要等到下一次新的IO活動到來才能獲取到就緒的描述符.信號驅動式IO就屬於邊緣觸發. epoll既可以選擇水平觸發,也可以選擇邊緣觸發
下面具體介紹三大I/O復用:select,poll,epoll
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
returns numbers of ready descriptors,0 on timeout,or -1 on error
int poll(struct pollfd fds[],nfds_t nfds,int timeout);
returns number of ready file descriptors,0 on timeout,or -1 on error;
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);
下面從事件集、最大支持文件描述符、工作模式、具體實現四個方面進行對比: 3組系統調用都通過某種結構體來高速內核監聽哪些文件描述符上的事件,並使用該結構體類型的參數來獲取內核處理的結構。 select的參數類型fd_set沒有將文件描述符和事件綁定,只能處理可讀、可寫、異常事件,這使得select不能處理更多類型的事件。由於內核對fd_set的修改,應用程序下次調用select前需要重置這3個fd_set集合。 poll通過把文件描述符和事件定義在pollfd中,任何事件都被統一處理,從而使得編程接口簡潔許多。並且內核每次修改的是pollfd結構體的revents成員,而events成員保持不變,因此下次調用poll時無須重置pollfd結構體類型的事件集參數。由於每次select 和poll調用都返回整個用戶注冊的事件集合(其中包括就緒的和未就緒的),所以應用程序索引就緒文件描述符的時間復雜度為O(n).epoll 則采用與select和poll完全不同的方式來管理用戶注冊的事件。它在內核中維護一個事件表,並提供了一個獨立的系統調用epoll_ctl來控制往其中添加、刪除、修改事件。這樣,每次epoll_wait調用都直接從該內核事件表中取得用戶注冊的事件,而無須反復從用戶空間讀入這些事件。epoll_wait系統調用的events參數僅用來返回就緒的事件,這使得應用程序索引就緒文件描述符的時間復雜度為O(1) poll和epoll_wait分別用nfds和maxevents參數指定最多監聽多少個文件描述符和事件。這兩個數值都能達到系統最大的文件描述符數目,即65535(cat/proc/sys/fs/file-max).而select允許監聽的最大文件描述符數量通常有限制。雖然用戶可以修改這個限制,但這可能導致不可預期的後果。 select和poll都只能工作在LT模式,而epoll可以工作在ET模式。並且epoll還支持EPOLLONESHOT事件,該事件可以進一步減少可讀、可寫、和異常等事件被觸發的次數 實現原理上,select和poll采用輪詢的方式,即每次都要掃描整個注冊文件描述符集合,並將其中就緒的文件描述符返回給用戶程序,因此他們檢測就緒事件的時間復雜度為O(n).epoll_wait采用回調的方式,內核檢測到就緒文件描述符時,將觸發回調函數,回調函數將該文件描述符上對應的事件插入內核就緒事件隊列。內核最後在適當時機將該就緒事件隊列中的內容拷貝到用戶空間。因此epoll_wait無須輪詢整個文件描述符集合來檢測哪些文件描述符就緒,其算法復雜度為O(1). 總結起來如下表所示: