Linux I/O 模型

術(shù)語概念描述:

IO有內(nèi)存IO、網(wǎng)絡(luò)IO和磁盤IO三種,通常我們說的IO指的是后兩者区拳。

阻塞和非阻塞,是函數(shù)/方法的實(shí)現(xiàn)方式意乓,即在數(shù)據(jù)就緒之前是立刻返回還是等待樱调。

以文件IO為例,一個(gè)IO讀過程是文件數(shù)據(jù)從磁盤→內(nèi)核緩沖區(qū)→用戶內(nèi)存的過程。同步與異步的區(qū)別主要在于數(shù)據(jù)從內(nèi)核緩沖區(qū)→用戶內(nèi)存這個(gè)過程需不需要用戶進(jìn)程等待。有個(gè)數(shù)據(jù)拷貝的過程笆凌,是拷貝完再通知還是在內(nèi)核緩沖區(qū)就通知圣猎。(網(wǎng)絡(luò)IO把磁盤換做網(wǎng)卡即可)

Linux IO模型

????????同步阻塞

????????同步非阻塞

????????IO復(fù)用

????????信號(hào)驅(qū)動(dòng)

????????異步非阻塞

同步阻塞

????????去餐館吃飯,點(diǎn)一個(gè)自己最愛吃的蓋澆飯乞而,然后在原地等著一直到蓋澆飯做好送悔,自己端到餐桌就餐。這就是典型的同步阻塞爪模。當(dāng)廚師給你做飯的時(shí)候放祟,你需要一直在那里等著。

????????網(wǎng)絡(luò)編程中呻右,讀取客戶端的數(shù)據(jù)需要調(diào)用recvfrom跪妥。在默認(rèn)情況下,這個(gè)調(diào)用會(huì)一直阻塞直到數(shù)據(jù)接收完畢声滥,就是一個(gè)同步阻塞的IO方式眉撵。這也是最簡(jiǎn)單的IO模型,在通常fd(文件描述句柄)較少落塑、就緒很快的情況下使用是沒有問題的纽疟。

同步非阻塞

????????你每次點(diǎn)完飯就在那里等著,突然有一天你發(fā)現(xiàn)自己真傻憾赁。于是污朽,你點(diǎn)完之后,就回桌子那里坐著龙考,然后估計(jì)差不多了蟆肆,就問老板飯好了沒,如果好了就去端晦款,沒好的話就等一會(huì)再去問炎功,依次循環(huán)直到飯做好。這就是同步非阻塞缓溅。

????????這種方式在編程中對(duì)socket設(shè)置O_NONBLOCK即可蛇损。但此方式僅僅針對(duì)網(wǎng)絡(luò)IO有效,對(duì)磁盤IO并沒有作用坛怪。因?yàn)楸镜匚募蘒O就沒有被認(rèn)為是阻塞淤齐,我們所說的網(wǎng)絡(luò)IO的阻塞是因?yàn)榫W(wǎng)路IO有無限阻塞的可能,而本地文件除非是被鎖住袜匿,否則是不可能無限阻塞的更啄,因此只有鎖這種情況下,O_NONBLOCK才會(huì)有作用沉帮。而且锈死,磁盤IO時(shí)要么數(shù)據(jù)在內(nèi)核緩沖區(qū)中直接可以返回,要么需要調(diào)用物理設(shè)備去讀取穆壕,這時(shí)候進(jìn)程的其他工作都需要等待待牵。因此,后續(xù)的IO復(fù)用和信號(hào)驅(qū)動(dòng)IO對(duì)文件IO也是沒有意義的喇勋。

IO復(fù)用

????????你點(diǎn)一份飯然后循環(huán)的去問好沒好顯然有點(diǎn)得不償失缨该,還不如就等在那里直到準(zhǔn)備好,但是當(dāng)你點(diǎn)了好幾樣飯菜的時(shí)候川背,你每次都去問一下所有飯菜的狀態(tài)(未做好/已做好)肯定比你每次阻塞在那里等著好多了贰拿。當(dāng)然,你問的時(shí)候是需要阻塞的熄云,一直到有準(zhǔn)備好的飯菜或者你等的不耐煩(超時(shí))膨更。這就引出了IO復(fù)用,也叫多路IO就緒通知缴允。這是一種進(jìn)程預(yù)先告知內(nèi)核的能力荚守,讓內(nèi)核發(fā)現(xiàn)進(jìn)程指定的一個(gè)或多個(gè)IO條件就緒了奔誓,就通知進(jìn)程晋涣。使得一個(gè)進(jìn)程能在一連串的事件上等待绪抛。

????????IO復(fù)用的實(shí)現(xiàn)方式目前主要有select柔滔、poll和epoll贫母。select和poll的原理基本相同:注冊(cè)待偵聽的fd(這里的fd創(chuàng)建時(shí)最好使用非阻塞)熬苍,每次調(diào)用都去檢查這些fd的狀態(tài)换淆,當(dāng)有一個(gè)或者多個(gè)fd就緒的時(shí)候返回心褐,返回結(jié)果中包括已就緒和未就緒的fd摄职。

????????相比select誊役,poll解決了單個(gè)進(jìn)程能夠打開的文件描述符數(shù)量有限制這個(gè)問題:select受限于FD_SIZE的限制,如果修改則需要修改這個(gè)宏重新編譯內(nèi)核谷市;而poll通過一個(gè)pollfd數(shù)組向內(nèi)核傳遞需要關(guān)注的事件势木,避開了文件描述符數(shù)量限制。此外歌懒,select和poll共同具有的一個(gè)很大的缺點(diǎn)就是包含大量fd的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核態(tài)地址空間之間啦桌,開銷會(huì)隨著fd數(shù)量增多而線性增大

????????select和poll就類似于上面說的就餐方式及皂。但當(dāng)你每次都去詢問時(shí)甫男,老板會(huì)把所有你點(diǎn)的飯菜都輪詢一遍再告訴你情況,當(dāng)大量飯菜很長(zhǎng)時(shí)間都不能準(zhǔn)備好的情況下是很低效的验烧。于是板驳,老板有些不耐煩了,就讓廚師每做好一個(gè)菜就記下來他碍拆。這樣每次你再去問的時(shí)候若治,他會(huì)直接把已經(jīng)準(zhǔn)備好的菜告訴你慨蓝,你再去端。這就是事件驅(qū)動(dòng)IO就緒通知的方式epoll端幼。

????????epoll的出現(xiàn)礼烈,解決了select、poll的缺點(diǎn):基于事件驅(qū)動(dòng)的方式婆跑,避免了每次都要把所有fd都掃描一遍此熬。epoll_wait只返回就緒的fdepoll使用nmap內(nèi)存映射技術(shù)避免了內(nèi)存復(fù)制的開銷滑进。epoll的fd數(shù)量上限是操作系統(tǒng)的最大文件句柄數(shù)目,這個(gè)數(shù)目一般和內(nèi)存有關(guān)犀忱,通常遠(yuǎn)大于1024。

總結(jié):

????????select:支持注冊(cè) FD_SETSIZE(1024)?個(gè) socket扶关。

????????poll:poll?作為 select?的替代者阴汇,最大的區(qū)別就是,poll?不再限制 socket?數(shù)量节槐。

????????epoll:epoll?能直接返回具體的準(zhǔn)備好的通道鲫寄,時(shí)間復(fù)雜度 O(1)。

????????ps:select 和poll 都有一個(gè)共同的問題疯淫,那就是它們都只會(huì)返回所有通道(channel)地来,但是不會(huì)告訴你具體是哪幾個(gè)通道已經(jīng)就緒。一旦知道有通道準(zhǔn)備好以后熙掺,需要進(jìn)行一次掃描未斑,通道少的時(shí)候還行,一旦通道的數(shù)量是幾十萬個(gè)以上的時(shí)候币绩,掃描一次的時(shí)間復(fù)雜度O(n)蜡秽。后來才催生了epoll實(shí)現(xiàn)。

????????此外缆镣,對(duì)于IO復(fù)用還有一個(gè)水平觸發(fā)和邊緣觸發(fā)的概念:

