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
2 poll實作
poll的實作和select非常相似,隻是描述fd集合的方式不同,poll使用pollfd結構而不是select的fd_set結構,其他的都差不多。
關于select和poll的實作分析,可以參考下面幾篇博文:
<a href="http://blog.csdn.net/lizhiguo0532/article/details/6568964#comments" target="_blank">http://blog.csdn.net/lizhiguo0532/article/details/6568964#comments</a>
<a href="http://blog.csdn.net/lizhiguo0532/article/details/6568968" target="_blank">http://blog.csdn.net/lizhiguo0532/article/details/6568968</a>
<a href="http://blog.csdn.net/lizhiguo0532/article/details/6568969" target="_blank">http://blog.csdn.net/lizhiguo0532/article/details/6568969</a>
<a href="http://www.ibm.com/developerworks/cn/linux/l-cn-edntwk/index.html?ca=drs-" target="_blank">http://www.ibm.com/developerworks/cn/linux/l-cn-edntwk/index.html?ca=drs-</a>
<a href="http://linux.chinaunix.net/techdoc/net/2009/05/03/1109887.shtml" target="_blank">http://linux.chinaunix.net/techdoc/net/2009/05/03/1109887.shtml</a>
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步是類似的)。
對于第三個缺點,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内部定義的等待隊列)。這也能節省不少的開銷。
本文轉自 leejia1989 51CTO部落格,原文連結:http://blog.51cto.com/leejia/1407310,如需轉載請自行聯系原作者