操作系統(tǒng)IO模型(譯)

在學(xué)習(xí)NIO之前,我們非常有必要了解一下操作系統(tǒng)中的各種IO模型,否則是不會(huì)理解NIO的實(shí)現(xiàn)的.

這篇文章是我翻譯I/O Multiplexing: The select and poll Functions這篇文章中的前半部分關(guān)于IO模型的部分.這篇文章中,還對(duì)select()等系統(tǒng)調(diào)用有更加深入的介紹,各位不妨讀一下.

正文

在Unix下,我們有五種不同的IO模型,分別是:

  • 阻塞IO(Blocking IO)
  • 非阻塞IO(Nonblocking IO)
  • I/O復(fù)用(I/O Multiplexing)
  • 信號(hào)驅(qū)動(dòng)IO(signal driven I/O)
  • 異步IO(Asynchronous IO)

對(duì)于一個(gè)讀操作來說,一般會(huì)經(jīng)過下面兩個(gè)過程:

  • 等待數(shù)據(jù)就緒.比如說,對(duì)于一個(gè)網(wǎng)絡(luò)連接來說,就是等待數(shù)據(jù)通過連接到達(dá)主機(jī).當(dāng)數(shù)據(jù)到達(dá)主機(jī)時(shí),把數(shù)據(jù)拷貝到內(nèi)核中的緩沖區(qū).
  • 將數(shù)據(jù)從內(nèi)核拷貝到進(jìn)程.即把數(shù)據(jù)從內(nèi)核的緩沖區(qū)拷貝到應(yīng)用程序的緩沖區(qū).

阻塞IO

最常用的IO模型就是阻塞IO.默認(rèn)情況下,全部的socket都是阻塞的.其處理過程如下圖所示:

在這個(gè)例子中,我們會(huì)通過UDP而不是TCP來舉例,因?yàn)閷?duì)于UDP來說,等待數(shù)據(jù)就緒這一步更加直觀:要不就是收到了一個(gè)數(shù)據(jù)報(bào),要不就是沒收到一個(gè)數(shù)據(jù)報(bào).但是對(duì)于TCP來說,還有很多額外的變量.

上圖中的recvfrom是一個(gè)系統(tǒng)調(diào)用.當(dāng)我們執(zhí)行一次系統(tǒng)調(diào)用的時(shí)候,有一次從用戶態(tài)到內(nèi)核態(tài)的切換.

從上圖中我們可以看到,進(jìn)程調(diào)用recvfrom之后,這個(gè)系統(tǒng)調(diào)用并不會(huì)立即返回,它會(huì)等到數(shù)據(jù)報(bào)到達(dá)并且被拷貝到應(yīng)用程序的緩沖區(qū)中,或者出現(xiàn)了一個(gè)錯(cuò)誤,才會(huì)返回.我們稱這個(gè)過程是阻塞的,應(yīng)用程序只有在數(shù)據(jù)報(bào)被放入緩沖區(qū)之后,才能繼續(xù)進(jìn)行.

非阻塞IO

非阻塞IO和阻塞IO相對(duì),它會(huì)告訴內(nèi)核,"當(dāng)我要你完成的IO操作不能完成時(shí),不要讓進(jìn)程阻塞,你給我返回一個(gè)錯(cuò)誤就行了".過程如下圖所示:

  • 在上面的三個(gè)recvfrom操作中,由于數(shù)據(jù)并沒有就緒,所以內(nèi)核返回了一個(gè)EWOULDBLOCK錯(cuò)誤.
  • 在第四個(gè)recvfrom中,數(shù)據(jù)已經(jīng)就緒了,并且已經(jīng)被拷貝到我們的應(yīng)用程序的緩沖區(qū)了,內(nèi)核返回一個(gè)OK,然后我們的應(yīng)用程序處理這些數(shù)據(jù).

我們可以看到,在這種模型中,我們需要使用輪詢的方式來確定數(shù)據(jù)到底是否就緒.盡管這會(huì)浪費(fèi)CPU時(shí)間,但是仍然是比較常見的模型,一般是在系統(tǒng)函數(shù)中用到.

I/O多路復(fù)用

在I/O多路復(fù)用中,我們會(huì)調(diào)用select()或者poll(),并且阻塞在這兩個(gè)系統(tǒng)調(diào)用上.而不是阻塞在recvfrom這個(gè)實(shí)際的IO操作的系統(tǒng)調(diào)用上.下面是I/O多路復(fù)用模型的過程圖:

從上圖中,我們可以看到,我們會(huì)阻塞在select()這個(gè)系統(tǒng)調(diào)用上,并等待數(shù)據(jù)到達(dá).當(dāng)select()告訴我們數(shù)據(jù)到達(dá)時(shí),再通過recvfrom系統(tǒng)調(diào)用將數(shù)據(jù)拷貝到應(yīng)用程序的緩沖區(qū).

看到這里,如果各位不了解select(),可能就會(huì)有一個(gè)疑問.你這不是脫了褲子放屁嗎?這不是還是跟阻塞IO模型一樣,還是阻塞嗎?只不過現(xiàn)在不是阻塞在recvfrom上,而是阻塞在select上而已.而且,現(xiàn)在還多了一次系統(tǒng)調(diào)用,那效率不是更低嗎?