????????水平觸發(fā)(可以返回多次):當(dāng)就緒的fd未被用戶進(jìn)程處理后芽突,下一次查詢依舊會(huì)返回,這是select和poll的觸發(fā)方式董瞻。

????????邊緣觸發(fā)(只返回一次):無論就緒的fd是否被處理寞蚌,下一次不再返回。理論上性能更高钠糊,但是實(shí)現(xiàn)相當(dāng)復(fù)雜挟秤,并且任何意外的丟失事件都會(huì)造成請(qǐng)求處理錯(cuò)誤。epoll默認(rèn)使用水平觸發(fā)抄伍,通過相應(yīng)選項(xiàng)可以使用邊緣觸發(fā)艘刚。

信號(hào)驅(qū)動(dòng)

????????上文的就餐方式還是需要你每次都去問一下飯菜狀況。于是截珍,你再次不耐煩了攀甚,就跟老板說箩朴,哪個(gè)飯菜好了就通知我一聲吧。然后就自己坐在桌子那里干自己的事情秋度。更甚者炸庞,你可以把手機(jī)號(hào)留給老板,自己出門静陈,等飯菜好了直接發(fā)條短信給你燕雁。這就類似信號(hào)驅(qū)動(dòng)的IO模型诞丽。

????????流程如下:

????????開啟套接字信號(hào)驅(qū)動(dòng)IO功能鲸拥;

????????系統(tǒng)調(diào)用sigaction執(zhí)行信號(hào)處理函數(shù)(非阻塞,立刻返回)僧免;

????????數(shù)據(jù)就緒(在內(nèi)核緩沖區(qū))刑赶,生成sigio信號(hào),通過信號(hào)回調(diào)通知應(yīng)用來讀取數(shù)據(jù)懂衩。

異步非阻塞

????????之前的就餐方式撞叨,到最后總是需要你自己去把飯菜端到餐桌。這下你也不耐煩了浊洞,于是就告訴老板牵敷,能不能飯好了直接端到你的面前或者送到你的家里(數(shù)據(jù)在用戶內(nèi)存就緒)。這就是異步非阻塞IO了法希。

????????對(duì)比信號(hào)驅(qū)動(dòng)IO枷餐,異步IO的主要區(qū)別在于:信號(hào)驅(qū)動(dòng)由內(nèi)核告訴我們何時(shí)可以開始一個(gè)IO操作(數(shù)據(jù)在內(nèi)核緩沖區(qū)中),而異步IO則由內(nèi)核通知IO操作何時(shí)已經(jīng)完成(數(shù)據(jù)已經(jīng)在用戶空間中)苫亦。異步IO又叫做事件驅(qū)動(dòng)IO毛肋,在Unix中,POSIX1003.1標(biāo)準(zhǔn)為異步方式訪問文件定義了一套庫函數(shù)屋剑,定義了AIO的一系列接口润匙。使用aio_read或者aio_write發(fā)起異步IO操作。使用aio_error檢查正在運(yùn)行的IO操作的狀態(tài)唉匾。

網(wǎng)絡(luò)編程模型

????????Java的I/O發(fā)展簡(jiǎn)史:

????????從JDK1.0到JDK1.3孕讳,Java的I/O類庫都非常原始,很多UNIX網(wǎng)絡(luò)編程中的概念或者接口在I/O類庫中都沒有體現(xiàn)巍膘,例如Pipe卫病、Channel、Buffer和Selector等典徘。2002年發(fā)布JDK1.4時(shí)蟀苛,NIO以JSR-51的身份正式隨JDK發(fā)布。它新增了個(gè)java.nio包逮诲,提供了很多進(jìn)行異步I/O開發(fā)的API和類庫帜平,主要的類和接口如下:

????????進(jìn)行異步I/O操作的緩沖區(qū)ByteBuffer等幽告;

????????進(jìn)行異步I/O操作的管道Pipe;

????????進(jìn)行各種I/O操作(異步或者同步)的Channel裆甩,包括ServerSocketChannel和SocketChannel冗锁;

????????多種字符集的編碼能力和解碼能力;

????????實(shí)現(xiàn)非阻塞I/O操作的多路復(fù)用器selector嗤栓;

