IO概念和五種IO模型

一、什麼是IO?

    我們都知道unix世界裏、一切皆文件、而文件是什麼呢?文件就是一串二進制流而已、不管socket、還是FIFO、管道、終端、對我們來說、一切都是文件、一切都是流、在信息交換的過程中、我們都是對這些流進行數據的收發操作、簡稱爲I/O操作(input and output)、往流中讀出數據、系統調用read、寫入數據、系統調用write、不過話說回來了、計算機裏有這麼多的流、我怎麼知道要操作哪個流呢?做到這個的就是文件描述符、即通常所說的fd、一個fd就是一個整數、所以對這個整數的操作、就是對這個文件(流)的操作、我們創建一個socket、通過系統調用會返回一個文件描述符、那麼剩下對socket的操作就會轉化爲對這個描述符的操作、不能不說這又是一種分層和抽象的思想、

136604819_1_20180624055738145

二、IO交互

    通常用戶進程中的一個完整IO分爲兩階段:

    用戶空間 <-----> 內核空間、

    內核空間 <-----> 設備空間、

136604819_2_20180624055738192

    內核空間中存放的是內核代碼和數據、而進程的用戶空間中存放的是用戶程序的代碼和數據、不管是內核空間還是用戶空間、它們都處於虛擬空間中、Linux使用兩級保護機制:0級供內核使用、3級供用戶程序使用、

操作系統和驅動程序運行在內核空間、應用程序運行在用戶空間、兩者不能簡單地使用指針傳遞數據、因爲Linux使用的虛擬內存機制、其必須通過系統調用請求kernel來協助完成IO動作、內核會爲每個IO設備維護一個緩衝區、用戶空間的數據可能被換出、當內核空間使用用戶空間指針時、對應的數據可能不在內存中

    對於一個輸入操作來說、進程IO系統調用後、內核會先看緩衝區中有沒有相應的緩存數據、沒有的話再到設備中讀取、因爲設備IO一般速度較慢、需要等待、內核緩衝區有數據則直接複製到進程空間、

    所以、對於一個網絡輸入操作通常包括兩個不同階段:

   (1)等待網絡數據到達網卡 –> 讀取到內核緩衝區

   (2)從內核緩衝區複製數據 –> 用戶空間

    IO有內存IO

    網絡IO

    磁盤IO三種、通常我們說的IO指的是後兩者

三、POSIX

    對IO底層交互感興趣的小夥伴可以好好了解一下POSIX(Portable Operating System Interface for Computing System)、我對深沉次原理也不怎麼熟、之所以寫此篇博文也是爲了後面的Java IO學習、深入淺出點到即可、此章節給有興趣的朋友一個引子、

四、IO模型

   《UNIX網絡編程》說得很清楚、5種IO模型分別是阻塞IO模型、非阻塞IO模型、IO複用模型、信號驅動的IO模型、異步IO模型、前4種爲同步IO操作、只有異步IO模型是異步IO操作、請仔細閱讀IO交互便於理解IO模型

136604819_3_20180624055738270

(一)阻塞IO模型

    當用戶進程調用了recvfrom這個系統調用、內核就開始了IO的第一個階段:準備數據、對於網絡IO來說、很多時候數據在一開始還沒有到達(比如、還沒有收到一個完整的UDP包)、這個時候內核就要等待足夠的數據到來、而在用戶進程這邊、整個進程會被阻塞、當內核一直等到數據準備好了、它就會將數據從內核中拷貝到用戶內存、然後返回結果、用戶進程才解除阻塞的狀態、重新運行起來、幾乎所有的程序員第一次接觸到的網絡編程都是從listen()、send()、recv()等接口開始的、這些接口都是阻塞型的、

