天天看點

select epoll poll的差別

其實總體上說,三者都是用于IO多路複用的機制。

IO多路複用就是通過一種機制,可以監視多個描述符,一旦某一個描述符就緒了(讀就緒or寫就緒),能夠通知程式進行相應的讀寫操作。

三者本質上都是同步IO,都是在讀寫事件就緒之後自己進行讀寫(異步是核心執行讀寫操作之後通知程式拿資料,将資料從核心拷貝到使用者空間即可,),也就是說讀寫過程其實是阻塞的。

先說一下整個結論:select和poll差不多,隻不過一個檔案描述符集是d_set;另一個是pollfd。poll對于fd的數量沒有限制。兩者每次調用都需要拷貝一次fd_set(pollfd),且核心需要周遊所有的fd_set,效率很低 開銷很大。

epoll通過epoll_ctl使得每一次注冊新事件時拷貝一次所有的fd;epoll_wait隻需要輪詢一個就緒隊列是否為空(因為epoll采用了回調機制,将就緒的fd插入到就緒隊列中),是以不用輪詢整個fd_set。

epoll其實是在核心 中維持了一個fd的清單,而select和poll是将該清單維持在使用者态,然後傳遞到核心中。

綜上,epoll更适合處理大量的fd且活躍fd不是很多的情況。

“epoll模式在操作中獲得就緒的檔案描述符時,傳回的不是實際的檔案描述符,而是一個代表就緒檔案描述符的值,然後在檔案描述符數組中查詢對應的的檔案描述符。這樣避免系統調用時候在記憶體中資料結構複制的開銷。“
           

三者的比較

select

  1. 首先 select,其實就是有長度限制的 從使用者空間拷貝fd_set到核心空間之後,周遊所有的fd(檔案描述符),調用其對應的poll方法,傳回一個描述讀寫操作是否就緒的mask掩碼,根據該掩碼給fd_set指派;
  2. 周遊完all fd之後若還沒有傳回一個可讀寫的mask掩碼,則會調用schedule_timeout使得調用select的程序(也就是current)進入睡眠。當裝置驅動發生自身資源可讀寫後,會喚醒其等待隊列上睡眠的程序。如果超過一定的逾時時間(schedule_timeout指定),還是沒人喚醒,則調用select的程序會重新被喚醒獲得CPU,進而重新周遊fd,判斷有沒有就緒的fd。
  3. 最後把fd_set從核心空間拷貝到使用者空間。
select最早于1983年出現在4.2BSD中,它通過一個select()系統調用來監視多個檔案描述符的數組,當select()傳回後,該數組中就緒的檔案描述符便會被核心修改标志位,使得程序可以獲得這些檔案描述符進而進行後續的讀寫操作。
select epoll poll的差別

select其實有幾個缺點:

  1. 每次調用select,都要fd_set從使用者态拷貝到核心态,這個開銷在fd很多的時候就很大;
  2. 同樣的,每次調用select都需要在核心中周遊all fd,開銷也很大;
  3. 還有就是select支援的檔案描述符數量太小了。預設是1024。

1024 why?::select的本質是采用32個整數的32位,即3232= 1024來辨別,fd值為1-1024。當fd的值超過1024限制時,就必須修改FD_SETSIZE的大小。這個時候就可以辨別32max值範圍的fd。

select目前幾乎在所有的平台上支援,其良好跨平台支援也是它的一個優點,事實上從現在看來,這也是它所剩不多的優點之一。

poll

然後 poll。他其實實作跟select很相似,隻是描述fd集合的方式不同,poll使用的pollfd結構,(select使用的是fd_set結構)。

poll對于檔案描述發(fd)的數量沒有限制(select限制是1024)。

因為poll一次在核心中申請4K(一個頁的大小來存放fd),盡量控制在4K以内

水準觸發

另外,select()和poll()将就緒的檔案描述符告訴程序後,如果程序沒有對其進行IO操作,那麼下次調用select()和poll()的時候将再次報告這些檔案描述符,是以它們一般不會丢失就緒的消息,這種方式稱為水準觸發(Level Triggered)。

epoll

最後,高效的epoll。他的主要特點在于epoll提供了三個函數(poll和select都是隻有一個poll函數/select函數):epoll_create,epoll_ctl和epoll_wait。

epoll_create是建立一個epoll句柄;

epoll_ctl是注冊要監聽的事件類型;

epoll_wait則是等待事件的産生。

對于select中提到的三個缺點,epoll中的改進是:

  1. 對于第一個缺點,epoll的解決方案在epoll_ctl函數中。每次注冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進核心,而不是在epoll_wait的時候重複拷貝。epoll保證了每個fd在整個過程中隻會拷貝一次。
  2. 對于第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應的裝置等待隊列中,而隻在epoll_ctl時把current挂一遍(這一遍必不可少)并為每個fd指定一個回調函數,當裝置就緒,喚醒等待隊列上的等待者時,就會調用這個回調函數,而這個回調函數會把就緒的fd加入一個就緒連結清單)。epoll_wait的工作實際上就是在這個就緒連結清單中檢視有沒有就緒的fd(利用schedule_timeout()實作睡一會,判斷一會的效果,和select實作中的第7步是類似的)。
  3. 對于第三個缺點,epoll沒有這個限制,它所支援的FD上限是最大可以打開檔案的數目,這個數字一般遠大于2048,舉個例子,在1GB記憶體的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統記憶體關系很大。