????????基于流行的Perl實(shí)現(xiàn)的正則表達(dá)式類庫冻河;

????????文件通道FileChannel。

????????新的NIO類庫的提供茉帅,極大地促進(jìn)了基于Java的異步非阻塞編程的發(fā)展和應(yīng)用叨叙,但是,它依然有不完善的地方堪澎,特別是對(duì)文件系統(tǒng)的處理能力仍顯不足擂错,主要問題如下:

????????沒有統(tǒng)一的文件屬性(例如讀寫權(quán)限);

????????API能力比較弱樱蛤,例如目錄的級(jí)聯(lián)創(chuàng)建和遞歸遍歷钮呀,往往需要自己實(shí)現(xiàn);

????????底層存儲(chǔ)系統(tǒng)的一些高級(jí)API無法使用昨凡;

????????所有的文件操作都是同步阻塞調(diào)用爽醋,不支持異步文件讀寫操作。

????????2011年7月28日便脊,JDK1.7正式發(fā)布蚂四。它的一個(gè)比較大的亮點(diǎn)就是將原來的NIO類庫進(jìn)行了升級(jí),被稱為NIO2.0就轧。NIO2.0由JSR-203演進(jìn)而來证杭,它主要提供了如下三個(gè)方面的改進(jìn):

????????提供能夠批量獲取文件屬性的API,這些API具有平臺(tái)無關(guān)性妒御,不與特性的文件系統(tǒng)相耦合解愤,另外它還提供了標(biāo)準(zhǔn)文件系統(tǒng)的SPI,供各個(gè)服務(wù)提供商擴(kuò)展實(shí)現(xiàn)乎莉;

????????提供AIO功能送讲,支持基于文件的異步I/O操作和針對(duì)網(wǎng)絡(luò)套接字的異步操作;

????????完成JSR-51定義的通道功能惋啃,包括對(duì)配置和多播數(shù)據(jù)報(bào)的支持等哼鬓。

????????上文講述了UNIX環(huán)境的五種IO模型”呙穑基于這五種模型异希,在Java中,隨著NIO和NIO2.0(AIO)的引入绒瘦,一般具有BIO称簿、NIO和AIO網(wǎng)絡(luò)編程模型扣癣。

BIO

????????BIO是一個(gè)典型的網(wǎng)絡(luò)編程模型,是通常我們實(shí)現(xiàn)一個(gè)服務(wù)端程序的過程憨降,步驟如下:

????????主線程accept請(qǐng)求阻塞父虑;

????????請(qǐng)求到達(dá),創(chuàng)建新的線程來處理這個(gè)套接字授药,完成對(duì)客戶端的響應(yīng)士嚎;

????????主線程繼續(xù)accept下一個(gè)請(qǐng)求。

????????這種模型有一個(gè)很大的問題是:當(dāng)客戶端連接增多時(shí)悔叽,服務(wù)端創(chuàng)建的線程也會(huì)暴漲莱衩,系統(tǒng)性能會(huì)急劇下降。因此骄蝇,在此模型的基礎(chǔ)上膳殷,類似于 tomcat的bio connector操骡,采用的是線程池來避免對(duì)于每一個(gè)客戶端都創(chuàng)建一個(gè)線程九火。有些地方把這種方式叫做偽異步IO(把請(qǐng)求拋到線程池中異步等待處理)。

NIO

????????JDK1.4開始引入了NIO類庫册招,這里的NIO指的是Non-blcok IO岔激,主要是使用Selector多路復(fù)用器來實(shí)現(xiàn)。Selector在Linux等主流操作系統(tǒng)上是通過epoll實(shí)現(xiàn)的是掰。

????????NIO的實(shí)現(xiàn)流程虑鼎,類似于select:

????????創(chuàng)建ServerSocketChannel監(jiān)聽客戶端連接并綁定監(jiān)聽端口,設(shè)置為非阻塞模式键痛;

????????創(chuàng)建Reactor線程炫彩,創(chuàng)建多路復(fù)用器(Selector)并啟動(dòng)線程;

????????將ServerSocketChannel注冊(cè)到Reactor線程的Selector上絮短。監(jiān)聽accept事件江兢;

