其實 Linux IO 模型沒那么難
IO 其實就是 Input 和 Output,在操作系統(tǒng)中就對應(yīng)數(shù)據(jù)流的輸入與輸出。這個數(shù)據(jù)流的兩端,可以是文件,也可以是網(wǎng)絡(luò)的一臺主機(jī)。但無論是文件,還是網(wǎng)絡(luò)主機(jī),其傳輸都是類似的,我們今天就以源頭為文件進(jìn)行說明。
一個文件要從磁盤到我們的內(nèi)存,需要經(jīng)過很復(fù)雜的操作。首先,需要將數(shù)據(jù)從硬件讀取出來,然后放入操作系統(tǒng)內(nèi)核緩沖區(qū),之后再將數(shù)據(jù)拷貝到程序緩沖區(qū),最后應(yīng)用程序才能讀取到這個文件。簡單地說,無論什么 IO 模型,其讀取過程總會經(jīng)歷下面兩個階段:
- 等待數(shù)據(jù)到達(dá)內(nèi)核緩沖區(qū)
- 從內(nèi)核緩沖區(qū)拷貝數(shù)據(jù)到程序緩沖區(qū)
而我們 Linux 根據(jù)這兩個階段的是否阻塞,分成了 5 個經(jīng)典的 IO 的模型,分別是:
- 阻塞 IO 模型
- 非阻塞 IO 模型
- IO 復(fù)用模型
- 信號驅(qū)動 IO 模型
- 異步 IO 模型
阻塞 IO 模型
阻塞 IO 稱為 Blocking IO,簡稱 BIO。在阻塞 IO 模型中,當(dāng)進(jìn)程發(fā)起一個讀取文件請求(recvfrom 系統(tǒng)調(diào)用)時,如果內(nèi)核緩存區(qū)沒有對應(yīng)的數(shù)據(jù),那么它不會立刻恢復(fù),而是去讀取磁盤數(shù)據(jù),當(dāng)數(shù)據(jù)讀取完畢后,再返回給進(jìn)程。此時,第一個階段完成。在這個階段進(jìn)程是阻塞的,因為它要等待內(nèi)核將數(shù)據(jù)讀取到內(nèi)核緩沖區(qū)。
而當(dāng)進(jìn)程收到內(nèi)核的響應(yīng)之后,進(jìn)程再把數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到程序緩沖區(qū),最后完成文件讀取操作。此時,第二個階段完成。在這個階段進(jìn)程也是阻塞的,因為它要將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到程序緩沖區(qū)。
簡單地說:在阻塞 IO 模型里,從硬件到系統(tǒng)內(nèi)核、從系統(tǒng)內(nèi)核到程序空間,都是阻塞的。
非阻塞 IO 模型
在非阻塞 IO 模型下,當(dāng)一個請求發(fā)起讀取文件請求(recvfrom)時,如果內(nèi)核緩沖區(qū)沒有數(shù)據(jù),那么內(nèi)核會讀取文件數(shù)據(jù)。但此時請求并不會阻塞,而是返回一個錯誤信息(EWOULDBLOCK)告訴進(jìn)程:數(shù)據(jù)暫時還沒準(zhǔn)備好,你待會兒再試試。
于是進(jìn)程就不斷地向內(nèi)核重試,問:數(shù)據(jù)準(zhǔn)備好了沒有,數(shù)據(jù)準(zhǔn)備好了沒有……當(dāng)內(nèi)核準(zhǔn)備好數(shù)據(jù),進(jìn)程就會收到對應(yīng)消息,于是第一階段就結(jié)束了。非阻塞 IO 中的非阻塞說的就是進(jìn)程不會阻塞在這里,而是會不斷重試。
雖然說這樣并沒有太大用處,反而會使得 CPU 空轉(zhuǎn),但總比之前有了一點進(jìn)步。在這個階段進(jìn)程并不是阻塞的。當(dāng)進(jìn)程得知內(nèi)核準(zhǔn)備好數(shù)據(jù)之后,其便會將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到程序緩沖區(qū)。這個階段與阻塞 I/O 模型是完全一樣的,同樣是會導(dǎo)致進(jìn)程阻塞。
簡單地說:在非阻塞 IO 模型里,從硬件到系統(tǒng)內(nèi)核、從系統(tǒng)內(nèi)核到程序空間,同樣都是阻塞的。但是其比阻塞 IO 爭氣了一點,并不是站在那里不動,好歹還跑了一下。雖然是在做無用功,但是好歹提高了一丟丟效率。
IO 復(fù)用模型
IO 復(fù)用之所以叫復(fù)用,是因為其能同時操作多個數(shù)據(jù)流。而前面的 阻塞 IO、非阻塞 IO 同一時間只能操作一個數(shù)據(jù)流。在 IO 復(fù)用模型中,進(jìn)程監(jiān)聽多個數(shù)據(jù)流并阻塞,當(dāng)任何一個數(shù)據(jù)流有數(shù)據(jù)之后,其便會收到內(nèi)核的響應(yīng)。此時,第一個階段完成,在這個階段進(jìn)程其實是阻塞的。
而當(dāng)收到內(nèi)核的響應(yīng)后,進(jìn)程便會將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到程序緩沖區(qū)。這個階段與上面兩個模型一模一樣,進(jìn)程同樣阻塞。
簡單地說:IO 復(fù)用模型在第二階段與阻塞 IO 和非阻塞 IO 是完全一致的。但是在第一階段上,其有效率上的巨大提升,其能同時輪詢多個數(shù)據(jù)流,提高了效率。
信號驅(qū)動 IO 模型
信號驅(qū)動與前面幾個模型的不同之處就在于信號這個詞。信號驅(qū)動 IO 在第一階段,即數(shù)據(jù)到達(dá)內(nèi)核緩沖區(qū)之前,進(jìn)程是不阻塞的,而是設(shè)置一個信號回調(diào)。當(dāng)數(shù)據(jù)到達(dá)內(nèi)核緩沖區(qū)之后,內(nèi)核調(diào)用程序的回調(diào)。通過這種方式,信號驅(qū)動 IO 下的進(jìn)程就可以不阻塞,可以去做其他事情了。
而當(dāng)進(jìn)程收到信號,進(jìn)程再將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到程序緩沖區(qū)。這個過程與上面幾個是完全一樣的,同樣也是阻塞的。
信號驅(qū)動 IO 可以說是 IO 讀取的一個里程碑,其真正實現(xiàn)了異步讀取數(shù)據(jù)。信號驅(qū)動 IO 其二個階段,與上面幾個是一樣的。但是其在第一個階段做到了真正的異步。信號驅(qū)動 IO 在第一階段,其去請求內(nèi)核讀取數(shù)據(jù),這時候其不會阻塞,也不會去尋輪,而是設(shè)置一個信號回調(diào)。 當(dāng)數(shù)據(jù)完全拷貝到系統(tǒng)內(nèi)核時,系統(tǒng)發(fā)出 SIGIO 信號,通知進(jìn)程去進(jìn)行第二階段,將數(shù)據(jù)拷貝到程序緩沖區(qū)。
異步 IO 模型
異步 IO 相比前面幾個流程,真正做到了完全非阻塞。無論是在第一階段,還是在第二階段都是非阻塞。與信號驅(qū)動 IO 類似,異步 IO 模型通過信號回調(diào)的方式,在第一個階段實現(xiàn)了進(jìn)程的非阻塞。而當(dāng)數(shù)據(jù)到達(dá)內(nèi)核緩沖區(qū)之后,進(jìn)程便會收到通知。
而當(dāng)進(jìn)程收到通知之后,進(jìn)程再次將數(shù)據(jù)從內(nèi)核緩沖區(qū)復(fù)制到進(jìn)程緩沖區(qū),但這時進(jìn)程并不等待,而是同樣設(shè)置一個信號回調(diào)。當(dāng)復(fù)制完成后,進(jìn)程收到通知,再進(jìn)行相應(yīng)的處理。
異步 IO 與信號驅(qū)動 IO 相比,做得更加徹底了!
異步 IO 不僅僅是在第一階段實現(xiàn)了信號回調(diào),其也在第二階段實現(xiàn)了信號回調(diào),從而完全實現(xiàn)了異步 IO 操作。
總結(jié)
我們回顧一下這 5 種 IO 模型,按照其在兩個階段的特點區(qū)分:
- 阻塞 IO 模型:硬件到系統(tǒng)內(nèi)核,阻塞。系統(tǒng)內(nèi)核到程序空間,阻塞。
- 非阻塞 IO 模型:硬件到系統(tǒng)內(nèi)核,輪詢阻塞。系統(tǒng)內(nèi)核到程序空間,阻塞。
- 復(fù)用 IO 模型:硬件到系統(tǒng)內(nèi)核,多流輪詢阻塞。系統(tǒng)內(nèi)核到程序空間,阻塞。
- 信號驅(qū)動 IO 模型:硬件到系統(tǒng)內(nèi)核,信號回調(diào)不阻塞。系統(tǒng)內(nèi)核到程序空間,阻塞。
- 異步 IO 模型:硬件到系統(tǒng)內(nèi)核,信號回調(diào)不阻塞。系統(tǒng)內(nèi)核到程序空間,信號回調(diào)不阻塞。
從上面的 5 種 IO 模型,我們可以看出,真正實現(xiàn)異步非阻塞的只有異步 IO 這種模型,而其他四種都是同步性 IO。因為在第二階段:從內(nèi)核緩沖區(qū)復(fù)制到進(jìn)程緩沖區(qū)的時候,不可能干其他事情。
本文轉(zhuǎn)載自微信公眾號「陳樹義」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系陳樹義公眾號。