select * 映射错误_高性能IO模型分析-浅析Select、Poll、Epoll机制(三)
本章(第三章)內(nèi)容其實(shí)和第二章內(nèi)容,都是第一章內(nèi)容的延伸。第二章內(nèi)容是第一章內(nèi)容的延伸,本章內(nèi)容則是第一章內(nèi)容再往底層方面的延伸,也是面試中考察網(wǎng)絡(luò)方面知識(shí)時(shí),可能會(huì)問(wèn)到的幾個(gè)點(diǎn)。
select、poll、epoll都是I/O多路復(fù)用的機(jī)制。I/O多路復(fù)用就是通過(guò)一種機(jī)制,一個(gè)進(jìn)程可以監(jiān)視多個(gè)文件描述符,一旦某個(gè)描述符就緒(讀就緒或?qū)懢途w),能夠通知程序進(jìn)行相應(yīng)的讀寫(xiě)操作 。
但是,select,poll,epoll本質(zhì)還是同步I/O(I/O多路復(fù)用本身就是同步IO)的范疇,因?yàn)樗鼈兌夹枰谧x寫(xiě)事件就緒后線(xiàn)程自己進(jìn)行讀寫(xiě),讀寫(xiě)的過(guò)程阻塞的。而異步I/O的實(shí)現(xiàn)是系統(tǒng)會(huì)把負(fù)責(zé)把數(shù)據(jù)從內(nèi)核空間拷貝到用戶(hù)空間,無(wú)需線(xiàn)程自己再進(jìn)行阻塞的讀寫(xiě),內(nèi)核已經(jīng)準(zhǔn)備完成。
一、Select機(jī)制
API簡(jiǎn)介
linux系統(tǒng)中/usr/include/sys/select.h文件中對(duì)select方法的定義如下:
/* fd_set for select and pselect. */typedef struct { /* XPG4.2 requires this member name. Otherwise avoid the name from the global namespace. */ #ifdef __USE_XOPEN __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS]; # define __FDS_BITS(set) ((set)->fds_bits) #else __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS]; # define __FDS_BITS(set) ((set)->__fds_bits) #endif } fd_set;/* Check the first NFDS descriptors each in READFDS (if not NULL) for read readiness, in WRITEFDS (if not NULL) for write readiness, and in EXCEPTFDS (if not NULL) for exceptional conditions. If TIMEOUT is not NULL, time out after waiting the interval specified therein. Returns the number of ready descriptors, or -1 for errors. This function is a cancellation point and therefore not marked with __THROW. */extern int select (int __nfds, fd_set *__restrict __readfds, fd_set *__restrict __writefds, fd_set *__restrict __exceptfds, struct timeval *__restrict __timeout);int __nfds是fd_set中最大的描述符+1,當(dāng)調(diào)用select時(shí),內(nèi)核態(tài)會(huì)判斷fd_set中描述符是否就緒,__nfds告訴內(nèi)核最多判斷到哪一個(gè)描述符。
__readfds、__writefds、__exceptfds都是結(jié)構(gòu)體fd_set,fd_set可以看作是一個(gè)描述符的集合。 select函數(shù)中存在三個(gè)fd_set集合,分別代表三種事件,readfds表示讀描述符集合,writefds表示讀描述符集合,exceptfds表示異常描述符集合。當(dāng)對(duì)應(yīng)的fd_set = NULL時(shí),表示不監(jiān)聽(tīng)該類(lèi)描述符。
timeval __timeout用來(lái)指定select的工作方式,即當(dāng)文件描述符尚未就緒時(shí),select是永遠(yuǎn)等下去,還是等待一定的時(shí)間,或者是直接返回
函數(shù)返回值int表示: 就緒描述符的數(shù)量,如果為-1表示產(chǎn)生錯(cuò)誤 。
運(yùn)行機(jī)制
Select會(huì)將全量fd_set從用戶(hù)空間拷貝到內(nèi)核空間,并注冊(cè)回調(diào)函數(shù), 在內(nèi)核態(tài)空間來(lái)判斷每個(gè)請(qǐng)求是否準(zhǔn)備好數(shù)據(jù) 。select在沒(méi)有查詢(xún)到有文件描述符就緒的情況下,將一直阻塞(I/O多路服用中提過(guò):select是一個(gè)阻塞函數(shù))。如果有一個(gè)或者多個(gè)描述符就緒,那么select將就緒的文件描述符置位,然后select返回。返回后,由程序遍歷查看哪個(gè)請(qǐng)求有數(shù)據(jù)。
Select的缺陷
- 每次調(diào)用select,都需要把fd集合從用戶(hù)態(tài)拷貝到內(nèi)核態(tài),fd越多開(kāi)銷(xiāo)則越大;
- 每次調(diào)用select都需要在內(nèi)核遍歷傳遞進(jìn)來(lái)的所有fd,這個(gè)開(kāi)銷(xiāo)在fd很多時(shí)也很大
- select支持的文件描述符數(shù)量有限,默認(rèn)是1024。參見(jiàn)/usr/include/linux/posix_types.h中的定義:
# define __FD_SETSIZE 1024
二、Poll機(jī)制
API簡(jiǎn)介
linux系統(tǒng)中/usr/include/sys/poll.h文件中對(duì)poll方法的定義如下:
/* Data structure describing a polling request. */struct pollfd { int fd; /* File descriptor to poll. */ short int events; /* Types of events poller cares about. */ short int revents; /* Types of events that actually occurred. */ };/* Poll the file descriptors described by the NFDS structures starting at FDS. If TIMEOUT is nonzero and not -1, allow TIMEOUT milliseconds for an event to occur; if TIMEOUT is -1, block until an event occurs. Returns the number of file descriptors with events, zero if timed out, or -1 for errors. This function is a cancellation point and therefore not marked with __THROW. */extern int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout);__fds參數(shù)時(shí)Poll機(jī)制中定義的結(jié)構(gòu)體pollfd,用來(lái)指定一個(gè)需要監(jiān)聽(tīng)的描述符。結(jié)構(gòu)體中fd為需要監(jiān)聽(tīng)的文件描述符,events為需要監(jiān)聽(tīng)的事件類(lèi)型,而revents為經(jīng)過(guò)poll調(diào)用之后返回的事件類(lèi)型,在調(diào)用poll的時(shí)候,一般會(huì)傳入一個(gè)pollfd的結(jié)構(gòu)體數(shù)組,數(shù)組的元素個(gè)數(shù)表示監(jiān)控的描述符個(gè)數(shù)。
__nfds和__timeout參數(shù)都和Select機(jī)制中的同名參數(shù)含義類(lèi)似
運(yùn)行機(jī)制
poll的實(shí)現(xiàn)和select非常相似,只是描述fd集合的方式不同,poll使用pollfd結(jié)構(gòu)代替select的fd_set(網(wǎng)上講:類(lèi)似于位圖)結(jié)構(gòu),其他的本質(zhì)上都差不多。所以Poll機(jī)制突破了Select機(jī)制中的文件描述符數(shù)量最大為1024的限制。
Poll的缺陷
Poll機(jī)制相較于Select機(jī)制中,解決了文件描述符數(shù)量上限為1024的缺陷。但另外兩點(diǎn)缺陷依然存在:
- 每次調(diào)用poll,都需要把fd集合從用戶(hù)態(tài)拷貝到內(nèi)核態(tài),fd越多開(kāi)銷(xiāo)則越大;
- 每次調(diào)用poll,都需要在內(nèi)核遍歷傳遞進(jìn)來(lái)的所有fd,這個(gè)開(kāi)銷(xiāo)在fd很多時(shí)也很大
三、Epoll機(jī)制
Epoll在Linux2.6內(nèi)核正式提出,是基于事件驅(qū)動(dòng)的I/O方式。相對(duì)于select來(lái)說(shuō),epoll沒(méi)有描述符個(gè)數(shù)限制;使用一個(gè)文件描述符管理多個(gè)描述符,將用戶(hù)關(guān)心的文件描述符的事件存放到內(nèi)核的一個(gè)事件表中,通過(guò)內(nèi)存映射,使其在用戶(hù)空間也可直接訪(fǎng)問(wèn),省去了拷貝帶來(lái)的資源消耗。
API簡(jiǎn)介
linux系統(tǒng)中/usr/include/sys/epoll.h文件中有如下方法:
/* Creates an epoll instance. Returns an fd for the new instance. The "size" parameter is a hint specifying the number of file descriptors to be associated with the new instance. The fd returned by epoll_create() should be closed with close(). */extern int epoll_create (int __size) __THROW;/* Manipulate an epoll instance "epfd". Returns 0 in case of success, -1 in case of error ( the "errno" variable will contain the specific error code ) The "op" parameter is one of the EPOLL_CTL_* constants defined above. The "fd" parameter is the target of the operation. The "event" parameter describes which events the caller is interested in and any associated user data. */extern int epoll_ctl (int __epfd, int __op, int __fd, struct epoll_event *__event) __THROW;/* Wait for events on an epoll instance "epfd". Returns the number of triggered events returned in "events" buffer. Or -1 in case of error with the "errno" variable set to the specific error code. The "events" parameter is a buffer that will contain triggered events. The "maxevents" is the maximum number of events to be returned ( usually size of "events" ). The "timeout" parameter specifies the maximum wait time in milliseconds (-1 == infinite). This function is a cancellation point and therefore not marked with __THROW. */extern int epoll_wait (int __epfd, struct epoll_event *__events, int __maxevents, int __timeout);epoll_create函數(shù):創(chuàng)建一個(gè)epoll實(shí)例并返回,該實(shí)例可以用于監(jiān)控__size個(gè)文件描述符
epoll_ctl函數(shù):向epoll中注冊(cè)事件,該函數(shù)如果調(diào)用成功返回0,否則返回-1。
- __epfd為epoll_create返回的epoll實(shí)例
- __op表示要進(jìn)行的操作
- __fd為要進(jìn)行監(jiān)控的文件描述符
- __event要監(jiān)控的事件
epoll_wait函數(shù):類(lèi)似與select機(jī)制中的select函數(shù)、poll機(jī)制中的poll函數(shù),等待內(nèi)核返回監(jiān)聽(tīng)描述符的事件產(chǎn)生。該函數(shù)返回已經(jīng)就緒的事件的數(shù)量,如果為-1表示出錯(cuò)。
- __epfd為epoll_create返回的epoll實(shí)例
- __events數(shù)組為 epoll_wait要返回的已經(jīng)產(chǎn)生的事件集合
- __maxevents為希望返回的最大的事件數(shù)量(通常為_(kāi)_events的大小)
- __timeout和select、poll機(jī)制中的同名參數(shù)含義相同
運(yùn)行機(jī)制
epoll操作過(guò)程需要上述三個(gè)函數(shù),也正是通過(guò)三個(gè)函數(shù)完成Select機(jī)制中一個(gè)函數(shù)完成的事情,解決了Select機(jī)制的三大缺陷。epoll的工作機(jī)制更為復(fù)雜,我們就解釋一下,它是如何解決Select機(jī)制的三大缺陷的。
工作模式
相較于Select和Poll,Epoll內(nèi)部還分為兩種工作模式: LT水平觸發(fā)(level trigger)和ET邊緣觸發(fā)(edge trigger)。
- LT模式: 默認(rèn)的工作模式,即當(dāng)epoll_wait檢測(cè)到某描述符事件就緒并通知應(yīng)用程序時(shí),應(yīng)用程序可以不立即處理該事件;事件會(huì)被放回到就緒鏈表中,下次調(diào)用epoll_wait時(shí),會(huì)再次通知此事件。
- ET模式: 當(dāng)epoll_wait檢測(cè)到某描述符事件就緒并通知應(yīng)用程序時(shí),應(yīng)用程序必須立即處理該事件。如果不處理,下次調(diào)用epoll_wait時(shí),不會(huì)再次響應(yīng)并通知此事件。
由于上述兩種工作模式的區(qū)別,LT模式同時(shí)支持block和no-block socket兩種,而ET模式下僅支持no-block socket。即epoll工作在ET模式的時(shí)候,必須使用非阻塞套接口,以避免由于一個(gè)fd的阻塞I/O操作把多個(gè)處理其他文件描述符的任務(wù)餓死。ET模式在很大程度上減少了epoll事件被重復(fù)觸發(fā)的次數(shù),因此效率要比LT模式高。
Epoll的優(yōu)點(diǎn)
- 使用內(nèi)存映射技術(shù),節(jié)省了用戶(hù)態(tài)和內(nèi)核態(tài)間數(shù)據(jù)拷貝的資源消耗;
- 通過(guò)每個(gè)fd定義的回調(diào)函數(shù)來(lái)實(shí)現(xiàn)的,只有就緒的fd才會(huì)執(zhí)行回調(diào)函數(shù)。I/O的效率不會(huì)隨著監(jiān)視fd的數(shù)量的增長(zhǎng)而下降;
- 文件描述符數(shù)量不再受限;
四、Select、Poll、Epoll機(jī)制的對(duì)比
下圖主流I/O多路復(fù)用機(jī)制的benchmark:
當(dāng)并發(fā)fd較小時(shí),Select、Poll、Epoll的響應(yīng)效率想差無(wú)幾,甚至Select和Poll更勝一籌。但是當(dāng)并發(fā)連接(fd)較多時(shí),Epoll的優(yōu)勢(shì)便真正展現(xiàn)出來(lái)。
下面一張表格總結(jié)三種模式的區(qū)別:
通過(guò)上述的一些總結(jié),希望我們對(duì)I/O多路復(fù)用的Select、Poll、Epoll機(jī)制有一個(gè)更深刻的認(rèn)識(shí)。也要明白為什么epoll會(huì)成為L(zhǎng)inux平臺(tái)下實(shí)現(xiàn)高性能網(wǎng)絡(luò)服務(wù)器的首選I/O多路復(fù)用機(jī)制。
五、Epoll的使用場(chǎng)景
上面的文章中已經(jīng)不斷介紹了Epoll機(jī)制的優(yōu)勢(shì),又提到它是Linux平臺(tái)下實(shí)現(xiàn)高性能網(wǎng)絡(luò)服務(wù)器的首選I/O復(fù)用機(jī)制。實(shí)際工作中,我們?cè)谀睦飼?huì)用到它?怎么用呢?
比如下面代碼,就是我們使用高性能網(wǎng)絡(luò)框架Netty實(shí)現(xiàn)IM項(xiàng)目中對(duì)于netty的bossGroup和workerGroup以及serverChannel的配置
String os = System.getProperty("os.name");if(os.toLowerCase().startsWith("win") || os.toLowerCase().startsWith("mac")){ // 點(diǎn)開(kāi)NioEventLoopGroup的源碼,對(duì)于這個(gè)類(lèi)是這么注釋的 // MultithreadEventLoopGroup implementations which is used for NIO Selector based Channel bossGroup = new NioEventLoopGroup(1); workerGroup = new NioEventLoopGroup(4);}else{ // 點(diǎn)開(kāi)EpollEventLoopGroup的源碼,對(duì)于這個(gè)類(lèi)是這么注釋的 // EventLoopGroup which uses epoll under the covers. Because of this it only works on linux. bossGroup = new EpollEventLoopGroup(1); workerGroup = new EpollEventLoopGroup(4);}bootStrap = new ServerBootstrap();bootStrap.group(bossGroup,workerGroup);if(os.toLowerCase().startsWith("win") || os.toLowerCase().startsWith("mac")) { // NioServerSocketChannel implementation which uses NIO selector based implementation to accept new connections. bootStrap.channel(NioServerSocketChannel.class);}else{ // ServerSocketChannel implementation that uses linux EPOLL Edge-Triggered Mode for maximal performance. // 注意看注釋中的“l(fā)inux EPOLL Edge-Triggered Mode”,linux下ET模式的Epoll機(jī)制 bootStrap.channel(EpollServerSocketChannel.class);}看完這些,我們對(duì)Select、Poll、Epoll的了解是不是更多了一點(diǎn)。
至此,我們高性能IO模型分析的三篇文章已完結(jié)。如果能幫助到你,點(diǎn)個(gè)贊再走唄!
總結(jié)
以上是生活随笔為你收集整理的select * 映射错误_高性能IO模型分析-浅析Select、Poll、Epoll机制(三)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 查看git安装目录_一、Linux和Wi
- 下一篇: activty在哪个栈里面_第二篇Act