epoll可以同時支援水準觸發和邊緣觸發(Edge Triggered,隻告訴程序哪些檔案描述符剛剛變為就緒狀态,它隻說一遍,如果我們沒有采取行動,那麼它将不會再次告知,這種方式稱為邊緣觸發),理論上邊緣觸發的性能要更高一些,但是代碼實作相當複雜。

epoll同樣隻告知那些就緒的檔案描述符,而且當我們調用epoll_wait()獲得就緒檔案描述符時,傳回的不是實際的描述符,而是一個代表就緒描述符數量的值,你隻需要去epoll指定的一個數組中依次取得相應數量的檔案描述符即可,這裡也使用了記憶體映射(mmap)技術,這樣便徹底省掉了這些檔案描述符在系統調用時複制的開銷。

另一個本質的改進在于epoll采用基于事件的就緒通知方式。在select/poll中,程序隻有在調用一定的方法後,核心才對所有監視的檔案描述符進行掃描,而epoll事先通過epoll_ctl()來注冊一個檔案描述符,一旦基于某個檔案描述符就緒時,核心會采用類似callback的回調機制,迅速激活這個檔案描述符,當程序調用epoll_wait()時便得到通知。
 
           

總結

(1)select,poll實作需要自己不斷輪詢所有fd集合,直到裝置就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用epoll_wait不斷輪詢就緒連結清單,期間也可能多次睡眠和喚醒交替,但是它是裝置就緒時,調用回調函數,把就緒fd放入就緒連結清單中,并喚醒在epoll_wait中進入睡眠的程序。雖然都要睡眠和交替,但是select和poll在“醒着”的時候要周遊整個fd集合,而epoll在“醒着”的時候隻要判斷一下就緒連結清單是否為空就行了,這節省了大量的CPU時間。這就是回調機制帶來的性能提升。

(2)select,poll每次調用都要把fd集合從使用者态往核心态拷貝一次,并且要把current往裝置等待隊列中挂一次,而epoll隻要一次拷貝,而且把current往等待隊列上挂也隻挂一次(在epoll_wait的開始,注意這裡的等待隊列并不是裝置等待隊列,隻是一個epoll内部定義的等待隊列)。這也能節省不少的開銷。

epoll比select和poll高效的原因主要有兩點:

  1. 減少了使用者态和核心态之間的檔案描述符拷貝
  2. 減少了對就緒檔案描述符的周遊

epoll的優點:

  1. 支援一個程序打開大數目的socket描述符(FD)

    select 最不能忍受的是一個程序所打開的FD是有一定限制的,由FD_SETSIZE設定,預設值是2048。對于那些需要支援的上萬連接配接數目的IM伺服器來說顯然太少了。這時候你一是可以選擇修改這個宏然後重新編譯核心,不過資料也同時指出這樣會帶來網絡效率的下降,二是可以選擇多程序的解決方案(傳統的 Apache方案),不過雖然linux上面建立程序的代價比較小,但仍舊是不可忽視的,加上程序間資料同步遠比不上線程間同步的高效,是以也不是一種完美的方案。不過 epoll則沒有這個限制,它所支援的FD上限是最大可以打開檔案的數目,這個數字一般遠大于2048,舉個例子,在1GB記憶體的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統記憶體關系很大。

  2. IO效率不随FD數目增加而線性下降

    傳統的select/poll另一個緻命弱點就是當你擁有一個很大的socket集合,不過由于網絡延時,任一時間隻有部分的socket是"活躍"的,但是select/poll每次調用都會線性掃描全部的集合,導緻效率呈現線性下降。但是epoll不存在這個問題,它隻會對**"活躍"的socket**進行操作—這是因為在核心實作中epoll是根據每個fd上面的callback函數實作的。那麼,隻有"活躍"的socket才會主動的去調用 callback函數,其他idle狀态socket則不會,在這點上,epoll實作了一個"僞"AIO,因為這時候推動力在os核心。在一些 benchmark中,如果所有的socket基本上都是活躍的—比如一個高速LAN環境,epoll并不比select/poll有什麼效率,相反,如果過多使用epoll_ctl,效率相比還有稍微的下降。但是一旦使用idle connections模拟WAN環境,epoll的效率就遠在select/poll之上了。

  3. 使用mmap加速核心與使用者空間的消息傳遞。

    這點實際上涉及到epoll的具體實作了。無論是select,poll還是epoll都需要核心把FD消息通知給使用者空間,如何避免不必要的記憶體拷貝就很重要,在這點上,epoll是通過核心于使用者空間mmap同一塊記憶體實作的。而如果你想我一樣從2.5核心就關注epoll的話,一定不會忘記手工 mmap這一步的。

  4. 核心微調

    這一點其實不算epoll的優點了,而是整個linux平台的優點。也許你可以懷疑linux平台,但是你無法回避linux平台賦予你微調核心的能力。比如,核心TCP/IP協定棧使用記憶體池管理sk_buff結構,那麼可以在運作時期動态調整這個記憶體pool(skb_head_pool)的大小— 通過echo XXXX>/proc/sys/net/core/hot_list_length完成。再比如listen函數的第2個參數(TCP完成3次握手的資料包隊列長度),也可以根據你平台記憶體大小動态調整。更甚至在一個資料包面數目巨大但同時每個資料包本身大小卻很小的特殊系統上嘗試最新的NAPI網卡驅動架構

資料:

https://blog.csdn.net/hust_dxxxd/article/details/50906149

https://blog.csdn.net/fdgyfghh/article/details/83926265

繼續閱讀