????????Selector在線程run方法中無線循環(huán)輪詢準(zhǔn)備就緒的Key;

????????Selector監(jiān)聽到新的客戶端接入丁频,處理新的請(qǐng)求杉允,完成tcp三次握手,建立物理連接席里;

????????將新的客戶端連接注冊(cè)到Selector上叔磷,監(jiān)聽讀操作。讀取客戶端發(fā)送的網(wǎng)絡(luò)消息奖磁;

????????客戶端發(fā)送的數(shù)據(jù)就緒則讀取客戶端請(qǐng)求改基,進(jìn)行處理。

????????相比BIO咖为,NIO的編程非常復(fù)雜秕狰。

AIO

????????JDK1.7引入NIO2.0嵌洼,提供了異步文件通道和異步套接字通道的實(shí)現(xiàn),是真正的異步非阻塞IO, 對(duì)應(yīng)于Unix中的異步IO封恰。

????????通常會(huì)有一個(gè)線程池用于執(zhí)行異步任務(wù)麻养,提交任務(wù)的線程將任務(wù)提交到線程池就可以立馬返回,不必等到任務(wù)真正完成诺舔。如果想要知道任務(wù)的執(zhí)行結(jié)果鳖昌,通常是通過傳遞一個(gè)回調(diào)函數(shù),任務(wù)結(jié)束后去調(diào)用這個(gè)函數(shù)或者Future get(需要用時(shí)編碼阻塞獲取)的方式低飒。同樣的原理许昨,Java 中的異步?IO 也是一樣的,都是由一個(gè)線程池來負(fù)責(zé)執(zhí)行任務(wù)褥赊,然后使用回調(diào)或自己去查詢結(jié)果糕档。異步?IO 主要是為了控制線程數(shù)量,減少過多的線程帶來的內(nèi)存消耗和?CPU 在線程調(diào)度上的開銷拌喉。

????????NIO的實(shí)現(xiàn)流程如下:

????????創(chuàng)建AsynchronousServerSocketChannel速那,綁定監(jiān)聽端口;

????????調(diào)用AsynchronousServerSocketChannel的accpet方法尿背,傳入自己實(shí)現(xiàn)的CompletionHandler(回調(diào)函數(shù))端仰。包括上一步,都是非阻塞的田藐;

????????連接傳入荔烧,回調(diào)CompletionHandler的completed方法,在里面汽久,調(diào)用AsynchronousSocketChannel的read方法鹤竭,傳入負(fù)責(zé)處理數(shù)據(jù)的CompletionHandler;

????????數(shù)據(jù)就緒景醇,觸發(fā)負(fù)責(zé)處理數(shù)據(jù)的CompletionHandler的completed方法臀稚。繼續(xù)做下一步處理即可。

????????寫入操作類似啡直,也需要傳入CompletionHandler烁涌。

總結(jié):

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市酒觅,隨后出現(xiàn)的幾起案子撮执,更是在濱河造成了極大的恐慌,老刑警劉巖舷丹,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抒钱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)谋币,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門仗扬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蕾额,你說我怎么就攤上這事早芭。” “怎么了诅蝶?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵退个,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我调炬,道長(zhǎng)语盈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任缰泡,我火速辦了婚禮刀荒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘棘钞。我一直安慰自己缠借,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布武翎。 她就那樣靜靜地躺著烈炭,像睡著了一般溶锭。 火紅的嫁衣襯著肌膚如雪宝恶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天趴捅,我揣著相機(jī)與錄音垫毙,去河邊找鬼。 笑死拱绑,一個(gè)胖子當(dāng)著我的面吹牛综芥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猎拨,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼膀藐,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了红省?” 一聲冷哼從身側(cè)響起额各,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吧恃,沒想到半個(gè)月后虾啦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年傲醉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蝇闭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡硬毕,死狀恐怖呻引,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吐咳,我是刑警寧澤苞七,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站挪丢,受9級(jí)特大地震影響蹂风,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乾蓬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一惠啄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧任内,春花似錦撵渡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至越除,卻和暖如春节腐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背摘盆。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工翼雀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人孩擂。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓狼渊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親类垦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狈邑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容