前言:
1题翻、什么是IO多路復(fù)用:
隨著網(wǎng)絡(luò)需求的增大揩徊,對(duì)于網(wǎng)絡(luò)服務(wù)性能的要求也越來(lái)越高,而這也逐步促進(jìn)了IO模型的發(fā)展嵌赠。
最初的IO模型是阻塞式的塑荒,就是在數(shù)據(jù)沒(méi)有準(zhǔn)備好的時(shí)候,進(jìn)程處于阻塞狀態(tài)姜挺,屁事不干齿税。
然后程序猿就想:我靠,這小子(暫時(shí)叫recvfrom吧)這是占著茅坑不拉屎呀炊豪,得讓他出去干點(diǎn)其他活凌箕。于是就出現(xiàn)了非阻塞式IO。
非阻塞式IO就是在進(jìn)程之中不斷的詢問(wèn)fd词渤,看數(shù)據(jù)有沒(méi)有準(zhǔn)備好牵舱,如果沒(méi)有準(zhǔn)備好,那么先去干其他事缺虐,然后過(guò)一會(huì)再來(lái)問(wèn)數(shù)據(jù)有沒(méi)有準(zhǔn)備好芜壁,這就解決了進(jìn)程“偷懶”的問(wèn)題。
但是后來(lái)發(fā)現(xiàn)那個(gè)進(jìn)程每天累死累活的,也就只能監(jiān)控一個(gè)IO沿盅,而且是不管有沒(méi)有準(zhǔn)備好都去訪問(wèn)把篓,大家都知道系統(tǒng)調(diào)用很耗時(shí)間,這樣每次去詢問(wèn)腰涧,而且還沒(méi)有數(shù)據(jù)的情況下很耗cpu資源韧掩。有沒(méi)有辦法讓他同時(shí)監(jiān)控多個(gè)IO(老板壓榨程序猿,程序猿壓榨系統(tǒng))窖铡,有數(shù)據(jù)準(zhǔn)備好才返回疗锐?
于是就請(qǐng)了個(gè)職業(yè)經(jīng)理人,當(dāng)然,這種職業(yè)經(jīng)理人也有三種费彼,分別是select()滑臊,poll(),epoll()箍铲,請(qǐng)誰(shuí)好后面再說(shuō)雇卷,請(qǐng)來(lái)先讓他循環(huán)詢問(wèn)是否有IO準(zhǔn)備好數(shù)據(jù),準(zhǔn)備好就立馬通知原來(lái)的那個(gè)recvfrom颠猴,讓他做相應(yīng)的處理关划,沒(méi)有準(zhǔn)備好就進(jìn)行阻塞。這就是IO的多路復(fù)用
IO多路復(fù)用好處在于可以不用去創(chuàng)建和維護(hù)多個(gè)線程/進(jìn)程翘瓮,用一個(gè)線程/進(jìn)程來(lái)去監(jiān)控多個(gè)IO流贮折,減小了系統(tǒng)的開(kāi)銷
2、select()
select原理:
select會(huì)在內(nèi)核中不斷詢問(wèn)數(shù)據(jù)是否準(zhǔn)備好资盅,如果沒(méi)有準(zhǔn)備好的數(shù)據(jù)调榄,就將進(jìn)程阻塞,直到有一個(gè)或者多個(gè)IO數(shù)據(jù)準(zhǔn)備好后呵扛,告訴recvfrom每庆,叫他來(lái)對(duì)數(shù)據(jù)進(jìn)行讀寫(xiě)。具體過(guò)程如下
當(dāng)我們調(diào)用select()時(shí):
1今穿、上下文切換轉(zhuǎn)換為內(nèi)核態(tài)
2扣孟、將fd從用戶空間復(fù)制到內(nèi)核空間
3、內(nèi)核遍歷所有fd荣赶,查看其對(duì)應(yīng)事件是否發(fā)生
4、如果沒(méi)發(fā)生鸽斟,將進(jìn)程阻塞拔创,當(dāng)設(shè)備驅(qū)動(dòng)產(chǎn)生中斷或者timeout時(shí)間后,將進(jìn)程喚醒富蓄,再次進(jìn)行遍歷
5剩燥、返回遍歷后的fd
6、將fd從內(nèi)核空間復(fù)制到用戶空間
但是聰明的你一定會(huì)發(fā)現(xiàn)幾個(gè)問(wèn)題:
第一:當(dāng)那個(gè)職業(yè)經(jīng)紀(jì)人發(fā)現(xiàn)傳進(jìn)來(lái)需要監(jiān)控的文件描述符(下面用fd表示)有數(shù)據(jù)準(zhǔn)備好的,然后就一股腦的把所有fd傳回去灭红,完全不告訴你那個(gè)fd準(zhǔn)備好了侣滩,這樣我recvfrom又得循環(huán)所有fd,查看哪個(gè)有數(shù)據(jù)的变擒,再進(jìn)行讀寫(xiě)君珠。這樣的話不僅在復(fù)制這個(gè)fd的時(shí)候會(huì)消耗大量cpu,而且重復(fù)勞動(dòng)太多了娇斑。這就好像是大學(xué)時(shí)候的坑爹教授在期末的時(shí)候跟我們說(shuō):考試的內(nèi)容都在這本書(shū)里面策添,這本書(shū)就是重點(diǎn),咱絕對(duì)不超綱毫缆,你們放心唯竹。。苦丁。浸颓。(每次聽(tīng)完都有種想揍人的沖動(dòng))
第二:監(jiān)控的文件描述符數(shù)量有限,最大是1024個(gè)
3旺拉、poll
那么产上,我們聰明的程序員就想改進(jìn)下select,那么他們最先改進(jìn)的就是監(jiān)控的文件描述符的限制账阻,原來(lái)不是說(shuō)有1024限制嗎蒂秘,那么我現(xiàn)在就把他設(shè)計(jì)成沒(méi)有限制,這個(gè)時(shí)候就出現(xiàn)了poll淘太,poll的基本原理和select差不多姻僧,而且poll和select一樣有著第一個(gè)缺點(diǎn),就是fd的數(shù)組在復(fù)制進(jìn)內(nèi)核空間和用戶空間之間蒲牧,開(kāi)銷會(huì)隨著文件描述符的增大而線性增大撇贺。
但是缺點(diǎn)要一點(diǎn)一點(diǎn)改,程序猿想出了個(gè)終極版本冰抢,就是epoll松嘶,徹底改進(jìn)了原來(lái)那種低效的無(wú)差別輪詢,而且不限數(shù)量挎扰。
4翠订、epoll
epoll和上面兩種模型不同的方式在于
1、從用戶空間到內(nèi)核態(tài)遵倦,數(shù)據(jù)只拷貝一次:
2尽超、epoll采用基于事件的就緒通知方式,epoll會(huì)給當(dāng)前監(jiān)控的fd每人發(fā)一個(gè)回調(diào)函數(shù)梧躺,當(dāng)有事件發(fā)生的時(shí)候似谁,內(nèi)核會(huì)采用類似callback的回調(diào)機(jī)制(有點(diǎn)像事件中斷),將文件描述符號(hào)放在一個(gè)文件描述符表里面,然后每次檢測(cè)這個(gè)表是不是為空巩踏,如果不為空就通知recvfrom進(jìn)行讀寫(xiě)數(shù)據(jù)秃诵,為空就阻塞。具體過(guò)程如下:
epoll提供了三個(gè)函數(shù)塞琼,epoll_create,epoll_ctl和epoll_wait菠净,epoll_create是創(chuàng)建一個(gè)epoll句柄;epoll_ctl是注冊(cè)要監(jiān)聽(tīng)的事件類型屈梁;epoll_wait則是等待事件的產(chǎn)生
epoll_create 創(chuàng)建一個(gè)epoll對(duì)象嗤练,一般epollfd = epoll_create()
epoll_ctl (epoll_add/epoll_del的合體),往epoll對(duì)象中增加/刪除某一個(gè)流的某一個(gè)事件
比如
epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注冊(cè)緩沖區(qū)非空事件在讶,即有數(shù)據(jù)流入
epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注冊(cè)緩沖區(qū)非滿事件煞抬,即流可以被寫(xiě)入
epoll_wait(epollfd,...)等待直到注冊(cè)的事件發(fā)生
(注:當(dāng)對(duì)一個(gè)非阻塞流的讀寫(xiě)發(fā)生緩沖區(qū)滿或緩沖區(qū)空,write/read會(huì)返回-1构哺,并設(shè)置errno=EAGAIN革答。而epoll只關(guān)心緩沖區(qū)非滿和緩沖區(qū)非空事件)。
總結(jié):
(1)select曙强,poll實(shí)現(xiàn)需要自己不斷輪詢所有fd集合残拐,直到設(shè)備就緒,期間可能要睡眠和喚醒多次交替碟嘴。而epoll其實(shí)也需要調(diào)用epoll_wait不斷輪詢就緒鏈表溪食,期間也可能多次睡眠和喚醒交替,但是它是設(shè)備就緒時(shí)娜扇,調(diào)用回調(diào)函數(shù)错沃,把就緒fd放入就緒鏈表中,并喚醒在epoll_wait中進(jìn)入睡眠的進(jìn)程雀瓢。雖然都要睡眠和交替枢析,但是select和poll在“醒著”的時(shí)候要遍歷整個(gè)fd集合,而epoll在“醒著”的時(shí)候只要判斷一下就緒鏈表是否為空就行了刃麸,這節(jié)省了大量的CPU時(shí)間醒叁。這就是回調(diào)機(jī)制帶來(lái)的性能提升。
(2)select泊业,poll每次調(diào)用都要把fd集合從用戶態(tài)往內(nèi)核態(tài)拷貝一次把沼,并且要把current往設(shè)備等待隊(duì)列中掛一次,而epoll只要一次拷貝吁伺,而且把current往等待隊(duì)列上掛也只掛一次(在epoll_wait的開(kāi)始智政,注意這里的等待隊(duì)列并不是設(shè)備等待隊(duì)列,只是一個(gè)epoll內(nèi)部定義的等待隊(duì)列)箱蝠。這也能節(jié)省不少的開(kāi)銷。