select,poll,epoll

先說個故事html

鬼子進村程序員

處理大量的鏈接的讀寫,select 是夠低效的。由於 kernel 每次都要對 select 傳入的一組 socket 號作輪詢,這叫鬼子進村策略。一遍遍的詢問「鬼子進村了嗎?」,「鬼子進村了嗎?」... 大量的 cpu 時間都耗了進去。(更過度的是在 windows 上,還有個萬惡的 64 限制。)使用 kqueue 這些,變成了派一些我的去站崗,鬼子來了就能夠拿到通知,效率天然高了許多。web


 

緩存I/O

緩存I/O又稱爲標準I/O,大多數文件系統的默認I/O操做都是緩存I/O。在Linux的緩存I/O機制中,操做系統會將I/O的數據緩存在文件系統的頁緩存中,即數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間。編程

 

五種IO模型 

阻塞式I/O模型:默認狀況下,全部套接字都是阻塞的。recvfrom等待數據準備好,從內核向進程複製數據。windows

非阻塞式I/O: 進程把一個套接字設置成非阻塞是在通知內核,當所請求的I/O操做非得把本進程投入睡眠才能完成時,不要把進程投入睡眠,而是返回一個錯誤,recvfrom老是當即返回。數組

I/O多路複用:雖然I/O多路複用的函數也是阻塞的,可是其與以上兩種仍是有不一樣的,I/O多路複用是阻塞在select,epoll這樣的系統調用之上,而沒有阻塞在真正的I/O系統調用如recvfrom之上。其本質是經過一種機制(系統內核緩衝I/O數據),讓單個進程能夠監視多個文件描述符,一旦某個描述符就緒(通常是讀就緒或寫就緒),可以通知程序進行相應的讀寫操做。select、poll 和 epoll 都是 Linux API 提供的 IO 複用方式。緩存

信號驅動式I/O:用的不多,就不作講解了。服務器

異步I/O:這類函數的工做機制是告知內核啓動某個操做,並讓內核在整個操做(包括將數據從內核拷貝到用戶空間)完成後通知咱們。網絡

recvfrom函數(經socket接收數據)。數據結構

 

再看POSIX對這兩個術語的定義:

  • 同步I/O操做:致使請求進程阻塞,直到I/O操做完成;

  • 異步I/O操做:不致使請求進程阻塞。

 

select運行機制

select()的機制中提供一種fd_set的數據結構,其實是一個long類型的數組,每個數組元素都能與一打開的文件句柄(不論是Socket句柄,仍是其餘文件或命名管道或設備句柄)創建聯繫,創建聯繫的工做由程序員完成,當調用select()時,由內核根據IO狀態修改fd_set的內容,由此來通知執行了select()的進程哪一Socket或文件可讀。

從流程上來看,使用select函數進行IO請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視socket,以及調用select函數的額外操做,效率更差。可是,使用select之後最大的優點是用戶能夠在一個線程內同時處理多個socket的IO請求。用戶能夠註冊多個socket,而後不斷地調用select讀取被激活的socket,便可達到在同一個線程內同時處理多個IO請求的目的。而在同步阻塞模型中,必須經過多線程的方式才能達到這個目的。

poll運行機制
poll的機制與select相似,與select在本質上沒有多大差異,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,可是poll沒有最大文件描述符數量的限制。
 
epoll運行機制
epoll在Linux2.6內核正式提出,是基於事件驅動的I/O方式,相對於select來講,epoll沒有描述符個數限制,使用一個文件描述符管理多個描述符,將用戶關心的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。
epoll是Linux內核爲處理大批量文件描述符而做了改進的poll,是Linux下多路複用IO接口select/poll的加強版本,它能顯著提升程序在大量併發鏈接中只有少許活躍的狀況下的系統CPU利用率。緣由就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就好了。

epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減小epoll_wait/epoll_pwait的調用,提升應用程序效率。

  • 水平觸發(LT):默認工做模式,即當epoll_wait檢測到某描述符事件就緒並通知應用程序時,應用程序能夠不當即處理該事件;下次調用epoll_wait時,會再次通知此事件
  • 邊緣觸發(ET): 當epoll_wait檢測到某描述符事件就緒並通知應用程序時,應用程序必須當即處理該事件。若是不處理,下次調用epoll_wait時,不會再次通知此事件。(直到你作了某些操做致使該描述符變成未就緒狀態了,也就是說邊緣觸發只在狀態由未就緒變爲就緒時只通知一次)。

LT和ET本來應該是用於脈衝信號的,可能用它來解釋更加形象。Level和Edge指的就是觸發點,Level爲只要處於水平,那麼就一直觸發,而Edge則爲上升沿和降低沿的時候觸發。好比:0->1 就是Edge,1->1 就是Level。

ET模式很大程度上減小了epoll事件的觸發次數,所以效率比LT模式下高。

 

總結

一張圖總結一下select,poll,epoll的區別:

  select poll epoll
操做方式 遍歷 遍歷 回調
底層實現 數組 鏈表 哈希表
IO效率 每次調用都進行線性遍歷,時間複雜度爲O(n) 每次調用都進行線性遍歷,時間複雜度爲O(n) 事件通知方式,每當fd就緒,系統註冊的回調函數就會被調用,將就緒fd放到readyList裏面,時間複雜度O(1)
最大鏈接數 1024(x86)或2048(x64) 無上限 無上限
fd拷貝 每次調用select,都須要把fd集合從用戶態拷貝到內核態 每次調用poll,都須要把fd集合從用戶態拷貝到內核態 調用epoll_ctl時拷貝進內核並保存,以後每次epoll_wait不拷貝
 
epoll是Linux目前大規模網絡併發程序開發的首選模型。在絕大多數狀況下性能遠超select和poll。目前流行的高性能web服務器Nginx正式依賴於epoll提供的高效網絡套接字輪詢服務。可是,在併發鏈接不高的狀況下,多線程+阻塞I/O方式可能性能更好。

既然select,poll,epoll都是I/O多路複用的具體的實現,之因此如今同時存在,其實他們也是不一樣歷史時期的產物

  • select出現是1984年在BSD裏面實現的
  • 14年以後也就是1997年才實現了poll,其實拖那麼久也不是效率問題, 而是那個時代的硬件實在太弱,一臺服務器處理1千多個連接簡直就是神同樣的存在了,select很長段時間已經知足需求
  • 2002, 大神 Davide Libenzi 實現了epoll


 參考:

個人網絡開發之旅——socket編程

https://blog.csdn.net/dapengbusi/article/details/50690690

IO多路複用的三種機制Select,Poll,Epoll

IOCP , kqueue , epoll ... 有多重要?

相關文章
相關標籤/搜索