多了一次系統(tǒng)調(diào)用,確實(shí)是I/O多路復(fù)用模型的缺點(diǎn).但是存在即合理,它也有優(yōu)點(diǎn).

它的優(yōu)點(diǎn)在于,select可以同時(shí)監(jiān)聽多個(gè)文件描述符,以及感興趣的事件.所以,我們可以在一個(gè)線程中完成之前需要好多個(gè)線程才能完成的事情.

比如,我們想要同時(shí)從一個(gè)接受來自Socket的數(shù)據(jù),以及從文件中讀數(shù)據(jù).在阻塞IO模型中,我們會(huì)這么做:

  1.創(chuàng)建一個(gè)線程A,在其中創(chuàng)建一個(gè)Socket Server,并通過它的accept()方法,等待客戶端的連接并處理數(shù)據(jù)
  2.創(chuàng)建一個(gè)線程B,在其中打開文件并且讀數(shù)據(jù).

這就需要兩個(gè)線程,對(duì)吧?

而且我們又知道,線程之間的切換是有開銷的,也是需要涉及到用戶態(tài)到內(nèi)核態(tài)的轉(zhuǎn)換.

而我們?cè)贗/O多路復(fù)用模型中,可以這樣做:

  1.通過注冊(cè)函數(shù)告訴系統(tǒng),應(yīng)用程序?qū)τ赟ocket的讀事件以及文件的讀事件感興趣
  2.通過輪詢調(diào)用select()方法,查看哪些我們感興趣的事件已經(jīng)發(fā)生了
  3.在同一個(gè)線程中,依次進(jìn)行對(duì)應(yīng)的操作

我們可以看到,在這里我們只需要用一個(gè)線程就可以做到在阻塞IO中我們需要兩個(gè)線程才能做到的事情.這就是I/O復(fù)用中的復(fù)用的含義.

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

信號(hào)驅(qū)動(dòng)IO使用信號(hào)量機(jī)制,它告訴內(nèi)核,當(dāng)文件描述符準(zhǔn)備就緒時(shí),通過SIGIO信號(hào)通知我們.過程如下:

  • 我們首先通過sigaction系統(tǒng)調(diào)用安裝一個(gè)事件處理器.這個(gè)操作會(huì)立即返回.所以我們的應(yīng)用程序會(huì)繼續(xù)運(yùn)行,而不會(huì)阻塞.
  • 當(dāng)數(shù)據(jù)準(zhǔn)備就緒時(shí),內(nèi)核會(huì)給我們的應(yīng)用程序發(fā)出一個(gè)SIGIO信號(hào),我們可以繼續(xù)進(jìn)行下面的處理:
    • 在信號(hào)處理器中,通過recvfrom系統(tǒng)調(diào)用將數(shù)據(jù)從內(nèi)核緩沖區(qū)讀取到應(yīng)用程序緩沖區(qū)中
    • 告訴應(yīng)用程序從緩沖區(qū)讀取數(shù)據(jù)并且處理

這種模型的優(yōu)點(diǎn)是,在等待數(shù)據(jù)就緒時(shí),應(yīng)用程序并不會(huì)被阻塞.應(yīng)用程序可以繼續(xù)運(yùn)行,只需要在數(shù)據(jù)就緒時(shí),讓時(shí)間處理器通知它即可.

異步IO

異步IO模型跟事件驅(qū)動(dòng)IO模型類似,也是告訴內(nèi)核,在一定情況下通知我們.但是它跟事件驅(qū)動(dòng)IO模型不同的是,在事件驅(qū)動(dòng)IO模型中,內(nèi)核會(huì)在數(shù)據(jù)就緒,即數(shù)據(jù)被拷貝到內(nèi)核緩沖區(qū)時(shí),通知我們.而在異步IO中,內(nèi)核會(huì)在整個(gè)操作都被完成,即數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到應(yīng)用程序緩沖區(qū)時(shí),通知我們.如下圖所示:

  • 我們調(diào)用aio_read這個(gè)系統(tǒng)調(diào)用,并且給內(nèi)核傳遞下面的數(shù)據(jù):
    • 文件描述符,緩沖區(qū)指針,緩沖區(qū)大小
    • 文件偏移量
    • 當(dāng)整個(gè)操作完成時(shí),如何通知我們
      這個(gè)系統(tǒng)調(diào)用會(huì)立即返回,在整個(gè)操作完成之前,不會(huì)被阻塞

五種IO模型的比較

同步IO和異步IO

POSIX中,定義了下面的兩個(gè)術(shù)語:

  • 同步IO:在整個(gè)IO操作完成之前,會(huì)導(dǎo)致應(yīng)用程序阻塞的IO操作叫做同步IO
  • 異步IO:在整個(gè)IO操作完成之前,不會(huì)導(dǎo)致應(yīng)用程序阻塞的IO操作叫做異步IO

我們可以看到,上面我們定義的五個(gè)IO模型,前四個(gè)(阻塞IO模型,非阻塞IO模型,IO復(fù)用模型,信號(hào)驅(qū)動(dòng)模型)都屬于同步IO,而只有異步IO模型屬于異步IO.

注意

原文中還有更多關(guān)于select等IO復(fù)用的詳細(xì)介紹,所以強(qiáng)烈建議各位讀一下原文.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容