1. 程式人生 > >關於epoll和select的區別,哪些說法是正確的?----騰訊2016研發工程師線上模擬筆試題

關於epoll和select的區別,哪些說法是正確的?----騰訊2016研發工程師線上模擬筆試題

關於epoll和select的區別,哪些說法是正確的?

正確答案: A B C   

epoll和select都是I/O多路複用的技術,都可以實現同時監聽多個I/O事件的狀態
epoll相比select效率更高,主要是基於其作業系統支援的I/O事件通知機制,而select是基於輪詢機制
epoll支援水平觸發和邊沿觸發兩種模式
select能並行支援I/O比較小,且無法修改

select和epoll這兩個機制都是多路I/O機制的解決方案,select為POSIX標準中的,而epoll為Linux所特有的。 epoll的最大好處是不會隨著FD的數目增長而降低效率,在select中採用輪詢處理,其中的資料結構類似一個數組的資料結構,而epoll是維護一個佇列,直接看佇列是不是空就可以了。 nginx就是使用epoll來實現I/O複用支援高併發,目前在高並 發的場景下,nginx越來越收到歡迎。 select的一 個缺點在於單個程序能夠監視的檔案描述符的數量存在最大限制 epoll:
(1)IO的效率不會隨著監視fd的數量的增長而下降。epoll不同於select和poll輪詢的方式,而是通過每個fd定義的回撥函式來實現的。只有就緒的fd才會執行回撥函式; (2)支援電平觸發和邊沿觸發(只告訴程序哪些檔案描述符剛剛變為就緒狀態,它只說一遍,如果我們沒有采取行動,那麼它將不會再次告知,這種方式稱為邊緣觸發)兩種方式,理論上邊緣觸發的效能要更高一些,但是程式碼實現相當複雜。 (3)有著良好的就緒事件通知機制 select: (1)單個程序可監視的fd數量受到了限制,在32位機器上,他所能管理的fd數量最大為1024; (2)對socket進行掃描時是線性掃描,當socket檔案描述符數量變多時,大量的時間是被白白浪費掉的。

select,poll,epoll都是IO多路複用的機制。I/O多路複用就通過一種機制,可以監視多個描述符,一旦某個描述符就緒(一般是讀就緒或者寫就緒),能夠通知程式進行相應的讀寫操作。 但select,poll,epoll本質上都是同步I/O,因為他們都需要在讀寫事件就緒後自己負責進行讀寫,也就是說這個讀寫過程是阻塞的

 ,而非同步I/O則無需自己負責進行讀寫,非同步I/O的實現會負責把資料從核心拷貝到使用者空間。關於這三種IO多路複用的用法,前面三篇總結寫的很清楚,並用伺服器回射echo程式進行了測試。連線如下所示:

今天對這三種IO多路複用進行對比,參考網上和書上面的資料,整理如下:

1、select實現

select的呼叫過程如下所示:

(1)使用copy_from_user從使用者空間拷貝fd_set到核心空間

(2)註冊回撥函式__pollwait

(3)遍歷所有fd,呼叫其對應的poll方法(對於socket,這個poll方法是sock_poll,sock_poll根據情況會呼叫到tcp_poll,udp_poll或者datagram_poll)

(4)以tcp_poll為例,其核心實現就是__pollwait,也就是上面註冊的回撥函式。

(5)__pollwait的主要工作就是把current(當前程序)掛到裝置的等待佇列中,不同的裝置有不同的等待佇列,對於tcp_poll來說,其等待佇列是sk->sk_sleep(注意把程序掛到等待佇列中並不代表程序已經睡眠了)。在裝置收到一條訊息(網路裝置)或填寫完檔案資料(磁碟裝置)後,會喚醒裝置等待佇列上睡眠的程序,這時current便被喚醒了。

(6)poll方法返回時會返回一個描述讀寫操作是否就緒的mask掩碼,根據這個mask掩碼給fd_set賦值。

(7)如果遍歷完所有的fd,還沒有返回一個可讀寫的mask掩碼,則會呼叫schedule_timeout是呼叫select的程序(也就是current)進入睡眠。當裝置驅動發生自身資源可讀寫後,會喚醒其等待佇列上睡眠的程序。如果超過一定的超時時間(schedule_timeout指定),還是沒人喚醒,則呼叫select的程序會重新被喚醒獲得CPU,進而重新遍歷fd,判斷有沒有就緒的fd。

(8)把fd_set從核心空間拷貝到使用者空間。

總結:

select的幾大缺點:

(1)每次呼叫select,都需要把fd集合從使用者態拷貝到核心態,這個開銷在fd很多時會很大

(2)同時每次呼叫select都需要在核心遍歷傳遞進來的所有fd,這個開銷在fd很多時也很大

(3)select支援的檔案描述符數量太小了,預設是1024 (但是是可以進行修改的,可以修改核心的FDSET巨集,但是要重新編譯核心)

這種不停的修改核心以及查詢時間是O(n)使得人們逐漸在探索新的方式替代它.............

2 poll實現

poll的實現和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結構而不是select的fd_set結構,其他的都差不多。

關於select和poll的實現分析,可以參考下面幾篇博文:

3、epoll

epoll既然是對select和poll的改進,就應該能避免上述的三個缺點。那epoll都是怎麼解決的呢?在此之前,我們先看一下epoll和select和poll的呼叫介面上的不同,select和poll都只提供了一個函式——select或者poll函式。而epoll提供了三個函式,epoll_create,epoll_ctl和epoll_wait,epoll_create是建立一個epoll控制代碼;epoll_ctl是註冊要監聽的事件型別;epoll_wait則是等待事件的產生。

對於第一個缺點,epoll的解決方案在epoll_ctl函式中。每次註冊新的事件到epoll控制代碼中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進核心,而不是在epoll_wait的時候重複拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。

對於第二個缺點,epoll的解決方案不像select或poll一樣每次都把current輪流加入fd對應的裝置等待佇列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)併為每個fd指定一個回撥函式,當裝置就緒,喚醒等待佇列上的等待者時,就會呼叫這個回撥函式,而這個回撥函式會把就緒的fd加入一個就緒連結串列)。epoll_wait的工作實際上就是在這個就緒連結串列中檢視有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select實現中的第7步是類似的)。 (Linux 2.6核心之後這個連結串列就使用紅黑樹來實現達到插入和查詢速度快的目的)

對於第三個缺點,epoll沒有這個限制,它所支援的FD上限是最大可以開啟檔案的數目,這個數字一般遠大於2048,舉個例子,在1GB記憶體的機器上大約是10萬左右,具體數目可以cat /proc/sys/fs/file-max察看,一般來說這個數目和系統記憶體關係很大。

總結:

(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內部定義的等待佇列)。這也能節省不少的開銷。

參考資料: