select/poll和epoll
在linux 沒有實(shí)現(xiàn)epoll事件驅(qū)動(dòng)機(jī)制之前,我們一般選擇用select或者poll等IO多路復(fù)用的方法來實(shí)現(xiàn)并發(fā)服務(wù)程序。在大數(shù)據(jù)疲吸、高并發(fā)、集群等一些名詞唱得火熱之年代,select和poll的用武之地越來越有限赃承,風(fēng)頭已經(jīng)被epoll占盡。
select()和poll() IO多路復(fù)用模型
select基本原理
select 函數(shù)監(jiān)視的文件描述符分3類悴侵,分別是writefds瞧剖、readfds、和exceptfds。調(diào)用后select函數(shù)會(huì)阻塞抓于,直到有描述符就緒(有數(shù)據(jù) 可讀做粤、可寫、或者有except)捉撮,或者超時(shí)(timeout指定等待時(shí)間怕品,如果立即返回設(shè)為null即可),函數(shù)返回巾遭。當(dāng)select函數(shù)返回后肉康,可以通過遍歷fdset,來找到就緒的描述符灼舍。
select的缺點(diǎn):
- 單個(gè)進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制吼和,通常是1024,當(dāng)然可以更改數(shù)量骑素,但由于select采用輪詢的方式掃描文件描述符炫乓,文件描述符數(shù)量越多,性能越差献丑;
在linux內(nèi)核頭文件中末捣,有這樣的定義:#define __FD_SETSIZE 1024
- 內(nèi)核 / 用戶空間內(nèi)存拷貝問題,select需要復(fù)制大量的句柄數(shù)據(jù)結(jié)構(gòu)阳距,產(chǎn)生巨大的開銷塔粒;
- select返回的是含有整個(gè)句柄的數(shù)組,應(yīng)用程序需要遍歷整個(gè)數(shù)組才能發(fā)現(xiàn)哪些句柄發(fā)生了事件筐摘;
- select的觸發(fā)方式是水平觸發(fā)卒茬,應(yīng)用程序如果沒有完成對一個(gè)已經(jīng)就緒的文件描述符進(jìn)行IO操作,那么之后每次select調(diào)用還是會(huì)將這些文件描述符通知進(jìn)程咖熟。.
poll基本原理
poll本質(zhì)上和select沒有區(qū)別圃酵,它將用戶傳入的數(shù)組拷貝到內(nèi)核空間,然后查詢每個(gè)fd對應(yīng)的設(shè)備狀態(tài)馍管,如果設(shè)備就緒則在設(shè)備等待隊(duì)列中加入一項(xiàng)并繼續(xù)遍歷郭赐,如果遍歷完所有fd后沒有發(fā)現(xiàn)就緒設(shè)備,則掛起當(dāng)前進(jìn)程确沸,直到設(shè)備就緒或者主動(dòng)超時(shí)捌锭,被喚醒后它又要再次遍歷fd。這個(gè)過程經(jīng)歷了多次無謂的遍歷罗捎。
poll的缺點(diǎn):
相比select模型观谦,poll使用鏈表保存文件描述符捉偏,因此沒有了監(jiān)視文件數(shù)量的限制泻红,但其他三個(gè)缺點(diǎn)依然存在讹躯。poll還有一個(gè)特點(diǎn)是“水平觸發(fā)”剩彬,如果報(bào)告了fd后母廷,沒有被處理,那么下次poll時(shí)會(huì)再次報(bào)告該fd抖拦。
epoll IO多路復(fù)用模型
由于epoll的實(shí)現(xiàn)機(jī)制與select/poll機(jī)制完全不同复颈,上面所說的 select的缺點(diǎn)在epoll上不復(fù)存在。epoll是在2.6內(nèi)核中提出的,是之前的select和poll的增強(qiáng)版本。相對于select和poll來說肢执,epoll更加靈活耻陕,沒有描述符限制想诅。epoll使用一個(gè)文件描述符管理多個(gè)描述符徘禁,將用戶關(guān)系的文件描述符的事件存放到內(nèi)核的一個(gè)事件表中骤菠,這樣在用戶空間和內(nèi)核空間的copy只需一次鹉戚。
epoll基本原理
epoll支持水平觸發(fā)和邊緣觸發(fā)赢底,最大的特點(diǎn)在于邊緣觸發(fā)粹庞,它只告訴進(jìn)程哪些fd剛剛變?yōu)榫途w態(tài)咳焚,并且只會(huì)通知一次革半。還有一個(gè)特點(diǎn)是,epoll使用“事件”的就緒通知方式商虐,通過epoll_ctl注冊fd权烧,一旦該fd就緒,內(nèi)核就會(huì)采用類似callback的回調(diào)機(jī)制來激活該fd般码,epoll_wait便可以收到通知宫静。
epoll的優(yōu)點(diǎn):
- 沒有最大并發(fā)連接的限制,能打開的FD的上限遠(yuǎn)大于1024(1G的內(nèi)存上能監(jiān)聽約10萬個(gè)端口)券时。
- 效率提升孤里,不是輪詢的方式,不會(huì)隨著FD數(shù)目的增加效率下降橘洞。只有活躍可用的FD才會(huì)調(diào)用callback函數(shù)捌袜;即Epoll最大的優(yōu)點(diǎn)就在于它只管你“活躍”的連接,而跟連接總數(shù)無關(guān)炸枣,因此在實(shí)際的網(wǎng)絡(luò)環(huán)境中虏等,Epoll的效率就會(huì)遠(yuǎn)遠(yuǎn)高于select和poll弄唧。
- 內(nèi)存拷貝,利用mmap()文件映射內(nèi)存加速與內(nèi)核空間的消息傳遞霍衫;即epoll使用mmap減少復(fù)制開銷套才。
epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。LT模式是默認(rèn)模式慕淡,LT模式與ET模式的區(qū)別如下:
- LT模式:當(dāng)epoll_wait檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序背伴,應(yīng)用程序可以不立即處理該事件。下次調(diào)用epoll_wait時(shí)峰髓,會(huì)再次響應(yīng)應(yīng)用程序并通知此事件傻寂。
- ET模式:當(dāng)epoll_wait檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序必須立即處理該事件携兵。如果不處理疾掰,下次調(diào)用epoll_wait時(shí),不會(huì)再次響應(yīng)應(yīng)用程序并通知此事件徐紧。
LT和ET模式的優(yōu)缺點(diǎn)
- LT模式
LT(level triggered)是缺省的工作方式静檬,并且同時(shí)支持block和no-block socket。在這種做法中并级,內(nèi)核告訴你一個(gè)文件描述符是否就緒了拂檩,然后你可以對這個(gè)就緒的fd進(jìn)行IO操作。如果你不作任何操作嘲碧,內(nèi)核還是會(huì)繼續(xù)通知你的稻励。- ET模式
ET(edge-triggered)是高速工作方式,只支持no-block socket愈涩。在這種模式下望抽,當(dāng)描述符從未就緒變?yōu)榫途w時(shí),內(nèi)核通過epoll告訴你履婉。然后它會(huì)假設(shè)你知道文件描述符已經(jīng)就緒煤篙,并且不會(huì)再為那個(gè)文件描述符發(fā)送更多的就緒通知,直到你做了某些操作導(dǎo)致那個(gè)文件描述符不再為就緒狀態(tài)了(比如毁腿,你在發(fā)送辑奈,接收或者接收請求,或者發(fā)送接收的數(shù)據(jù)少于一定量時(shí)導(dǎo)致了一個(gè)EWOULDBLOCK 錯(cuò)誤)狸棍。但是請注意身害,如果一直不對這個(gè)fd作IO操作(從而導(dǎo)致它再次變成未就緒),內(nèi)核不會(huì)發(fā)送更多的通知(only once)草戈。
ET模式在很大程度上減少了epoll事件被重復(fù)觸發(fā)的次數(shù)塌鸯,因此效率要比LT模式高。epoll工作在ET模式的時(shí)候唐片,必須使用非阻塞套接口丙猬,以避免由于一個(gè)文件句柄的阻塞讀/阻塞寫操作把處理多個(gè)文件描述符的任務(wù)餓死涨颜。- 在select/poll中,進(jìn)程只有在調(diào)用一定的方法后茧球,內(nèi)核才對所有監(jiān)視的文件描述符進(jìn)行掃描庭瑰,而epoll事先通過epoll_ctl()來注冊一個(gè)文件描述符,一旦基于某個(gè)文件描述符就緒時(shí)抢埋,內(nèi)核會(huì)采用類似callback的回調(diào)機(jī)制弹灭,迅速激活這個(gè)文件描述符,當(dāng)進(jìn)程調(diào)用epoll_wait()時(shí)便得到通知揪垄。(此處去掉了遍歷文件描述符穷吮,而是通過監(jiān)聽回調(diào)的的機(jī)制。這正是epoll的魅力所在饥努。)
- Java NIO對epoll的實(shí)現(xiàn)是LT模式
- Netty實(shí)現(xiàn)了ET模式的epoll
設(shè)想一下如下場景:有100萬個(gè)客戶端同時(shí)與一個(gè)服務(wù)器進(jìn)程保持著TCP連接捡鱼。而每一時(shí)刻,通常只有幾百上千個(gè)TCP連接是活躍的(事實(shí)上大部分場景都是這種情況)酷愧。如何實(shí)現(xiàn)這樣的高并發(fā)驾诈?
在select/poll時(shí)代,服務(wù)器進(jìn)程每次都把這100萬個(gè)連接告訴操作系統(tǒng)(從用戶態(tài)復(fù)制句柄數(shù)據(jù)結(jié)構(gòu)到內(nèi)核態(tài))溶浴,讓操作系統(tǒng)內(nèi)核去查詢這些套接字上是否有事件發(fā)生乍迄,輪詢完后,再將句柄數(shù)據(jù)復(fù)制到用戶態(tài)戳葵,讓服務(wù)器應(yīng)用程序輪詢處理已發(fā)生的網(wǎng)絡(luò)事件就乓,這一過程資源消耗較大,因此拱烁,select/poll一般只能處理幾千的并發(fā)連接。
epoll的設(shè)計(jì)和實(shí)現(xiàn)與select完全不同噩翠。epoll通過在Linux內(nèi)核中申請一個(gè)簡易的文件系統(tǒng)戏自。把原先的select/poll調(diào)用分成了3個(gè)部分:
- 調(diào)用epoll_create()建立一個(gè)epoll對象(在epoll文件系統(tǒng)中為這個(gè)句柄對象分配資源)
- 調(diào)用epoll_ctl向epoll對象中添加這100萬個(gè)連接的套接字
- 調(diào)用epoll_wait收集發(fā)生的事件的連接
如此一來,要實(shí)現(xiàn)上面說是的場景伤锚,只需要在進(jìn)程啟動(dòng)時(shí)建立一個(gè)epoll對象擅笔,然后在需要的時(shí)候向這個(gè)epoll對象中添加或者刪除連接。同時(shí)屯援,epoll_wait的效率也非常高猛们,因?yàn)檎{(diào)用epoll_wait時(shí),并沒有一股腦的向操作系統(tǒng)復(fù)制這100萬個(gè)連接的句柄數(shù)據(jù)狞洋,內(nèi)核也不需要去遍歷全部的連接弯淘。
下面來看看Linux內(nèi)核具體的epoll機(jī)制實(shí)現(xiàn)思路:
當(dāng)某一進(jìn)程調(diào)用epoll_create方法時(shí),Linux內(nèi)核會(huì)創(chuàng)建一個(gè)eventpoll結(jié)構(gòu)體吉懊,這個(gè)結(jié)構(gòu)體中有兩個(gè)成員與epoll的使用方式密切相關(guān)庐橙。eventpoll結(jié)構(gòu)體如下所示:
struct eventpoll{
....
/*紅黑樹的根節(jié)點(diǎn)假勿,這顆樹中存儲(chǔ)著所有添加到epoll中的需要監(jiān)控的事件*/
struct rb_root rbr;
/*雙鏈表中則存放著將要通過epoll_wait返回給用戶的滿足條件的事件*/
struct list_head rdlist;
....
};
每一個(gè)epoll對象都有一個(gè)獨(dú)立的eventpoll結(jié)構(gòu)體,用于存放通過epoll_ctl方法向epoll對象中添加進(jìn)來的事件态鳖。這些事件都會(huì)掛載在紅黑樹中转培,如此,重復(fù)添加的事件就可以通過紅黑樹而高效的識(shí)別出來(紅黑樹的插入時(shí)間效率是lgn浆竭,其中n為樹的高度)浸须。
而所有添加到epoll中的事件都會(huì)與設(shè)備(網(wǎng)卡)驅(qū)動(dòng)程序建立回調(diào)關(guān)系,也就是說邦泄,當(dāng)相應(yīng)的事件發(fā)生時(shí)會(huì)調(diào)用這個(gè)回調(diào)方法羽戒。這個(gè)回調(diào)方法在內(nèi)核中叫ep_poll_callback,它會(huì)將發(fā)生的事件添加到rdlist雙鏈表中。
在epoll中虎韵,對于每一個(gè)事件易稠,都會(huì)建立一個(gè)epitem結(jié)構(gòu)體,如下所示:
struct epitem{
struct rb_node rbn;//紅黑樹節(jié)點(diǎn)
struct list_head rdllink;//雙向鏈表節(jié)點(diǎn)
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所屬的eventpoll對象
struct epoll_event event; //期待發(fā)生的事件類型
}
當(dāng)調(diào)用epoll_wait檢查是否有事件發(fā)生時(shí)包蓝,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可驶社。如果rdlist不為空,則把發(fā)生的事件復(fù)制到用戶態(tài)测萎,同時(shí)將事件數(shù)量返回給用戶亡电。
從上面的講解可知:通過紅黑樹和雙鏈表數(shù)據(jù)結(jié)構(gòu),并結(jié)合回調(diào)機(jī)制硅瞧,造就了epoll的高效份乒。
OK,講解完了Epoll的機(jī)理腕唧,我們便能很容易掌握epoll的用法了或辖。一句話描述就是:三步曲。
- 第一步:epoll_create()系統(tǒng)調(diào)用枣接。此調(diào)用返回一個(gè)句柄颂暇,之后所有的使用都依靠這個(gè)句柄來標(biāo)識(shí)。
- 第二步:epoll_ctl()系統(tǒng)調(diào)用但惶。通過此調(diào)用向epoll對象中添加耳鸯、刪除、修改感興趣的事件膀曾,返回0標(biāo)識(shí)成功县爬,返回-1表示失敗。
- 第三部:epoll_wait()系統(tǒng)調(diào)用添谊。通過此調(diào)用收集收集在epoll監(jiān)控中已經(jīng)發(fā)生的事件财喳。
select/poll和epoll對比
支持一個(gè)進(jìn)程能打開的最大連接數(shù)
FD劇增后帶來的IO效率
消息傳體方式
綜上,在選擇select碉钠,poll纲缓,epoll時(shí)要根據(jù)具體的使用場合以及這三種方式的自身特點(diǎn):
- 表面上看epoll的性能最好卷拘,但是在連接數(shù)少并且連接都十分活躍的情況下,select和poll的性能可能比epoll好祝高,畢竟epoll的通知機(jī)制需要很多函數(shù)回調(diào)栗弟。
- select低效是因?yàn)槊看嗡夹枰喸儭5托б彩窍鄬Φ墓す耄暻闆r而定乍赫,也可通過良好的設(shè)計(jì)改善。
DMA
DMA(Direct Memory Access)陆蟆,即直接存儲(chǔ)器存取雷厂,是一種快速傳送數(shù)據(jù)的機(jī)制。數(shù)據(jù)傳遞可以從適配卡到內(nèi)存叠殷,從內(nèi)存到適配卡或從一段內(nèi)存到另一段內(nèi)存改鲫。利用它進(jìn)行數(shù)據(jù)傳送時(shí)不需要CPU的參與。每臺(tái)電腦主機(jī)板上都有DMA控制器林束,通常計(jì)算機(jī)對其編程像棘,并用一個(gè)適配器上的ROM(如軟盤驅(qū)動(dòng)控制器上的ROM)來儲(chǔ)存程序,這些程序控制DMA傳送數(shù)據(jù)壶冒。一旦控制器初始化完成缕题,數(shù)據(jù)開始傳送,DMA就可以脫離CPU胖腾,獨(dú)立完成數(shù)據(jù)傳送烟零。
參考文章
https://blog.csdn.net/davidsguo008/article/details/73556811
https://www.cnblogs.com/jeakeven/p/5435916.html