I/O復(fù)用 select/poll/epoll

轉(zhuǎn)載自I/O復(fù)用 select/poll/epoll

一、概述

I/O復(fù)用使得程序能同時(shí)監(jiān)聽(tīng)多個(gè)文件描述符,這對(duì)提高程序的性能至關(guān)重要。

I/O復(fù)用雖然能同時(shí)監(jiān)聽(tīng)多個(gè)文件描述符,但本身是阻塞的。并且當(dāng)多個(gè)描述符同時(shí)就緒時(shí),如果不采用額外的措施,程序就只能按照順序依次處理其中的每個(gè)文件描述符,使得服務(wù)器程序看起來(lái)像串行工作一樣。要實(shí)現(xiàn)并發(fā),只能使用多進(jìn)程或者多線程等方式實(shí)現(xiàn)。

linux下實(shí)現(xiàn)I/O復(fù)用的系統(tǒng)調(diào)用主要有select、poll、epoll。

二、select

select系統(tǒng)調(diào)用的用途是,在一段時(shí)間內(nèi),監(jiān)聽(tīng)用戶感興趣的文件描述符上可寫(xiě)和異常事件。

#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
            const struct timeval *timeout);

1、nfds

nfds參數(shù)指被監(jiān)聽(tīng)文件描述符的總數(shù),通常設(shè)為做大文件描述符值加1。

2、readfds, writefds, exceptfds

分別指向可讀,可寫(xiě)和異常事件對(duì)應(yīng)的文件描述符集合。select通過(guò)這三個(gè)參數(shù)傳入自己感興趣的文件描述符,內(nèi)核修改它們來(lái)通知應(yīng)用程序哪些文件描述符已經(jīng)就緒。

fd_set結(jié)構(gòu)體僅包含一個(gè)整型數(shù)組,該數(shù)組每一個(gè)元素的每一位都標(biāo)記一個(gè)文件描述符,是一個(gè)bitmap。其最大文件描述符的數(shù)量由FD_SETSIZE指定,這限制了select能同時(shí)處理的文件描述符總量。

3、timeout

超時(shí)時(shí)間。該參數(shù)與poll、epoll不同,是一個(gè)timeval結(jié)構(gòu)類型指針。可以精確到微妙。如果給timeval中tv_sec和tv_usec傳遞的都是0,則表示立即返回,非阻塞。如果給timeout傳遞null,則select將一直阻塞,直到某個(gè)文件描述符就緒。

4、返回值

成功時(shí),返回就緒(可讀、可寫(xiě)和異常)文件描述符總數(shù)。超時(shí)時(shí)間內(nèi)沒(méi)有任何文件返回0。失敗返回-1,并設(shè)置errno。如果等待期間,接收到信號(hào),則立刻返回-1,并設(shè)置errno為EINTR。

三、poll

poll和select相似,也是在指定時(shí)間內(nèi)輪詢一定數(shù)量的文件描述符,以測(cè)試其中是否有就緒者。poll原型如下:

#include<poll.h>
int poll(struct pollfd * nfds_t nfds, int timeout);

struct pollfd{
  int fd;
  short events;
  short revents;
};

1、fds

fds參數(shù)是一個(gè)pollfd結(jié)構(gòu)類型的數(shù)組,它指定了所有我們感興趣文件描述符上發(fā)生的可讀、可寫(xiě)和異常等事件。

pollfd中fd是文件描述符,events是注冊(cè)的事件,revents是實(shí)際發(fā)生的事件,由內(nèi)核填充,用來(lái)通知應(yīng)用程序fd上實(shí)際發(fā)生了哪些事件。

2、nfds

指定被監(jiān)聽(tīng)事件幾何fds的大小
typedef unsigned long int nfds_t;

3、timeout

poll的超時(shí)時(shí)間。單位是毫秒,當(dāng)值為-1時(shí),poll調(diào)用將永遠(yuǎn)阻塞,直到某個(gè)事件發(fā)生;當(dāng)timeout為0,poll將立即返回。

4、返回值

返回值與select相同

四、epoll

epoll是linux特有的I/O復(fù)用函數(shù)。它在實(shí)現(xiàn)和使用上與select、poll有很大差別。

  • 用一組函數(shù)來(lái)完成任務(wù),而不是單個(gè)函數(shù)。
  • 把用戶關(guān)系的文件描述符上的事件放在內(nèi)核里的一個(gè)事件表中,無(wú)需像select、poll那樣每次調(diào)用都重復(fù)傳入文件描述符和事件集。
  • 需要一個(gè)額外的文件描述符,來(lái)唯一標(biāo)識(shí)內(nèi)核中這個(gè)事件表。這個(gè)文件描述符由epoll_create函數(shù)創(chuàng)建。
1、epoll_create
#include<sys/epoll.h>
int epoll_create(int size);

size參數(shù)現(xiàn)在不起作用,只是給內(nèi)核一個(gè)提示,告訴它事件表需要多大。該函數(shù)返回的文件描述符用于其它epoll函數(shù)的第一個(gè)參數(shù),以指定要訪問(wèn)的內(nèi)核事件表。

2、epoll_ctl
#include<sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event);

/*op有三種類型:
EPOLL_CTL_ADD,往事件表中注冊(cè)fd上的事件
EPOLL_CTL_MOD,修改fd上的注冊(cè)事件
EPOLL_CTL_DEL,刪除fd上的注冊(cè)事件*/

struct epoll_event {
  __uint32_t events;  /* Epoll events */
  epoll_data_t data;  /* User data variable */
};

3、epoll_wait