blocking IO的特點就是在IO執行的兩個階段(等待數據和拷貝數據兩個階段)都被阻塞了、

    典型應用:阻塞Socket、Java BIO

    進程阻塞掛起不消耗CPU資源、及時響應每個操作

    實現難度低、開發應用較容易

    適用併發量小的網絡應用開發

    不適用併發量大的應用、因爲一個請求IO會阻塞進程、所以、得爲每請求分配一個處理進程(線程)以及時響應、系統開銷大

136604819_4_20180624055738349

(二)非阻塞IO模型

    當用戶進程發出read操作時、如果內核中的數據還沒有準備好、那麼它並不會block用戶進程、而是立刻返回一個error、從用戶進程角度講、它發起一個read操作後、並不需要等待、而是馬上就得到了一個結果、用戶進程判斷結果是一個error時、它就知道數據還沒有準備好、於是它可以再次發送read操作、一旦內核中的數據準備好了、並且又再次收到了用戶進程的系統調用、那麼它馬上就將數據拷貝到了用戶內存、然後返回、非阻塞的接口相比於阻塞型接口的顯著差異在於、在被調用之後立即返回、

在非阻塞式IO中、用戶進程其實是需要不斷的主動詢問kernel數據準備好了沒有

    典型應用:Socket 設置 NONBLOCK

    進程輪詢(重複)調用、消耗CPU的資源

    實現難度低、開發應用相對阻塞IO模式較難

    適用併發量較小、且不需要及時響應的網絡應用開發

136604819_5_20180624055738442

(三)IO複用模型

    多個的進程的IO可以註冊到一個複用器(select)上、當用戶進程調用該select、select會監聽所有註冊進來的IO、如果select所有監聽的IO在內核緩衝區都沒有可讀數據、select調用進程會被阻塞、而當任一IO在內核緩衝區中有可數據時、select調用就會返回、而後select調用進程可以自己或通知另外的進程(註冊進程)來再次發起讀取IO、讀取內核中準備好的數據、多個進程註冊IO後、只有一個select調用進程被阻塞

    IO複用相對阻塞和非阻塞更難簡單說明、所以額外解釋一段、其實IO複用模型和阻塞IO模型並沒有太大的不同、事實上、還更差一些、因爲這裏需要使用兩個系統調用(select和 recvfrom)、而阻塞IO模型只有一次系統調用(recvfrom)、但是、用select的優勢在於它可以同時處理多個連接、所以如果處理的連接數不是很高的話、使用select/epoll的web server不一定比使用多線程加阻塞IO的web server性能更好、可能延遲還更大、select/epoll的優勢並不是對於單個連接能處理得更快、而是在於能處理更多的連接

在IO複用模型中、對於每一個socket、一般都設置成爲非阻塞、但是、如上圖所示、整個用戶的進程其實是一直被阻塞的、只不過進程是被select這個函數阻塞、而不是被socket IO給阻塞

    典型應用:Java NIO、Nginx(epoll、poll、select)

    專一進程解決多個進程IO的阻塞問題、性能好、Reactor模式

    實現、開發應用難度較大

    適用高併發服務應用開發、一個進程/線程響應多個請求

136604819_6_20180624055738520

(四)信號驅動式IO模型

    信號驅動式IO就是指進程預先告知內核、向內核註冊一個信號處理函數、然後用戶進程返回不阻塞、當內核數據就緒時會發送一個信號給進程、用戶進程便在信號處理函數中調用IO讀取數據、從圖中明白實際IO內核拷貝到用戶進程的過程還是阻塞的、信號驅動式IO並沒有實現真正的異步、因爲通知到進程之後、依然是由進程來完成IO操作、

這和後面的異步IO模型很容易混淆、需要理解IO交互並結合五種IO模型的比較閱讀

在信號驅動式IO模型中、依然不符合POSIX描述的異步IO、只能算是半異步、並且實際中並不常用、

    典型應用:(不知道)

    回調機制、實現、開發應用難度大

