為什么我執(zhí)意要先把NIO基礎(chǔ)原理放到最開始來講,主要是因?yàn)榧狙校挥欣斫夂昧嗽恚拍芨玫娜ダ斫釴IO誉察。
一与涡、用戶空間和內(nèi)核空間
在linux操作系統(tǒng),會把內(nèi)存分為兩塊,一塊是內(nèi)核空間驼卖,一塊用戶空間氨肌。關(guān)于這一部分的知識,不做詳細(xì)分析酌畜,可以去看一下這個:Linux的用戶空間與內(nèi)核空間
我們都知道怎囚,為了OS的安全性等的考慮,進(jìn)程是無法直接操作I/O設(shè)備的桥胞,其必須通過系統(tǒng)調(diào)用請求內(nèi)核來協(xié)助完成I/O動作恳守,而內(nèi)核會為每個I/O設(shè)備維護(hù)一個buffer。我們每次請求IO設(shè)備的時(shí)候埠戳,我們都要先請求內(nèi)核井誉,內(nèi)核操作IO設(shè)備,把數(shù)據(jù)讀取到內(nèi)核Buffer中整胃,然后內(nèi)核再把Buffer中的數(shù)據(jù)讀取到用戶進(jìn)程的內(nèi)存中。
這就像我們?nèi)ャy行取錢一樣喳钟,三個角色,用戶(用戶),收銀員(內(nèi)核)锐锣,金庫(IO設(shè)備)未蝌,用戶肯定是不能直接去金庫中取錢,先要經(jīng)過收銀員易茬,然后收銀員去金庫中取錢酬蹋,再把錢交給用戶。
二抽莱、UNIX下的五種IO模型
- 阻塞IO
- 非阻塞IO
- IO多路復(fù)用
- 信號驅(qū)動IO
- 異步IO
1范抓、阻塞IO
用戶線程發(fā)起調(diào)用read發(fā)起IO讀操作,用戶線程阻塞食铐,內(nèi)容等待數(shù)據(jù)從IO設(shè)備中讀入 Buffer匕垫,完成后將數(shù)據(jù)拷貝至用戶線程內(nèi)存中,完成read操作虐呻。
這里可以看成小磊(用戶線程)去銀行取錢
1.小磊:我要取錢(發(fā)起read請求)(阻塞象泵,只能一直等,等到收銀員把錢準(zhǔn)備好)
2.收銀員:你稍等斟叼,我叫人(IO 設(shè)備)幫你準(zhǔn)備錢(等待數(shù)據(jù)到達(dá)偶惠,也就是從IO設(shè)備把數(shù)據(jù)讀到內(nèi)核Buffer中)
3.(取完錢了回來了)收銀員:錢取好了,你收好(數(shù)據(jù)到達(dá)了朗涩,等待用戶拿錢)
4.小磊把錢裝進(jìn)包里(數(shù)據(jù)拷貝忽孽,完成read請求),小磊很開心,可以直接花錢了扒腕。
2绢淀、非阻塞IO模型
用戶發(fā)起調(diào)用read請求發(fā)起IO讀操作,系統(tǒng)不會立刻阻塞用戶線程瘾腰,而是立刻返回一個錯誤標(biāo)識ewouldblock皆的,用戶進(jìn)程判斷該錯誤標(biāo)識是ewouldblock,就能夠知道數(shù)據(jù)還在準(zhǔn)備蹋盆,然后會進(jìn)行其他的操作费薄,用戶線程可以再次發(fā)送read請求,可以不斷輪詢栖雾,直到數(shù)據(jù)準(zhǔn)備完畢楞抡,然后講數(shù)據(jù)拷貝至用戶線程的內(nèi)存中,完成read操作析藕。
好了召廷,我們再來取一次錢:
1.小磊:我要取錢(發(fā)起read請求)
2.收銀員:好的,我叫人(IO 設(shè)備)幫你準(zhǔn)備錢账胧,你可以做點(diǎn)別的事情(小磊可以去做一些其它的操作竞慢,喝杯咖啡,打兩把王者爸文唷)
3.小磊打完游戲后(當(dāng)然也可以什么都不做)筹煮,再次詢問:錢取好了嗎?4.收銀員:沒好
5.小磊又可以繼續(xù)干其它事情居夹,或者再次詢問(不斷輪詢)败潦,直到收銀員說準(zhǔn)備好了(數(shù)據(jù)到達(dá)了)
6.準(zhǔn)備好了后,小磊把錢裝進(jìn)包里(數(shù)據(jù)拷貝准脂,IO完成)
3劫扒、IO多路復(fù)用
多路復(fù)用函數(shù) select把一些文件描述符(下面有文件描述符的定義)集合在一起某個文件描述符的狀態(tài)發(fā)生變化 比如進(jìn)入"寫就緒"或者 "讀就緒"狀態(tài) ,函數(shù)select就會立即返回并且通知進(jìn)程讀取或者寫入數(shù)據(jù)如果沒有I/O操作到達(dá)進(jìn)程就會阻塞直到函數(shù)select超時(shí)退出為止意狠。
IO多路復(fù)用就是基于select函數(shù)實(shí)現(xiàn)粟关,用戶進(jìn)程調(diào)用了select,整個線程就會被阻塞环戈,此時(shí)內(nèi)核就會監(jiān)視所有select負(fù)責(zé)的socket闷板,一旦其中有一個socket中的數(shù)據(jù)準(zhǔn)備好了,select就會返回院塞,然后用戶在調(diào)用read操作遮晚。
多路復(fù)用的優(yōu)缺點(diǎn):缺點(diǎn):多路復(fù)用比阻塞IO還多了一個select的系統(tǒng)調(diào)用,在處理連接數(shù) 不高的情況下拦止,可能性能還會低于阻塞IO县遣,優(yōu)點(diǎn):可以同時(shí)處理多個socket
OK糜颠,原理性的東西完畢了,是時(shí)候取錢了萧求,但是這次取錢不一樣了其兴,小磊他想出國玩,他需要人民幣買機(jī)票夸政,又需要美元在國外使用元旬,當(dāng)然,這家銀行也挺給力守问,提供取美元的服務(wù)匀归,不過美元和人民幣在不同的金庫(socket)中。
1.小磊:我要取人民幣和美元耗帕。(select請求)
2.收銀員:我去叫人(不同的socket)幫你準(zhǔn)備穆端,你稍等(小磊必須等待了,阻塞)仿便,收銀員不斷問準(zhǔn)備錢的人取好了嗎或者等待準(zhǔn)備錢的人告訴收銀員錢準(zhǔn)備好了(對應(yīng)下面的三種模型)
3.一旦存在任何一個(美元或者人民幣準(zhǔn)備好了体啰,也就是socket中有數(shù)據(jù)了)準(zhǔn)備好了,立馬通知小磊拿錢(數(shù)據(jù)拷貝)
4.接著小磊就把錢裝進(jìn)口袋里了(數(shù)據(jù)拷貝完成)
此時(shí)探越,你就取了一種紙幣了狡赐,接著你可以繼續(xù)請求(select())取另外一種紙幣
文件描述符fd
Linux的內(nèi)核將所有外部設(shè)備都可以看做一個文件來操作。那么我們對與外部設(shè)備的操作都可以看做對文件進(jìn)行操作钦幔。我們對一個文件的讀寫,都通過調(diào)用內(nèi)核提供的系統(tǒng)調(diào)用常柄;內(nèi)核給我們返回一個filedescriptor(fd,文件描述符)鲤氢。而對一個socket的讀寫也會有相應(yīng)的描述符,稱為socketfd(socket描述符)西潘。描述符就是一個數(shù)字卷玉,指向內(nèi)核中一個結(jié)構(gòu)體(文件路徑,數(shù)據(jù)區(qū)喷市,等一些屬性)相种。那么我們的應(yīng)用程序?qū)ξ募淖x寫就通過對描述符的讀寫完成。
每個進(jìn)程在PCB中保存著一份文件描述符表品姓,文件描述符就是這個表的索引寝并,索引對應(yīng)的位置有一個指向已打開文件的指針。例如圖中的stdin腹备,文件描述符是1衬潦,它的指針指向鍵盤文件,stdout植酥,文件描述是2镀岛,它的指針指向顯示器文件 弦牡,指向文件是一個結(jié)構(gòu)體 ,結(jié)構(gòu)體中保存了一些相關(guān)信息漂羊。
通過上面應(yīng)該大概能知道IO多路復(fù)用的大概流程了吧驾锰,但是,有一個點(diǎn)很重要的地方走越,內(nèi)核是如何檢測IO準(zhǔn)備完成的(收銀員如何確認(rèn)錢準(zhǔn)備好的)椭豫。
于是UNIX操作系統(tǒng)提供以下三種模型:
select
基本原理:select 函數(shù)監(jiān)視的fd分3類,分別是writefds买喧、readfds捻悯、和exceptfds。調(diào)用后select函數(shù)會阻塞淤毛,直到有fd就緒(有數(shù)據(jù) 可讀今缚、可寫、或者有except)低淡,或者超時(shí)(timeout指定等待時(shí)間姓言,如果立即返回設(shè)為null即可),函數(shù)返回蔗蹋。當(dāng)select函數(shù)返回后何荚,可以通過遍歷fdset,來找到就緒的描述符猪杭。
缺點(diǎn):
FD限制餐塘,32位系統(tǒng)1024個,64位2048
遍歷方式為輪詢
需要維護(hù)一個用來存放大量fd的數(shù)據(jù)結(jié)構(gòu)皂吮,這樣會使得用戶空間和內(nèi)核空間在傳遞該結(jié)構(gòu)時(shí)復(fù)制開銷大
poll
poll和select基本上沒太大區(qū)別戒傻,主要區(qū)別在于他存儲fd是基于鏈表的形式,所以理論上來說沒有最大連接限制蜂筹,但是他同樣的會在大量連接的時(shí)候需纳,效率非常低。
epoll
epoll是select和poll的增強(qiáng)版本艺挪。
基本原理:epoll支持水平觸發(fā)和邊緣觸發(fā)不翩,最大的特點(diǎn)在于邊緣觸發(fā),它只告訴進(jìn)程哪些fd剛剛變?yōu)榫途w態(tài)麻裳,并且只會通知一次口蝠。還有一個特點(diǎn)是,epoll使用“事件”的就緒通知方式掂器,通過epollctl注冊fd亚皂,一旦該fd就緒,內(nèi)核就會采用類似callback的回調(diào)機(jī)制來激活該fd国瓮,epollwait便可以收到通知灭必。
沒有最大連接的限制
效率高狞谱,隨著fd的增加不會降低效率
內(nèi)存拷貝,利用mmap()文件映射內(nèi)存加速與內(nèi)核空間的消息傳遞禁漓;即epoll使用mmap減少復(fù)制開銷跟衅。
底層數(shù)據(jù)結(jié)構(gòu)是紅黑樹,效率非常高
小結(jié):三種模式播歼,select和poll都是輪詢的方式伶跷,在連接數(shù)量高的時(shí)候效率極低,epoll是基于事件驅(qū)動的秘狞,在連接數(shù)量高的時(shí)候效率不會降低
4.信號驅(qū)動異步IO
這種io模型用的比較少叭莫,我就不介紹太多了,他是一種非阻塞IO模型烁试,用戶進(jìn)程發(fā)起IO請求后雇初,可以繼續(xù)執(zhí)行其他操作,等待內(nèi)核準(zhǔn)備好數(shù)據(jù)减响,會遞交一個SIGIO信號給用戶進(jìn)程靖诗,用戶進(jìn)程收到該信號后就知道數(shù)據(jù)準(zhǔn)備好了,然后再次發(fā)起IO請求支示,進(jìn)行數(shù)據(jù)拷貝刊橘。
5、異步IO
當(dāng)用戶進(jìn)程發(fā)起讀操作的時(shí)候颂鸿,會立刻得到一個返回促绵,用戶進(jìn)程會繼續(xù)執(zhí)行其他操作,內(nèi)核會等待數(shù)據(jù)到達(dá)嘴纺,并且將數(shù)據(jù)拷貝到用戶進(jìn)程的緩沖區(qū)中绞愚,拷貝完成后,內(nèi)核會發(fā)送一個信號給用戶進(jìn)程颖医,用戶進(jìn)程收到這個信號后,可以直接使用已經(jīng)拷貝好的數(shù)據(jù)裆蒸。
這異步IO的方式可以理解為收銀員不僅幫你把錢準(zhǔn)備好了熔萧,還會主動塞進(jìn)你口袋里。
三僚祷、同步佛致、異步、阻塞和非阻塞對比
同步IO和異步IO:他們最主要的區(qū)別就是同步IO還需要用戶進(jìn)程去拷貝數(shù)據(jù)辙谜,這段時(shí)間用戶進(jìn)程會阻塞俺榆,而異步IO就不需要去拷貝數(shù)據(jù)了,收到信號后装哆,數(shù)據(jù)已經(jīng)被拷貝完成了罐脊。
阻塞和非阻塞:阻塞是指用戶進(jìn)程必須等定嗓,非阻塞是指用戶進(jìn)程可以去執(zhí)行其他操作。