學(xué)習(xí)架構(gòu)師技術(shù)聯(lián)盟的深入聊聊Linux五種IO模型偎巢,筆記總結(jié)如下庭惜。
背景
大多數(shù)文件系統(tǒng)的默認(rèn) IO 操作都是緩存 IO,又被稱作標(biāo)準(zhǔn)IO。在 Linux 的緩存 IO 機(jī)制中,操作系統(tǒng)會(huì)將 IO 的數(shù)據(jù)緩存在文件系統(tǒng)的頁緩存(page cache )中,也就是說却妨,數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中,然后才會(huì)從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間括眠。
緩存 IO 的缺點(diǎn):數(shù)據(jù)在傳輸過程中需要在應(yīng)用程序地址空間和內(nèi)核進(jìn)行多次數(shù)據(jù)拷貝操作彪标,這些數(shù)據(jù)拷貝操作所帶來的 CPU 以及內(nèi)存開銷是非常大的。
網(wǎng)絡(luò)IO的本質(zhì)是socket的讀取掷豺,socket在linux系統(tǒng)被抽象為流捞烟,IO可以理解為對(duì)流的操作账锹。對(duì)于一次IO訪問(以read舉例),數(shù)據(jù)會(huì)先被拷貝到操作系統(tǒng)內(nèi)核的緩沖區(qū)中坷襟,然后從操作系統(tǒng)內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的地址空間奸柬。
當(dāng)一個(gè)read操作發(fā)生時(shí),經(jīng)歷兩個(gè)階段:
第一階段:等待數(shù)據(jù)準(zhǔn)備 (Waiting for? the data to be ready)婴程。
第二階段:將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程中? (Copying the data from the kernel to the process)廓奕。
對(duì)于socket流而言,
第一步:通常涉及等待網(wǎng)絡(luò)上的數(shù)據(jù)分組到達(dá)档叔,然后被復(fù)制到內(nèi)核的某個(gè)緩沖區(qū)桌粉。
第二步:把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)。
網(wǎng)絡(luò)應(yīng)用需要處理的無非就是兩大類問題衙四,網(wǎng)絡(luò)IO和數(shù)據(jù)計(jì)算铃肯。相對(duì)于后者,網(wǎng)絡(luò)IO的延遲传蹈,給應(yīng)用帶來的性能瓶頸大于后者押逼,我們重點(diǎn)關(guān)注前者。
網(wǎng)絡(luò)IO的模型
網(wǎng)絡(luò)IO的模型大致分為6種:
· 同步IO(synchronous IO)
· 阻塞IO(bloking IO)
· 非阻塞IO(non-blocking IO)
· 多路復(fù)用IO(multiplexing IO)
· 信號(hào)驅(qū)動(dòng)IO(signal-driven IO)
· 異步IO(asynchronous IO)
相關(guān)概念介紹:
同步是一個(gè)任務(wù)的完成需要依賴另外一個(gè)任務(wù)時(shí)惦界,只有等待被依賴的任務(wù)完成后挑格,依賴的任務(wù)才能算完成,這是一種可靠的任務(wù)序列沾歪。要么都成功漂彤,要么都失敗,兩個(gè)任務(wù)的狀態(tài)可以保持一致灾搏。
異步是不需要等待被依賴的任務(wù)完成挫望,只是通知被依賴的任務(wù)要完成什么工作,依賴的任務(wù)也立即執(zhí)行狂窑,只要自己完成了整個(gè)任務(wù)就算完成了媳板。至于被依賴的任務(wù)最終是否真正完成,依賴它的任務(wù)無法確定蕾域,所以它是不可靠的任務(wù)序列拷肌。
阻塞調(diào)用指調(diào)用結(jié)果返回之前到旦,當(dāng)前線程會(huì)被掛起旨巷,一直處于等待消息通知,不能夠執(zhí)行其他業(yè)務(wù)添忘。函數(shù)只有在得到結(jié)果之后才會(huì)返回采呐。
非阻塞調(diào)用指在不能立刻得到結(jié)果之前,該函數(shù)不會(huì)阻塞當(dāng)前線程搁骑,而會(huì)立刻返回斧吐。雖然表面上看非阻塞的方式可以明顯的提高CPU的利用率又固,但是也帶了另外一種后果就是系統(tǒng)的線程切換增加。增加的CPU執(zhí)行時(shí)間能不能補(bǔ)償系統(tǒng)的切換成本需要好好評(píng)估煤率。
(a) 如果這個(gè)線程在等待當(dāng)前函數(shù)返回時(shí)仰冠,仍在執(zhí)行其他消息處理,那這種情況就叫做同步非阻塞蝶糯;
(b) 如果這個(gè)線程在等待當(dāng)前函數(shù)返回時(shí)洋只,沒有執(zhí)行其他消息處理,而是處于掛起等待狀態(tài)昼捍,那這種情況就叫做同步阻塞识虚;
同步/異步關(guān)注的是消息通知的機(jī)制,而阻塞/非阻塞關(guān)注的是程序(線程)等待消息通知時(shí)的狀態(tài):等待消息的同時(shí)是否處理其他消息妒茬。
正在執(zhí)行的進(jìn)程担锤,由于期待的某些事件未發(fā)生,如請(qǐng)求系統(tǒng)資源失敗乍钻、等待某種操作的完成肛循、新數(shù)據(jù)尚未到達(dá)或無新工作做等,則由系統(tǒng)自動(dòng)執(zhí)行阻塞原語(Block)银择,使自己由運(yùn)行狀態(tài)變?yōu)樽枞麪顟B(tài)育拨。可見欢摄,進(jìn)程的阻塞是進(jìn)程自身的一種主動(dòng)行為熬丧,也因此只有處于運(yùn)行態(tài)的進(jìn)程(獲得CPU),才可能將其轉(zhuǎn)為阻塞狀態(tài)怀挠。當(dāng)進(jìn)程進(jìn)入阻塞狀態(tài)析蝴,是不占用CPU資源的。
為了控制進(jìn)程的執(zhí)行绿淋,內(nèi)核必須有能力掛起正在CPU上運(yùn)行的進(jìn)程闷畸,并恢復(fù)以前掛起的某個(gè)進(jìn)程的執(zhí)行。這種行為被稱為進(jìn)程切換吞滞。從一個(gè)進(jìn)程的運(yùn)行轉(zhuǎn)到另一個(gè)進(jìn)程上運(yùn)行佑菩,這個(gè)過程很消耗資源,經(jīng)過下面這些變化:
1裁赠、保存處理機(jī)上下文殿漠,包括程序計(jì)數(shù)器和其他寄存器。
2佩捞、更新PCB信息绞幌。
3、把進(jìn)程的PCB移入相應(yīng)的隊(duì)列一忱,如就緒莲蜘、在某事件阻塞等隊(duì)列谭确。
4、選擇另一個(gè)進(jìn)程執(zhí)行票渠,并更新其PCB逐哈。
5、更新內(nèi)存管理的數(shù)據(jù)結(jié)構(gòu)问顷。
6鞠眉、恢復(fù)處理機(jī)上下文。
注:總而言之就是很耗資源
網(wǎng)絡(luò)IO模型介紹
1择诈、阻塞IO模型
應(yīng)用程序調(diào)用一個(gè) IO 函數(shù)械蹋,導(dǎo)致應(yīng)用程序阻塞,等待數(shù)據(jù)準(zhǔn)備好羞芍。 如果數(shù)據(jù)沒有準(zhǔn)備好哗戈,一直等待…數(shù)據(jù)準(zhǔn)備好了,從內(nèi)核拷貝到用戶空間荷科,IO 函數(shù)返回成功指示唯咬。
2. 非阻塞IO模型
把一個(gè) SOCKET 接口設(shè)置為非阻塞就是告訴內(nèi)核,當(dāng)所請(qǐng)求的 I/O 操作無法完成時(shí)畏浆,不要將進(jìn)程睡眠胆胰,而是返回一個(gè)錯(cuò)誤。這樣我們的 I/O 操作函數(shù)將不斷的檢查數(shù)據(jù)是否已經(jīng)準(zhǔn)備好刻获,直到準(zhǔn)備好為止蜀涨。在這個(gè)不斷檢查的過程中,會(huì)大量的占用 CPU 的時(shí)間蝎毡。上述模型絕不被推薦厚柳。
3. 異步IO模型
相對(duì)于同步IO,異步IO不是順序執(zhí)行沐兵。用戶進(jìn)程進(jìn)行aio_read系統(tǒng)調(diào)用之后别垮,無論內(nèi)核數(shù)據(jù)是否準(zhǔn)備好,都會(huì)直接返回給用戶進(jìn)程扎谎,然后用戶態(tài)進(jìn)程可以去做別的事情碳想。等到socket數(shù)據(jù)準(zhǔn)備好了,內(nèi)核直接復(fù)制數(shù)據(jù)給進(jìn)程毁靶,然后從內(nèi)核向進(jìn)程發(fā)送通知胧奔。IO兩個(gè)階段,進(jìn)程都是非阻塞的老充。
Linux提供了AIO庫函數(shù)實(shí)現(xiàn)異步葡盗,但是用的很少。目前有很多開源的異步IO庫啡浊,例如libevent觅够、libev、libuv巷嚣。
4. 多路復(fù)用IO
由于同步非阻塞方式需要不斷主動(dòng)輪詢喘先,輪詢占據(jù)了很大一部分過程,輪詢會(huì)消耗大量的CPU時(shí)間廷粒,而 “后臺(tái)” 可能有多個(gè)任務(wù)在同時(shí)進(jìn)行窘拯,如果能將循環(huán)查詢的多個(gè)任務(wù)只留任何一個(gè)任務(wù)來做這個(gè)事情就好了,這就是所謂的 “IO 多路復(fù)用”坝茎。
IO多路復(fù)用有兩個(gè)特別的系統(tǒng)調(diào)用select涤姊、poll、epoll函數(shù)嗤放。select調(diào)用是內(nèi)核級(jí)別的思喊,select輪詢相對(duì)非阻塞的輪詢的區(qū)別在于---前者可以等待多個(gè)socket,能實(shí)現(xiàn)同時(shí)對(duì)多個(gè)IO端口進(jìn)行監(jiān)聽次酌,當(dāng)其中任何一個(gè)socket的數(shù)據(jù)準(zhǔn)好了恨课,就能返回進(jìn)行可讀,然后進(jìn)程再進(jìn)行recvform系統(tǒng)調(diào)用岳服,將數(shù)據(jù)由內(nèi)核拷貝到用戶進(jìn)程剂公,當(dāng)然這個(gè)過程是阻塞的。select或poll調(diào)用之后吊宋,會(huì)阻塞進(jìn)程纲辽,與blocking IO阻塞不同在于,此時(shí)的select不是等到socket數(shù)據(jù)全部到達(dá)再處理, 而是有了一部分?jǐn)?shù)據(jù)就會(huì)調(diào)用用戶進(jìn)程來處理璃搜。如何知道有一部分?jǐn)?shù)據(jù)到達(dá)了呢文兑?監(jiān)視的事情交給了內(nèi)核,內(nèi)核負(fù)責(zé)數(shù)據(jù)到達(dá)的處理腺劣。也可以理解為"非阻塞"吧绿贞。
對(duì)于多路復(fù)用,也就是輪詢多個(gè)socket橘原。多路復(fù)用既然可以處理多個(gè)IO籍铁,也就帶來了新的問題,多個(gè)IO之間的順序變得不確定了趾断,當(dāng)然也可以針對(duì)不同的編號(hào)拒名。
在I/O編程過程中,當(dāng)需要同時(shí)處理多個(gè)客戶端接入請(qǐng)求時(shí)芋酌,可以利用多線程或者I/O多路復(fù)用技術(shù)進(jìn)行處理增显。I/O多路復(fù)用技術(shù)通過把多個(gè)I/O的阻塞復(fù)用到同一個(gè)select的阻塞上,從而使得系統(tǒng)在單線程的情況下可以同時(shí)處理多個(gè)客戶端請(qǐng)求脐帝。與傳統(tǒng)的多線程/多進(jìn)程模型比同云,I/O多路復(fù)用的最大優(yōu)勢是系統(tǒng)開銷小糖权,系統(tǒng)不需要?jiǎng)?chuàng)建新的額外進(jìn)程或者線程,也不需要維護(hù)這些進(jìn)程和線程的運(yùn)行炸站,降底了系統(tǒng)的維護(hù)工作量星澳,節(jié)省了系統(tǒng)資源,I/O多路復(fù)用的主要應(yīng)用場景如下:
1旱易、服務(wù)器需要同時(shí)處理多個(gè)處于監(jiān)聽狀態(tài)或者多個(gè)連接狀態(tài)的套接字禁偎。
2、服務(wù)器需要同時(shí)處理多種網(wǎng)絡(luò)協(xié)議的套接字阀坏。
此時(shí)你是不是想到的了redis如何做的啊如暖,redis用的就是多路復(fù)用。
5. 信號(hào)驅(qū)動(dòng)IO
簡介:兩次調(diào)用忌堂,兩次返回盒至;
首先我們?cè)试S套接口進(jìn)行信號(hào)驅(qū)動(dòng) I/O,并安裝一個(gè)信號(hào)處理函數(shù),進(jìn)程繼續(xù)運(yùn)行并不阻塞浸船。當(dāng)數(shù)據(jù)準(zhǔn)備好時(shí)妄迁,進(jìn)程會(huì)收到一個(gè) SIGIO 信號(hào),可以在信號(hào)處理函數(shù)中調(diào)用 I/O 操作函數(shù)處理數(shù)據(jù)李命。