IO多路復用select,epoll,poll筆記

一、IO多路復用的模型

圖1

? ? ? ? 當用戶進程調(diào)用了select,那么整個進程會被block,而同時,kernel會“監(jiān)視”所有select負責的socket(一個管理多個socket連接),當任何一個socket中的數(shù)據(jù)準備好了,select就會返回。這個時候用戶進程再調(diào)用read操作,將數(shù)據(jù)從kernel拷貝到用戶進程。所以,I/O 多路復用的特點是通過一種機制實現(xiàn)一個進程能同時等待多個文件描述符,而這些文件描述符其中的任意一個進入讀就緒狀態(tài),select()函數(shù)就可以返回。select,poll,epoll都是IO多路復用的機制。I/O多路復用就是通過一種機制。但select,poll,epoll本質(zhì)上都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責從內(nèi)核進行讀寫,也就是說這個讀寫過程是阻塞的,而異步I/O則無需自己負責進行讀寫,異步I/O的實現(xiàn)會負責把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。

1、select

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

? ? ? ? select 函數(shù)監(jiān)視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調(diào)用后select函數(shù)會阻塞,直到有描述符就緒(有數(shù)據(jù) 可讀、可寫、或者有except),或者超時(timeout指定等待時間,如果立即返回設為null即可),函數(shù)返回。當select函數(shù)返回后,可以 通過遍歷fdset,來找到就緒的描述符。

? ? ? ? select時間復雜度O(n),它僅僅知道了,有I/O事件發(fā)生了,卻并不知道是哪那幾個流(可能有一個,多個,甚至全部),我們只能無差別輪詢所有流,找出能讀出數(shù)據(jù),或者寫入數(shù)據(jù)的流,對他們進行操作。所以select具有O(n)的無差別輪詢復雜度,同時處理的流越多,無差別輪詢時間就越長。

這樣所帶來的缺點是:

(1) 單個進程可監(jiān)視的fd數(shù)量被限制,即能監(jiān)聽端口的大小有限。

? ? ? ?一般來說這個數(shù)目和系統(tǒng)內(nèi)存關系很大,具體數(shù)目可以cat /proc/sys/fs/file-max察看。32位機默認是1024個。64位機默認是2048.

(2) 對socket進行掃描時是線性掃描,即采用輪詢的方法,效率較低

?????? 當套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調(diào)度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費很多CPU時間。如果能給套接字注冊某個回調(diào)函數(shù),當他們活躍時,自動完成相關操作,那就避免了輪詢,這正是epoll與kqueue做的。

(3)需要維護一個用來存放大量fd的數(shù)據(jù)結構,這樣會使得用戶空間和內(nèi)核空間在傳遞該結構時復制開銷大

2、poll

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

不同與select使用三個位圖來表示三個fdset的方式,poll使用一個 pollfd的指針實現(xiàn)。

struct pollfd {

int fd; /* file descriptor */

short events; /* requested events to watch */

short revents; /* returned events witnessed */

};

? ? ? ?pollfd結構包含了要監(jiān)視的event和發(fā)生的event,不再使用select“參數(shù)-值”傳遞的方式。同時,pollfd并沒有最大數(shù)量限制(但是數(shù)量過大后性能也是會下降),原因是它是基于鏈表來存儲的。 和select函數(shù)一樣,poll返回后,需要輪詢pollfd來獲取就緒的描述符。

? ? ? ?poll本質(zhì)上和select沒有區(qū)別,時間復雜度O(n),它將用戶傳入的數(shù)組拷貝到內(nèi)核空間,然后查詢每個fd對應的設備狀態(tài),如果設備就緒則在設備等待隊列中加入一項并繼續(xù)遍歷,如果遍歷完所有fd后沒有發(fā)現(xiàn)就緒設備,則掛起當前進程,直到設備就緒或者主動超時,被喚醒后它又要再次遍歷fd。這個過程經(jīng)歷了多次無謂的遍歷。

但是同樣有一個缺點:

