epoll為什么這么快

一圃泡、為什么epoll這么快:

epoll是多路復用IO(I/O Multiplexing)中的一種方式,但是僅用于linux2.6以上內核,在開始討論這個問題之前,先來解釋一下為什么需要多路復用IO.

以一個生活中的例子來解釋.

假設你在大學中讀書,要等待一個朋友來訪,而這個朋友只知道你在A號樓,但是不知道你具體住在哪里,于是你們約好了在A號樓門口見面.

如果你使用的阻塞IO模型來處理這個問題,那么你就只能一直守候在A號樓門口等待朋友的到來,在這段時間里你不能做別的事情,不難知道,這種方式的效率是低下的.

現在時代變化了,開始使用多路復用IO模型來處理這個問題.你告訴你的朋友來了A號樓找樓管大媽,讓她告訴你該怎么走.這里的樓管大媽扮演的就是多路復用IO的角色.

進一步解釋select和epoll模型的差異.

select版大媽做的是如下的事情:比如同學甲的朋友來了,select版大媽比較笨,她帶著朋友挨個房間進行查詢誰是同學甲,你等的朋友來了,于是在實際的代碼中,select版大媽做的是以下的事情:

intn = select(&readset,NULL,NULL,100);

for(inti =0; n >0; ++i)

{

if(FD_ISSET(fdarray[i], &readset))

? ? {

? ? ? ? do_something(fdarray[i]);

? ? ? ? --n;

? ? }

}

epoll版大媽就比較先進了,她記下了同學甲的信息,比如說他的房間號,那么等同學甲的朋友到來時,只需要告訴該朋友同學甲在哪個房間即可,不用自己親自帶著人滿大樓的找人了.于是epoll版大媽做的事情可以用如下的代碼表示:

n = epoll_wait(epfd,events,20,500);

