出處
阻塞 IO, 非阻塞 IO, 同步 IO, 異步 IO 這些術(shù)語(yǔ)相信有不少朋友都也不同程度的困惑吧? 我原來(lái)也是, 什么同步非阻塞 IO, 異步非阻塞 IO 的, 搞的頭都大了. 后來(lái)仔細(xì)讀了一遍
《UNIX 網(wǎng)絡(luò)編程卷一 套接字聯(lián)網(wǎng) API(第三版)》的 6.2 章節(jié), 終于把這些名詞搞懂了.
下面我以《UNIX 網(wǎng)絡(luò)編程卷一 套接字聯(lián)網(wǎng) API(第三版)》的 6.2 章節(jié)的內(nèi)容為準(zhǔn), 整理了一下各種網(wǎng)絡(luò) IO 模型具體定義以及一些容易混淆的地方.
簡(jiǎn)介
Unix 下有 5 種可用的 IO 模型, 分別是:
- 阻塞式 I/O
- 非阻塞式 I/O
- I/O 復(fù)用(select 和 poll)
- 信號(hào)驅(qū)動(dòng)式 I/O (SIGIO)
- 異步 I/O (POSIX 的 aio_系列函數(shù))
阻塞式 I/O 模型
最流行的 IO 操作是阻塞式 IO(Blocking IO). 以 UDP 數(shù)據(jù)報(bào)套接字為例, 下圖是其阻塞 IO 的調(diào)用過(guò)程:
在上圖中, 進(jìn)程調(diào)用 recvfrom, 其系統(tǒng)調(diào)用直到數(shù)據(jù)報(bào)返回并且被復(fù)制到應(yīng)用進(jìn)程的緩沖區(qū)中 或者發(fā)送錯(cuò)誤時(shí)才返回. 因此進(jìn)程在調(diào)用 recvfrom 開(kāi)始到它返回的整段時(shí)間內(nèi)都是被阻塞的.
非阻塞式 IO(Non-Blocking IO)
進(jìn)程把一個(gè)套接字設(shè)置為非阻塞是在通知內(nèi)核: 當(dāng)調(diào)用線程所請(qǐng)求的 IO 操作需要調(diào)用線程休眠來(lái)等待操作完成時(shí), 此時(shí)不要將調(diào)用線程休眠, 而是返回一個(gè)錯(cuò)誤
.
如上圖所示, 前三次調(diào)用 recvfrom 時(shí), 沒(méi)有數(shù)據(jù)可返回, 因此內(nèi)核轉(zhuǎn)而立即返回一個(gè) EWOULDBLOCK 錯(cuò)誤. 第四次調(diào)用 recvfrom 時(shí), 已經(jīng)有數(shù)據(jù)了, 此時(shí), recvfrom 會(huì)阻塞住, 等待內(nèi)核將數(shù)據(jù)賦值到應(yīng)用進(jìn)程的緩沖區(qū)中, 然后再返回.
(注意, 當(dāng)有數(shù)據(jù)時(shí), recvfrom 是阻塞的, 它會(huì)等待內(nèi)核將數(shù)據(jù)復(fù)制到應(yīng)用進(jìn)程的緩沖區(qū)后, 才返回).
當(dāng)一個(gè)應(yīng)用進(jìn)程像這樣對(duì)一個(gè)非阻塞描述符循環(huán)調(diào)用 recvfrom 時(shí), 我們稱(chēng)之為輪詢(xún)(polling). 應(yīng)用進(jìn)程持續(xù)輪詢(xún)內(nèi)核, 以查看某個(gè)操作是否完成, 這么做會(huì)消耗大量的 CPU 時(shí)間, 不過(guò)這種模型偶爾也會(huì)遇到, 通常是專(zhuān)門(mén)提供某一種功能的系統(tǒng)中才有.
IO 復(fù)用模型
有了 IO 復(fù)用(IO multiplexing), 我們就可以調(diào)用 select 或 poll, 阻塞在這兩個(gè)系統(tǒng)調(diào)用中的某一個(gè)之上, 而不是阻塞在真正的 IO 系統(tǒng)調(diào)用上. 例如:
如上圖所示, 當(dāng)調(diào)用了 select 后, select 會(huì)阻塞住, 等待數(shù)據(jù)報(bào)套接字變?yōu)榭勺x. 當(dāng) select 返回套接字可讀這一條件時(shí), 我們就可以調(diào)用 recvfrom 把所讀取的數(shù)據(jù)報(bào)復(fù)制到應(yīng)用進(jìn)程緩沖區(qū).
對(duì)比阻塞式 IO, IO 復(fù)用模型優(yōu)勢(shì)并不明顯, 并且從使用方式來(lái)說(shuō), IO 復(fù)用模型還需要多調(diào)用一次 select, 因此從易用性上來(lái)說(shuō), 比阻塞式 IO 還略有不足. 不過(guò) select 的殺手锏在于它可以監(jiān)聽(tīng)多個(gè)文件描述符, 大大減小了阻塞線程的個(gè)數(shù).
信號(hào)驅(qū)動(dòng) IO 模型
信號(hào)驅(qū)動(dòng)模型如上圖所示. 當(dāng)文件描述符就緒時(shí), 我們可以讓內(nèi)核以信號(hào)的方式通知我們.
我們首先需要開(kāi)啟套接字的信號(hào)驅(qū)動(dòng)式 IO 功能, 并通過(guò) sigaction 系統(tǒng)調(diào)用安裝一個(gè)信號(hào)處理函數(shù). sigaction 系統(tǒng)調(diào)用是異步的, 它會(huì)立即返回. 當(dāng)有數(shù)據(jù)時(shí), 內(nèi)核會(huì)給此進(jìn)程發(fā)送一個(gè) SIGIO 信號(hào), 進(jìn)而我們的信號(hào)處理函數(shù)就會(huì)被執(zhí)行, 我們就可以在這個(gè)函數(shù)中調(diào)用 recvfrom 讀取數(shù)據(jù).
異步 IO 模型
異步 IO (asynchronous IO) 由 POSIX 規(guī)范定義, 在 POSIX 中定義了若干個(gè)異步 IO 的操作函數(shù). 這個(gè)函數(shù)的工作原理是: 告知內(nèi)核啟動(dòng)某個(gè)動(dòng)作, 并讓內(nèi)核在整個(gè)操作(包括將數(shù)據(jù)從內(nèi)核復(fù)制到應(yīng)用進(jìn)程緩沖區(qū)
)完成后通知我們的應(yīng)用進(jìn)程.
異步 IO 模型和信號(hào)驅(qū)動(dòng)的 IO 模型的主要區(qū)別在于: 信號(hào)驅(qū)動(dòng) IO 是由內(nèi)核通知我們何時(shí)可以啟動(dòng)一個(gè) IO 操作, 而異步 IO 模型是由內(nèi)核通知我們 IO 操作何時(shí)完成.
異步 IO 模型的操作過(guò)程如圖所示:
當(dāng)我們調(diào)用 aio_read 函數(shù)時(shí)(POSIX 異步 IO 函數(shù)以 aio_或 lio_ 開(kāi)頭), 給內(nèi)核傳遞描述符, 緩沖區(qū)指針, 緩沖區(qū)大小(和 read 相同的三個(gè)參數(shù)) 和文件偏移(以 lseek 類(lèi)似), 并告訴內(nèi)核當(dāng)整個(gè)操作完成時(shí)如何通知應(yīng)用進(jìn)程. 該系統(tǒng)調(diào)用立即返回, 而且在等待 IO 完成期間, 應(yīng)用進(jìn)程不被阻塞.
各種 IO 模型的比較
如圖所示, 上述五中 IO 模型中, 前四種模型(阻塞 IO, 非阻塞 IO, IO 復(fù)用, 信號(hào)驅(qū)動(dòng) IO)的主要區(qū)別在于第一階段, 因?yàn)樗麄兊牡诙A段是一樣的: 在數(shù)據(jù)從內(nèi)核復(fù)制到調(diào)用者的緩沖區(qū)期間, 進(jìn)程阻塞于 recvfrom 調(diào)用. 而第五種, 即異步 IO 模型中, 兩個(gè)階段都不需要應(yīng)用進(jìn)程處理, 內(nèi)核為我們處理好了數(shù)據(jù)的等待和數(shù)據(jù)的復(fù)制過(guò)程.
關(guān)于同步 IO 和異步 IO
根據(jù) POSIX 定義:
- A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes(導(dǎo)致請(qǐng)求進(jìn)程阻塞, 直到 IO 操作完成).
- An asynchronous I/O operation does not cause the requesting process to be blocked(不導(dǎo)致請(qǐng)求進(jìn)程阻塞).
根據(jù)上述定義, 我們的前四種模型: 阻塞 IO 模型, 非阻塞 IO 模型, IO 復(fù)用模型和信號(hào)驅(qū)動(dòng) IO 模型都是同步 IO 模型, 因?yàn)槠渲姓嬲?IO 操作(recvfrom 調(diào)用) 會(huì)阻塞進(jìn)程(因?yàn)楫?dāng)有數(shù)據(jù)時(shí), recvfrom 會(huì)阻塞等待內(nèi)核將數(shù)據(jù)從內(nèi)核空間復(fù)制到應(yīng)用進(jìn)程空間, 當(dāng)賦值完成后, recvfrom 才返回.
) 只有異步 IO 模型與 POSIX 定義的異步 IO 相匹配.
總結(jié)
在處理網(wǎng)絡(luò) IO 操作時(shí), 阻塞和非阻塞 IO 都是同步 IO.
只有調(diào)用了特殊的 API 才是異步 IO.
因此網(wǎng)上常說(shuō)的 "同步阻塞 IO", "同步非阻塞 IO" 其實(shí)就是阻塞 IO 模型和非阻塞 IO 模型, 因?yàn)樽枞?IO 和非阻塞 IO 模型都是同步的, 加了 "同步" 二字其實(shí)是多余了.
網(wǎng)絡(luò)上常說(shuō)的 "異步非阻塞 IO" 其實(shí)就是異步 IO 模型.