(1)大量的fd的數(shù)組被整體復制于用戶空間和內(nèi)核地址空間之間,而不管這樣的復制是不是有意義。? ? ? ? ? ? ?

(2)? ?poll還有一個特點是“水平觸發(fā)”,如果報告了fd后,沒有被處理,那么下次poll時會再次報告該fd。

? ? ? ?從上面看,select和poll都需要在返回后,通過遍歷文件描述符來獲取已經(jīng)就緒的socket。事實上,同時連接的大量客戶端在一時刻可能只有很少的處于就緒狀態(tài),因此隨著監(jiān)視的描述符數(shù)量的增長,其效率也會線性下降。

3、epoll

? ? ? ?epoll是在2.6內(nèi)核中提出的,是之前的select和poll的增強版本。相對于select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內(nèi)核的一個事件表中,這樣在用戶空間和內(nèi)核空間的copy只需一次。

int epoll_create(int size);//創(chuàng)建一個epoll的句柄,size用來告訴內(nèi)核這個監(jiān)聽的數(shù)目一共有多大

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

1. int epoll_create(int size);創(chuàng)建一個epoll的句柄,size用來告訴內(nèi)核這個監(jiān)聽的數(shù)目一共有多大,這個參數(shù)不同于select()中的第一個參數(shù),給出最大監(jiān)聽的fd+1的值,參數(shù)size并不是限制了epoll所能監(jiān)聽的描述符最大個數(shù),只是對內(nèi)核初始分配內(nèi)部數(shù)據(jù)結構的一個建議。當創(chuàng)建好epoll句柄后,它就會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調(diào)用close()關閉,否則可能導致fd被耗盡。

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

函數(shù)是對指定描述符fd執(zhí)行op操作。

? epfd:是epoll_create()的返回值。

? op:表示op操作,用三個宏來表示:添加EPOLL_CTL_ADD,刪除EPOLL_CTL_DEL,? ? ? ? ? ? ? ? ? 修改EPOLL_CTL_MOD。分別添加、刪除和修改對fd的監(jiān)聽事件。

? fd:是需要監(jiān)聽的fd(文件描述符)

? epoll_event:是告訴內(nèi)核需要監(jiān)聽什么事

3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);等待epfd上的io事件,最多返回maxevents個事件。參數(shù)events用來從內(nèi)核得到事件的集合,maxevents告之內(nèi)核這個events有多大,這個maxevents的值不能大于創(chuàng)建epoll_create()時的size,參數(shù)timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數(shù)返回需要處理的事件數(shù)目,如返回0表示已超時。

? ? ?epoll可以理解為event poll,不同于忙輪詢和無差別輪詢,epoll會把哪個流發(fā)生了怎樣的I/O事件通知我們。所以我們說epoll實際上是事件驅動(每個事件關聯(lián)上fd)的,此時我們對這些流的操作都是有意義的。(復雜度降低到了O(1)),在 select/poll中,進程只有在調(diào)用一定的方法后,內(nèi)核才對所有監(jiān)視的文件描述符進行掃描,而epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基于某個文件描述符就緒時,內(nèi)核會采用類似callback的回調(diào)機制,迅速激活這個文件描述符,當進程調(diào)用epoll_wait() 時便得到通知。(此處去掉了遍歷文件描述符,而是通過監(jiān)聽回調(diào)的的機制。這正是epoll的魅力所在,epoll_wait每次讀取到的都是那種已經(jīng)準備好數(shù)據(jù)。)

epoll的優(yōu)點:

1、沒有最大并發(fā)連接的限制,能打開的FD的上限遠大于1024(1G的內(nèi)存上能監(jiān)聽約10萬個端口);

2、效率提升,不是輪詢的方式,不會隨著FD數(shù)目的增加效率下降。只有活躍可用的FD才會調(diào)用callback函數(shù);即Epoll最大的優(yōu)點就在于它只管你“活躍”的連接,而跟連接總數(shù)無關,因此在實際的網(wǎng)絡環(huán)境中,Epoll的效率就會遠遠高于select和poll。

