轉(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)及比較