for(i=0;i

{

? ? do_something(events[n]);

}

在epoll中,關鍵的數據結構epoll_event定義如下:

typedefunionepoll_data

{

void*ptr;

intfd;

__uint32_tu32;

__uint64_tu64;

}epoll_data_t;

structepoll_event{

__uint32_tevents;/* Epoll events */

epoll_data_tdata;/* User data variable */

};

可以看到,epoll_data是一個union結構體,它就是epoll版大媽用于保存同學信息的結構體,它可以保存很多類型的信息:fd,指針,等等.有了這個結構體,epoll大媽可以不用吹灰之力就可以定位到同學甲.

別小看了這些效率的提高,在一個大規(guī)模并發(fā)的服務器中,輪詢IO是最耗時間的操作之一.再回到那個例子中,如果每到來一個朋友樓管大媽都要全樓的查詢同學,那么處理的效率必然就低下了,過不久樓底就有不少的人了.

對比最早給出的阻塞IO的處理模型, 可以看到采用了多路復用IO之后, 程序可以自由的進行自己除了IO操作之外的工作, 只有到IO狀態(tài)發(fā)生變化的時候由多路復用IO進行通知, 然后再采取相應的操作, 而不用一直阻塞等待IO狀態(tài)發(fā)生變化了.

從上面的分析也可以看出,epoll比select的提高實際上是一個用空間換時間思想的具體應用.

二、深入理解epoll的實現原理:

開發(fā)高性能網絡程序時蛙讥,windows開發(fā)者們言必稱iocp薪丁,linux開發(fā)者們則言必稱epoll遇西。大家都明白epoll是一種IO多路復用技術,可以非常高效的處理數以百萬計的socket句柄严嗜,比起以前的select和poll效率高大發(fā)了粱檀。我們用起epoll來都感覺挺爽,確實快漫玄,那么梧税,它到底為什么可以高速處理這么多并發(fā)連接呢?

?先簡單回顧下如何使用C庫封裝的3個epoll系統調用吧称近。

intepoll_create(intsize);

intepoll_ctl(intepfd,intop,intfd, struct epoll_event *event);

intepoll_wait(intepfd, struct epoll_event *events,intmaxevents,inttimeout);

使用起來很清晰第队,首先要調用epoll_create建立一個epoll對象。參數size是內核保證能夠正確處理的最大句柄數刨秆,多于這個最大數時內核可不保證效果凳谦。

epoll_ctl可以操作上面建立的epoll,例如衡未,將剛建立的socket加入到epoll中讓其監(jiān)控尸执,或者把 epoll正在監(jiān)控的某個socket句柄移出epoll家凯,不再監(jiān)控它等等。

epoll_wait在調用時如失,在給定的timeout時間內绊诲,當在監(jiān)控的所有句柄中有事件發(fā)生時,就返回用戶態(tài)的進程褪贵。

從上面的調用方式就可以看到epoll比select/poll的優(yōu)越之處:因為后者每次調用時都要傳遞你所要監(jiān)控的所有socket給select/poll系統調用掂之,這意味著需要將用戶態(tài)的socket列表copy到內核態(tài),如果以萬計的句柄會導致每次都要copy幾十幾百KB的內存到內核態(tài)脆丁,非常低效世舰。而我們調用epoll_wait時就相當于以往調用select/poll,但是這時卻不用傳遞socket句柄給內核槽卫,因為內核已經在epoll_ctl中拿到了要監(jiān)控的句柄列表跟压。

所以,實際上在你調用epoll_create后歼培,內核就已經在內核態(tài)開始準備幫你存儲要監(jiān)控的句柄了震蒋,每次調用epoll_ctl只是在往內核的數據結構里塞入新的socket句柄。

在內核里躲庄,一切皆文件喷好。所以,epoll向內核注冊了一個文件系統读跷,用于存儲上述的被監(jiān)控socket。當你調用epoll_create時禾唁,就會在這個虛擬的epoll文件系統里創(chuàng)建一個file結點效览。當然這個file不是普通文件,它只服務于epoll荡短。

epoll在被內核初始化時(操作系統啟動)丐枉,同時會開辟出epoll自己的內核高速cache區(qū),用于安置每一個我們想監(jiān)控的socket掘托,這些socket會以紅黑樹的形式保存在內核cache里瘦锹,以支持快速的查找、插入闪盔、刪除弯院。這個內核高速cache區(qū),就是建立連續(xù)的物理內存頁泪掀,然后在之上建立slab層听绳,簡單的說,就是物理上分配好你想要的size的內存對象异赫,每次使用時都是使用空閑的已分配好的對象椅挣。

staticint__initeventpoll_init(void)

{?

? ? ... ...?

/* Allocates slab cache used to allocate "struct epitem" items */

epi_cache = kmem_cache_create("eventpoll_epi",sizeof(struct epitem),

0, SLAB_HWCACHE_ALIGN|EPI_SLAB_DEBUG|SLAB_PANIC,

NULL,NULL);

/* Allocates slab cache used to allocate "struct eppoll_entry" */

pwq_cache = kmem_cache_create("eventpoll_pwq",

sizeof(struct eppoll_entry),0,

EPI_SLAB_DEBUG|SLAB_PANIC,NULL,NULL);

... ...?

epoll的高效就在于头岔,當我們調用epoll_ctl往里塞入百萬個句柄時,epoll_wait仍然可以飛快的返回鼠证,并有效的將發(fā)生事件的句柄給我們用戶峡竣。這是由于我們在調用epoll_create時,內核除了幫我們在epoll文件系統里建了個file結點量九,在內核cache里建了個紅黑樹用于存儲以后epoll_ctl傳來的socket外适掰,還會再建立一個list鏈表,用于存儲準備就緒的事件娩鹉,當epoll_wait調用時攻谁,僅僅觀察這個list鏈表里有沒有數據即可。有數據就返回弯予,沒有數據就sleep戚宦,等到timeout時間到后即使鏈表沒數據也返回。所以锈嫩,epoll_wait非常高效受楼。

而且,通常情況下即使我們要監(jiān)控百萬計的句柄呼寸,大多一次也只返回很少量的準備就緒句柄而已艳汽,所以,epoll_wait僅需要從內核態(tài)copy少量的句柄到用戶態(tài)而已对雪,如何能不高效河狐?!

那么瑟捣,這個準備就緒list鏈表是怎么維護的呢馋艺?當我們執(zhí)行epoll_ctl時,除了把socket放到epoll文件系統里file對象對應的紅黑樹上之外迈套,還會給內核中斷處理程序注冊一個回調函數捐祠,告訴內核,如果這個句柄的中斷到了桑李,就把它放到準備就緒list鏈表里踱蛀。所以,當一個socket上有數據到了贵白,內核在把網卡上的數據copy到內核中后就來把socket插入到準備就緒鏈表里了率拒。

如此,一顆紅黑樹禁荒,一張準備就緒句柄鏈表俏橘,少量的內核cache,就幫我們解決了大并發(fā)下的socket處理問題圈浇。執(zhí)行epoll_create時寥掐,創(chuàng)建了紅黑樹和就緒鏈表靴寂,執(zhí)行epoll_ctl時,如果增加socket句柄召耘,則檢查在紅黑樹中是否存在百炬,存在立即返回,不存在則添加到樹干上污它,然后向內核注冊回調函數剖踊,用于當中斷事件來臨時向準備就緒鏈表中插入數據。執(zhí)行epoll_wait時立刻返回準備就緒鏈表里的數據即可衫贬。

最后看看epoll獨有的兩種模式LT和ET德澈。無論是LT和ET模式,都適用于以上所說的流程固惯。區(qū)別是梆造,LT模式下,只要一個句柄上的事件一次沒有處理完葬毫,會在以后調用epoll_wait時次次返回這個句柄镇辉,而ET模式僅在第一次返回。

這件事怎么做到的呢贴捡?當一個socket句柄上有事件時忽肛,內核會把該句柄插入上面所說的準備就緒list鏈表,這時我們調用epoll_wait烂斋,會把準備就緒的socket拷貝到用戶態(tài)內存屹逛,然后清空準備就緒list鏈表,最后汛骂,epoll_wait干了件事罕模,就是檢查這些socket,如果不是ET模式(就是LT模式的句柄了)香缺,并且這些socket上確實有未處理的事件時,又把該句柄放回到剛剛清空的準備就緒鏈表了歇僧。所以图张,非ET的句柄,只要它上面還有事件诈悍,epoll_wait每次都會返回祸轮。而ET模式的句柄,除非有新中斷到侥钳,即使socket上的事件沒有處理完适袜,也是不會次次從epoll_wait返回的。

三舷夺、擴展閱讀(epoll與之前其他相關技術的比較):

Linux提供了select苦酱、poll售貌、epoll接口來實現IO復用,三者的原型如下所示颂跨,本文從參數、實現扯饶、性能等方面對三者進行對比。?

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

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

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

select尾序、poll钓丰、epoll_wait參數及實現對比?

1.? select的第一個參數nfds為fdset集合中最大描述符值加1,fdset是一個位數組每币,其大小限制為__FD_SETSIZE(1024)携丁,位數組的每一位代表其對應的描述符是否需要被檢查。?

select的第二三四個參數表示需要關注讀脯爪、寫、錯誤事件的文件描述符位數組痕慢,這些參數既是輸入參數也是輸出參數,可能會被內核修改用于標示哪些描述符上發(fā)生了關注的事件掖举。所以每次調用select前都需要重新初始化fdset快骗。?

timeout參數為超時時間,該結構會被內核修改塔次,其值為超時剩余的時間方篮。?

select對應于內核中的sys_select調用,sys_select首先將第二三四個參數指向的fd_set拷貝到內核励负,然后對每個被SET的描述符調用進行poll藕溅,并記錄在臨時結果中(fdset),如果有事件發(fā)生继榆,select會將臨時結果寫到用戶空間并返回;當輪詢一遍后沒有任何事件發(fā)生時略吨,如果指定了超時時間,則select會睡眠到超時翠忠,睡眠結束后再進行一次輪詢鞠苟,并將臨時結果寫到用戶空間,然后返回当娱。?

select返回后,需要逐一檢查關注的描述符是否被SET(事件是否發(fā)生)趾访。?

2.? poll與select不同态秧,通過一個pollfd數組向內核傳遞需要關注的事件扼鞋,故沒有描述符個數的限制申鱼,pollfd中的events字段和revents分別用于標示關注的事件和發(fā)生的事件云头,故pollfd數組只需要被初始化一次捐友。?

poll的實現機制與select類似溃槐,其對應內核中的sys_poll匣砖,只不過poll向內核傳遞pollfd數組昏滴,然后對pollfd中的每個描述符進行poll猴鲫,相比處理fdset來說谣殊,poll效率更高拂共。?

poll返回后姻几,需要對pollfd中的每個元素檢查其revents值宜狐,來得指事件是否發(fā)生蛇捌。?

3.? epoll通過epoll_create創(chuàng)建一個用于epoll輪詢的描述符,通過epoll_ctl添加/修改/刪除事件络拌,通過epoll_wait檢查事件俭驮,epoll_wait的第二個參數用于存放結果春贸。?

epoll與select混萝、poll不同祥诽,首先譬圣,其不用每次調用都向內核拷貝事件描述信息雄坪,在第一次調用后,事件信息就會與對應的epoll描述符關聯起來维哈。另外epoll不是通過輪詢绳姨,而是通過在等待的描述符上注冊回調函數阔挠,當事件發(fā)生時飘庄,回調函數負責把發(fā)生的事件存儲在就緒事件鏈表中跪削,最后寫到用戶空間。?

epoll返回后碾盐,該參數指向的緩沖區(qū)中即為發(fā)生的事件,對緩沖區(qū)中每個元素進行處理即可揩局,而不需要像poll凌盯、select那樣進行輪詢檢查付枫。?

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末驰怎,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子叶眉,更是在濱河造成了極大的恐慌,老刑警劉巖芹枷,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鸳慈,死亡現場離奇詭異走芋,居然都是意外死亡绩郎,警方通過查閱死者的電腦和手機翁逞,發(fā)現死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進店門肋杖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來挖函,“玉大人状植,你說我怎么就攤上這事〗蚧” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵肉拓,是天一觀的道長。 經常有香客問我暖途,道長卑惜,這世上最難降的妖魔是什么驻售? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮芋浮,結果婚禮上,老公的妹妹穿的比我還像新娘纸巷。我一直安慰自己镇草,他們只是感情好瘤旨,可當我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著存哲,像睡著了一般因宇。 火紅的嫁衣襯著肌膚如雪祟偷。 梳的紋絲不亂的頭發(fā)上察滑,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天修肠,我揣著相機與錄音,去河邊找鬼嵌施。 笑死饲化,一個胖子當著我的面吹牛吗伤,可吹牛的內容都是我干的吃靠。 我是一名探鬼主播足淆,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼捺球,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了夕冲?” 一聲冷哼從身側響起裂逐,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎卜高,沒想到半個月后弥姻,有當地人在樹林里發(fā)現了一具尸體掺涛,經...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年薪缆,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拣帽。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡疼电,死狀恐怖减拭,靈堂內的尸體忽然破棺而出蔽豺,到底是詐尸還是另有隱情拧粪,我是刑警寧澤修陡,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布可霎,位于F島的核電站,受9級特大地震影響癣朗,放射性物質發(fā)生泄漏号杏。R本人自食惡果不足惜斯棒,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一盾致、第九天 我趴在偏房一處隱蔽的房頂上張望荣暮。 院中可真熱鬧庭惜,春花似錦穗酥、人聲如沸护赊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至判耕,卻和暖如春透绩,著一層夾襖步出監(jiān)牢的瞬間壁熄,已是汗流浹背帚豪。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工草丧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狸臣,地道東北人昌执。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像懂拾,于是被迫代替她去往敵國和親此洲。 傳聞我的和親對象是個殘疾皇子委粉,可洞房花燭夜當晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內容