1、什麼是IO?程序員
咱們都知道unix世界裏、一切皆文件、而文件是什麼呢?文件就是一串二進制流而已、無論socket、仍是FIFO、管道、終端、對咱們來講、一切都是文件、一切都是流、在信息交換的過程當中、咱們都是對這些流進行數據的收發操做、簡稱爲I/O操做(input and output)、往流中讀出數據、系統調用read、寫入數據、系統調用write、不過話說回來了、計算機裏有這麼多的流、我怎麼知道要操做哪一個流呢?作到這個的就是文件描述符、即一般所說的fd、一個fd就是一個整數、因此對這個整數的操做、就是對這個文件(流)的操做、咱們建立一個socket、經過系統調用會返回一個文件描述符、那麼剩下對socket的操做就會轉化爲對這個描述符的操做、不能不說這又是一種分層和抽象的思想、web
2、IO交互編程
一般用戶進程中的一個完整IO分爲兩階段:緩存
用戶空間 <-----> 內核空間、服務器
內核空間 <-----> 設備空間、網絡
內核空間中存放的是內核代碼和數據、而進程的用戶空間中存放的是用戶程序的代碼和數據、不論是內核空間仍是用戶空間、它們都處於虛擬空間中、Linux使用兩級保護機制:0級供內核使用、3級供用戶程序使用、多線程
操做系統和驅動程序運行在內核空間、應用程序運行在用戶空間、二者不能簡單地使用指針傳遞數據、由於Linux使用的虛擬內存機制、其必須經過系統調用請求kernel來協助完成IO動做、內核會爲每一個IO設備維護一個緩衝區、用戶空間的數據可能被換出、當內核空間使用用戶空間指針時、對應的數據可能不在內存中併發
對於一個輸入操做來講、進程IO系統調用後、內核會先看緩衝區中有沒有相應的緩存數據、沒有的話再到設備中讀取、由於設備IO通常速度較慢、須要等待、內核緩衝區有數據則直接複製到進程空間、異步
因此、對於一個網絡輸入操做一般包括兩個不一樣階段:socket
(1)等待網絡數據到達網卡 –> 讀取到內核緩衝區
(2)從內核緩衝區複製數據 –> 用戶空間
IO有內存IO
網絡IO
磁盤IO三種、一般咱們說的IO指的是後二者
3、POSIX
對IO底層交互感興趣的小夥伴能夠好好了解一下POSIX(Portable Operating System Interface for Computing System)、我對深沉次原理也不怎麼熟、之因此寫此篇博文也是爲了後面的Java IO學習、深刻淺出點到便可、此章節給有興趣的朋友一個引子、
4、IO模型
《UNIX網絡編程》說得很清楚、5種IO模型分別是阻塞IO模型、非阻塞IO模型、IO複用模型、信號驅動的IO模型、異步IO模型、前4種爲同步IO操做、只有異步IO模型是異步IO操做、請仔細閱讀IO交互便於理解IO模型
(一)阻塞IO模型
當用戶進程調用了recvfrom這個系統調用、內核就開始了IO的第一個階段:準備數據、對於網絡IO來講、不少時候數據在一開始尚未到達(好比、尚未收到一個完整的UDP包)、這個時候內核就要等待足夠的數據到來、而在用戶進程這邊、整個進程會被阻塞、當內核一直等到數據準備好了、它就會將數據從內核中拷貝到用戶內存、而後返回結果、用戶進程才解除阻塞的狀態、從新運行起來、幾乎全部的程序員第一次接觸到的網絡編程都是從listen()、send()、recv()等接口開始的、這些接口都是阻塞型的、
blocking IO的特色就是在IO執行的兩個階段(等待數據和拷貝數據兩個階段)都被阻塞了、
典型應用:阻塞Socket、Java BIO
進程阻塞掛起不消耗CPU資源、及時響應每一個操做
實現難度低、開發應用較容易
適用併發量小的網絡應用開發
不適用併發量大的應用、由於一個請求IO會阻塞進程、因此、得爲每請求分配一個處理進程(線程)以及時響應、系統開銷大
(二)非阻塞IO模型
當用戶進程發出read操做時、若是內核中的數據尚未準備好、那麼它並不會block用戶進程、而是馬上返回一個error、從用戶進程角度講、它發起一個read操做後、並不須要等待、而是立刻就獲得了一個結果、用戶進程判斷結果是一個error時、它就知道數據尚未準備好、因而它能夠再次發送read操做、一旦內核中的數據準備好了、而且又再次收到了用戶進程的系統調用、那麼它立刻就將數據拷貝到了用戶內存、而後返回、非阻塞的接口相比於阻塞型接口的顯著差別在於、在被調用以後當即返回、
在非阻塞式IO中、用戶進程實際上是須要不斷的主動詢問kernel數據準備好了沒有
典型應用:Socket 設置 NONBLOCK
進程輪詢(重複)調用、消耗CPU的資源
實現難度低、開發應用相對阻塞IO模式較難
適用併發量較小、且不須要及時響應的網絡應用開發
(三)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模式
實現、開發應用難度較大
適用高併發服務應用開發、一個進程/線程響應多個請求
(四)信號驅動式IO模型
信號驅動式IO就是指進程預先告知內核、向內核註冊一個信號處理函數、而後用戶進程返回不阻塞、當內核數據就緒時會發送一個信號給進程、用戶進程便在信號處理函數中調用IO讀取數據、從圖中明白實際IO內核拷貝到用戶進程的過程仍是阻塞的、信號驅動式IO並無實現真正的異步、由於通知到進程以後、依然是由進程來完成IO操做、
這和後面的異步IO模型很容易混淆、須要理解IO交互並結合五種IO模型的比較閱讀
在信號驅動式IO模型中、依然不符合POSIX描述的異步IO、只能算是半異步、而且實際中並不經常使用、
典型應用:(不知道)
回調機制、實現、開發應用難度大
(五)異步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操做由用戶自定義的信號函數來實現