3、 內(nèi)存拷貝,利用mmap()文件映射內(nèi)存加速與內(nèi)核空間的消息傳遞;即epoll使用mmap減少復制開銷。

LT模式與ET模式

? ? ? epoll有EPOLLLT和EPOLLET兩種觸發(fā)模式,LT是默認的模式,ET是“高速”模式。LT模式下,只要這個fd還有數(shù)據(jù)可讀,每次 epoll_wait都會返回它的事件,提醒用戶程序去操作,而在ET(邊緣觸發(fā))模式中,它只會提示一次,直到下次再有數(shù)據(jù)流入之前都不會再提示了,無 論fd中是否還有數(shù)據(jù)可讀。所以在ET模式下,read一個fd的時候一定要把它的buffer讀光,也就是說一直讀到read的返回值小于請求值,或者 遇到EAGAIN錯誤。還有一個特點是,epoll使用“事件”的就緒通知方式,通過epoll_ctl注冊fd,一旦該fd就緒,內(nèi)核就會采用類似callback的回調(diào)機制來激活該fd,epoll_wait便可以收到通知。

epoll為什么要有EPOLLET觸發(fā)模式?

? ? ? ? 如果采用EPOLLLT模式的話,系統(tǒng)中一旦有大量你不需要讀寫的就緒文件描述符,它們每次調(diào)用epoll_wait都會返回,這樣會大大降低處理程序檢索自己關心的就緒文件描述符的效率.。而采用EPOLLET這種邊沿觸發(fā)模式的話,當被監(jiān)控的文件描述符上有可讀寫事件發(fā)生時,epoll_wait()會通知處理程序去讀寫。如果這次沒有把數(shù)據(jù)全部讀寫完(如讀寫緩沖區(qū)太小),那么下次調(diào)用epoll_wait()時,它不會通知你,也就是它只會通知你一次,直到該文件描述符上出現(xiàn)第二次可讀寫事件才會通知你?。。?b>這種模式比水平觸發(fā)效率高,系統(tǒng)不會充斥大量你不關心的就緒文件描述符

epoll對poll方式的改進

epoll既然是對select和poll的改進,那epoll都是怎么解決的呢?在此之前,我們先看一下epoll和select和poll的調(diào)用接口上的不同,select和poll都只提供了一個函數(shù)——select或者poll函數(shù)。而epoll提供了三個函數(shù),epoll_create,epoll_ctl和epoll_wait,epoll_create是創(chuàng)建一個epoll句柄;epoll_ctl是注冊要監(jiān)聽的事件類型;epoll_wait則是等待事件的產(chǎn)生。

對于第一個缺點,epoll的解決方案在epoll_ctl函數(shù)中。每次注冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把所有的fd拷貝進內(nèi)核,而不是在epoll_wait的時候重復拷貝。epoll保證了每個fd在整個過程中只會拷貝一次。

對于第二個缺點,epoll的解決方案不像select或poll一樣每次都把當前進程輪流加入fd對應的設備等待隊列中,而只在epoll_ctl時把當前進程掛一遍(這一遍必不可少)并為每個fd指定一個回調(diào)函數(shù),當設備就緒,喚醒等待隊列上的等待者時,就會調(diào)用這個回調(diào)函數(shù),而這個回調(diào)函數(shù)會把就緒的fd加入一個就緒鏈表)。epoll_wait的工作實際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用schedule_timeout()實現(xiàn)睡一會,判斷一會的效果,和select實現(xiàn)中的第7步是類似的)。

  對于第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大可以打開文件的數(shù)目,這個數(shù)字一般遠大于2048,舉個例子,在1GB內(nèi)存的機器上大約是10萬左右,具體數(shù)目可以cat /proc/sys/fs/file-max察看,一般來說這個數(shù)目和系統(tǒng)內(nèi)存關系很大。

參考:

https://www.cnblogs.com/aspirant/p/9166944.html

https://cloud.tencent.com/developer/article/1404087

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容