
IO多路復用是指內核一旦發(fā)現(xiàn)進程指定的一個或者多個IO條件準備讀取,它就通知該進程。IO多路復用適用如下場合:
當客戶處理多個描述符時(一般是交互式輸入和網(wǎng)絡套接口),必須使用I/O復用。
當一個客戶同時處理多個套接口時,而這種情況是可能的,但很少出現(xiàn)。
如果一個TCP服務器既要處理監(jiān)聽套接口,又要處理已連接套接口,一般也要用到I/O復用。
如果一個服務器即要處理TCP,又要處理UDP,一般要使用I/O復用。
如果一個服務器要處理多個服務或多個協(xié)議,一般要使用I/O復用。
與多進程和多線程技術相比,I/O多路復用技術的最大優(yōu)勢是系統(tǒng)開銷小,系統(tǒng)不必創(chuàng)建進程/線程,也不必維護這些進程/線程,從而大大減小了系統(tǒng)的開銷。
目前支持I/O多路復用的系統(tǒng)調用有 select,pselect,poll,epoll,I/O多路復用就是通過一種機制,一個進程可以監(jiān)視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程序進行相應的讀寫操作。但select,pselect,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現(xiàn)會負責把數(shù)據(jù)從內核拷貝到用戶空間。
對于IO多路復用機制不理解的同學,可以先行參考《聊聊Linux 五種IO模型》,來了解Linux五種IO模型。
1 select、poll、epoll簡介
epoll跟select都能提供多路I/O復用的解決方案。在現(xiàn)在的Linux內核里有都能夠支持,其中epoll是Linux所特有,而select則應該是POSIX所規(guī)定,一般操作系統(tǒng)均有實現(xiàn)。
1.1 select
基本原理:
select 函數(shù)監(jiān)視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調用后select函數(shù)會阻塞,直到有描述符就緒(有數(shù)據(jù) 可讀、可寫、或者有except),或者超時(timeout指定等待時間,如果立即返回設為null即可),函數(shù)返回。當select函數(shù)返回后,可以通過遍歷fdset,來找到就緒的描述符。
基本流程,如圖所示:

select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優(yōu)點。select的一個缺點在于單個進程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在Linux上一般為1024,可以通過修改宏定義甚至重新編譯內核的方式提升這一限制,但是這樣也會造成效率的降低。
select本質上是通過設置或者檢查存放fd標志位的數(shù)據(jù)結構來進行下一步處理。這樣所帶來的缺點是:
- select最大的缺陷就是單個進程所打開的FD是有一定限制的,它由FD_SETSIZE設置,默認值是1024。
一般來說這個數(shù)目和系統(tǒng)內存關系很大,
具體數(shù)目可以cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.
- 對socket進行掃描時是線性掃描,即采用輪詢的方法,效率較低。
當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。
如果能給套接字注冊某個回調函數(shù),當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll與kqueue做的。
需要維護一個用來存放大量fd的數(shù)據(jù)結構,這樣會使得用戶空間和內核空間在傳遞該結構時復制開銷大。
1.2 poll
基本原理:
poll本質上和select沒有區(qū)別,它將用戶傳入的數(shù)組拷貝到內核空間,然后查詢每個fd對應的設備狀態(tài),如果設備就緒則在設備等待隊列中加入一項并繼續(xù)遍歷,如果遍歷完所有fd后沒有發(fā)現(xiàn)就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒后它又要再次遍歷fd。這個過程經(jīng)歷了多次無謂的遍歷。
它沒有最大連接數(shù)的限制,原因是它是基于鏈表來存儲的,但是同樣有一個缺點:
大量的fd的數(shù)組被整體復制于用戶態(tài)和內核地址空間之間,而不管這樣的復制是不是有意義。
poll還有一個特點是“水平觸發(fā)”,如果報告了fd后,沒有被處理,那么下次poll時會再次報告該fd。
注意:
從上面看,select和poll都需要在返回后,
通過遍歷文件描述符來獲取已經(jīng)就緒的socket。事實上,同時連接的大量客戶端在一時刻可能只有很少的處于就緒狀態(tài),因此隨著監(jiān)視的描述符數(shù)量的增長,其效率也會線性下降。
1.3 epoll
epoll是在2.6內核中提出的,是之前的select和poll的增強版本。相對于select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。
基本原理:
epoll支持水平觸發(fā)和邊緣觸發(fā),最大的特點在于邊緣觸發(fā),它只告訴進程哪些fd剛剛變?yōu)榫途w態(tài),并且只會通知一次。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內核就會采用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知。
epoll的優(yōu)點:
沒有最大并發(fā)連接的限制,能打開的FD的上限遠大于1024(1G的內存上能監(jiān)聽約10萬個端口)。
效率提升,不是輪詢的方式,不會隨著FD數(shù)目的增加效率下降。只有活躍可用的FD才會調用callback函數(shù);即Epoll最大的優(yōu)點就在于它只管你“活躍”的連接,而跟連接總數(shù)無關,因此在實際的網(wǎng)絡環(huán)境中,Epoll的效率就會遠遠高于select和poll。
內存拷貝,利用mmap()文件映射內存加速與內核空間的消息傳遞;即epoll使用mmap減少復制開銷。
epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認模式,LT模式與ET模式的區(qū)別如下:
LT模式:當epoll_wait檢測到描述符事件發(fā)生并將此事件通知應用程序,
應用程序可以不立即處理該事件。下次調用epoll_wait時,會再次響應應用程序并通知此事件。ET模式:當epoll_wait檢測到描述符事件發(fā)生并將此事件通知應用程序,
應用程序必須立即處理該事件。如果不處理,下次調用epoll_wait時,不會再次響應應用程序并通知此事件。
- LT模式
LT(level triggered)是缺省的工作方式,并且同時支持block和no-block socket。在這種做法中,內核告訴你一個文件描述符是否就緒了,然后你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續(xù)通知你的。
- ET模式
ET(edge-triggered)是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變?yōu)榫途w時,內核通過epoll告訴你。然后它會假設你知道文件描述符已經(jīng)就緒,并且不會再為那個文件描述符發(fā)送更多的就緒通知,直到你做了某些操作導致那個文件描述符不再為就緒狀態(tài)了(比如,你在發(fā)送,接收或者接收請求,或者發(fā)送接收的數(shù)據(jù)少于一定量時導致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核不會發(fā)送更多的通知(only once)。
ET模式在很大程度上減少了epoll事件被重復觸發(fā)的次數(shù),因此效率要比LT模式高。epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。
- 在select/poll中,
進程只有在調用一定的方法后,內核才對所有監(jiān)視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基于某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait()時便得到通知。(此處去掉了遍歷文件描述符,而是通過監(jiān)聽回調的的機制。這正是epoll的魅力所在。)
注意:
如果沒有大量的idle-connection或者dead-connection,epoll的效率并不會比select/poll高很多,但是當遇到大量的idle-connection,就會發(fā)現(xiàn)epoll的效率大大高于select/poll。
2 select、poll、epoll區(qū)別
- 支持一個進程所能打開的最大連接數(shù)

- FD劇增后帶來的IO效率問題

- 消息傳遞方式

綜上,在選擇select,poll,epoll時要根據(jù)具體的使用場合以及這三種方式的自身特點:
表面上看epoll的性能最好,
但是在連接數(shù)少并且連接都十分活躍的情況下,select和poll的性能可能比epoll好,畢竟epoll的通知機制需要很多函數(shù)回調。
select低效是因為每次它都需要輪詢。但低效也是相對的,視情況而定,也可通過良好的設計改善。