是epoll系統(tǒng)調(diào)用主要的接口函數(shù)。它在一段時(shí)間內(nèi)等待一組文件描述符上的事件,其原型如下:

#include<sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

  • timeout含義與poll相同
  • 函數(shù)返回的含義與select/poll相同
  • maxevents表示最多監(jiān)聽(tīng)多少個(gè)事件

epoll_wait如果檢測(cè)到事件,就將所有就緒事件從內(nèi)核事件表中復(fù)制到它的第二個(gè)參數(shù)events指向的數(shù)組中。這個(gè)數(shù)組只用于輸出epoll_wait檢測(cè)到的事件。而不像select/poll那樣即用于輸入,也用于輸出。因此提高了應(yīng)用程序索引就緒文件的描述符的效率。

4、LT和ET模式

epoll對(duì)文件描述符的操作有兩種模式:LT(level trigger,電平觸發(fā))和ET(edge trigger)

LT模式是默認(rèn)的工作模式,此模式下epoll相當(dāng)于一個(gè)效率較高的poll。當(dāng)往epoll內(nèi)核事件表中注冊(cè)一個(gè)文件描述符上的EPOLLET事件時(shí),epoll將采用ET模式,ET是epoll高效的規(guī)則模式。

對(duì)于LT,epoll_wait檢測(cè)到其上有事件發(fā)生并將此事件通知應(yīng)用程序后,應(yīng)用程序可以不立即處理該事件。因此應(yīng)用程序下次調(diào)用epoll_wait時(shí),還將向應(yīng)用程序通知該事件,直至該事件被處理。

對(duì)于ET,epoll_wait檢測(cè)到其上有事件發(fā)生并將此事通知應(yīng)用程序后,應(yīng)用程序必須立即處理該事件,后續(xù)epoll_wait將不再向應(yīng)用程序通知這一事件。ET模式在很大程度上降低了同一事件被重復(fù)觸發(fā)次數(shù),因此效率比LT高。每個(gè)使用ET模式的文件描述符都應(yīng)該是非阻塞的。如果文件描述符是阻塞的,那么讀寫(xiě)操作都會(huì)因沒(méi)有后續(xù)事件而處于阻塞狀態(tài)。

五、三組I/O復(fù)用函數(shù)比較

  • 三者都由timeout參數(shù)指定超時(shí)時(shí)間,直到一個(gè)或多個(gè)文件描述符上有時(shí)間發(fā)生時(shí)返回,返回值就是文件描述符的數(shù)量。返回0表示沒(méi)有事件發(fā)生。

  • 1、select沒(méi)有文件描述符與事件綁定,它僅僅是一個(gè)文件描述符的幾何,因此select需要提供三個(gè)此類參數(shù)來(lái)區(qū)分傳入的可讀,可寫(xiě),異常事件。一方面使得其不能處理更多事件,另一方面內(nèi)核對(duì)fd_set集合在線修改,應(yīng)用程序下次需要重置此三個(gè)fd_set集合。

  • 2、poll則比select聰明些,它把文件描述符和事件都定義在其中,任何任何事件都被統(tǒng)一處理,從而簡(jiǎn)化了編程接口。由于select/poll每次都需要返回整個(gè)用戶注冊(cè)事件集合,應(yīng)用程序索引文件描述符時(shí)間復(fù)雜度為O(n)

  • 3、epoll在內(nèi)核維護(hù)一個(gè)事件表,并提供epoll_ctl來(lái)控制向其中添加、刪除、修改事件。這樣,epoll調(diào)用直接從該內(nèi)核事件表中取得用戶注冊(cè)事件,從而無(wú)需反復(fù)從用戶空間讀入這些事件。epoll_wait系統(tǒng)調(diào)用的events參數(shù)僅用來(lái)返回就緒事件,使得應(yīng)用程序索引就緒事件描述符的時(shí)間復(fù)雜度為O(1)

  • 4、poll/epoll分別采用nfds和maxevents參數(shù)指定最多監(jiān)聽(tīng)的文件描述符合事件。這兩個(gè)數(shù)值都能達(dá)到系統(tǒng)允許的最大最大文件描述符數(shù),65536。而select允許最大監(jiān)聽(tīng)最大文件描述符數(shù)量通常有限制。

  • 5、select/poll都支持相對(duì)低效的LT模式,epoll可在ET模式下工作,epoll還支持EPOLLONESHOT事件

  • 6、活動(dòng)連接比較多是,epoll效率未必比select、poll高,因?yàn)榇藭r(shí)回調(diào)函數(shù)被觸發(fā)過(guò)于頻繁。epoll適用于連接數(shù)量較多,但活動(dòng)連接相對(duì)較少的情況。

  • 7、select,poll,epoll本質(zhì)上都是同步I/O,因?yàn)樗麄兌夹枰谧x寫(xiě)事件就緒后自己負(fù)責(zé)進(jìn)行讀寫(xiě),也就是說(shuō)這個(gè)讀寫(xiě)過(guò)程是阻塞的,而異步I/O則無(wú)需自己負(fù)責(zé)進(jìn)行讀寫(xiě),異步I/O的實(shí)現(xiàn)會(huì)負(fù)責(zé)把數(shù)據(jù)從內(nèi)核拷貝到用戶空間。

參考文獻(xiàn):
1、Linux IO模式及 select、poll、epoll詳解
2、Anker—工作學(xué)習(xí)筆記
3、細(xì)說(shuō)select、poll和epoll之間的區(qū)別與優(yōu)缺點(diǎn)
4、select,poll,epoll優(yōu)缺點(diǎn)及比較

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

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