136604819_7_20180624055738645(五)異步IO模型

    用戶進程發起aio_read(POSIX異步IO函數aio_或者lio_開頭)操作之後、給內核傳遞描述符、緩衝區指針、緩衝區大小和read相同的三個參數以及文件偏移(與lseek類似)、告訴內核當整個操作完成時、如何通知我們、立刻就可以開始去做其它的事、而另一方面、從內核的角度、當它受到一個aio_read之後、首先它會立刻返回、所以不會對用戶進程產生任何阻塞、然後、內核會等待數據準備完成、然後將數據拷貝到用戶內存、當這一切都完成之後、內核會給用戶進程發送一個信號、告訴它aio_read操作完成了

    異步IO的工作機制是:告知內核啓動某個操作、並讓內核在整個操作完成後通知我們、這種模型與信號驅動的IO區別在於、信號驅動IO是由內核通知我們何時可以啓動一個IO操作、這個IO操作由用戶自定義的信號函數來實現、而異步IO模型是由內核告知我們IO操作何時完成、

這和前面的信號驅動式IO模型很容易混淆、需要理解IO交互並結合五種IO模型的比較閱讀

在異步IO模型中、真正實現了POSIX描述的異步IO、是五種IO模型中唯一的異步模型

    典型應用:Java 7 AIO、高性能服務器應用

    不阻塞、數據一步到位、Proactor模式

    需要操作系統的底層支持、LINUX 2.5 版本內核首現、2.6 版本產品的內核標準特性

    回調機制、實現、開發應用難度大

    非常適合高性能高併發應用

(六)五種IO模型的比較

阻塞IO和非阻塞IO的區別在哪?

    前面的介紹中其實已經很明確的說明了這兩者的區別、調用阻塞會一直阻塞住對應的進程直到操作完成、而非阻塞IO在內核還沒準備數據的情況下會立刻返回、阻塞和非阻塞關注的是進程在等待調用結果時的狀態、阻塞是指調用結果返回之前、當前進程會被掛起、調用進程只有在得到結果纔會返回、非阻塞調用指不能立刻得到結果、該調用不會阻塞當前進程、

同步IO和異步IO區別在哪?

    兩者的區別就在於同步做IO操作的時候會將進程阻塞、按照這個定義、之前所述的阻塞IO、非阻塞IO、IO複用、信號驅動都屬於同步IO、有人可能會說、非阻塞IO並沒有被阻塞啊、這裏有個非常狡猾的地方、定義中所指的IO操作是指真實的IO操作、就是例子中的recvfrom這個系統調用、非阻塞IO在執行recvfrom這個系統調用的時候、如果內核的數據沒有準備好、這時候不會阻塞進程、但是、當內核中數據準備好的時候、recvfrom會將數據從內核拷貝到用戶內存中、這個時候進程是被阻塞了、信號驅動也是同樣的道理、在這段時間內、進程是被阻塞的、而異步IO則不一樣、當進程發起IO操作之後、就直接返回再也不理睬了、直到內核發送一個信號、告訴進程說IO完成、在這整個過程中、進程完全沒有被阻塞、

    同異步IO的根本區別在於、同步IO主動的調用recvfrom來將數據拷貝到用戶內存、而異步則完全不同、它就像是用戶進程將整個IO操作交給了他人(內核)完成、然後他人做完後發信號通知、在此期間、用戶進程不需要去檢查IO操作的狀態、也不需要主動的去拷貝數據

信號驅動式IO和異步IO的區別?

    這裏之所以單獨拿出來是因爲如果還沒有清除IO概念很容易混淆、所以理解IO模型之前一定要理解IO概念、如果看完前面兩個問題、相信也能理解信號驅動IO與異步IO的區別在於啓用異步IO意味着通知內核啓動某個IO操作、並讓內核在整個操作(包括數據從內核複製到用戶緩衝區)完成時通知我們、也就是說、異步IO是由內核通知我們IO操作何時完成、即實際的IO操作也是異步的、信號驅動IO是由內核通知我們何時可以啓動一個IO操作、這個IO操作由用戶自定義的信號函數來實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章