linux系統IO分爲內核準備數據和將數據從內核拷貝到用戶空間兩個階段。linux
這張圖大體描述了數據從外部磁盤向運行中程序的內存中移動的過程。編程
如今操做系統都是採用虛擬存儲器,那麼對32位操做系統而言,它的尋址空間(虛擬儲存空間)爲4G(2的32次方)。操做系統的核心是內核,獨立於普通的應用程序,能夠訪問受保護的內存空間,也有訪問底層硬件設備的全部權限。爲了保證用戶進程不能直接操做內核,保證內核的安全,操做系統將虛擬空間劃分爲兩個部分,一個部分爲內核空間,一部分爲用戶空間。windows
如何分配這兩個空間的大小也是有講究的,如windows 32位操做系統,默認的用戶空間:內核空間的比例是1:1;而在32位Linux系統中的默認比例是3:1(3G用戶空間,1G內核空間)。緩存
爲了控制進程的執行,內核必需要有能力掛起正在CPU上運行的進程,並恢復之前掛起的某個進程的執行。這種行爲成爲進程的切換。任何進程都是在操做系統內核的支持下運行的,是與內核緊密相關的。安全
進程切換的過程,會通過下面這些變化:網絡
一、保存處理機上下文,包括程序計數器和其餘寄存器。數據結構
二、更新PCB信息。多線程
三、將進程的PCB移入相應的隊列,如就緒、在某事件阻塞等隊列。併發
四、選擇另一個進程執行,並更新PCB異步
五、更新內存管理的數據結構。
六、恢復處理機上下文
緩存IO又稱稱爲標準IO,大多數文件系統的默認IO操做都是緩存IO。在Linux的緩存IO機制中,操做系統會將IO的數據緩存在文件系統的頁緩存(page cache)。也就是說,數據會先被拷貝到操做系統內核的緩衝區中,而後纔會從操做系統內核的緩存區拷貝到應用程序的地址空間中。
這種作法的缺點就是,須要在應用程序地址空間和內核進行屢次拷貝,這些拷貝動做所帶來的CPU以及內存開銷是很是大的。
同步與異步:描述的是用戶線程與內核的交互方式,同步指用戶線程發起IO請求後須要等待或者輪詢內核IO操做完成後才能繼續執行;而異步是指用戶線程發起IO請求後仍然繼續執行,當內核IO操做完成後會通知用戶線程,或者調用用戶線程註冊的回調函數。
阻塞與非阻塞:描述是用戶線程調用內核IO操做的方式,阻塞是指IO操做須要完全完成後才返回到用戶空間;而非阻塞是指IO操做被調用後當即返回給用戶一個狀態值,無需等到IO操做完全完成。
網絡IO的本質就是socket的讀取,socket在linux系統被抽象爲流,IO能夠理解爲對流的操做。文章開始的時候也提到了,對於一次IO訪問(以read爲例),數據會先被拷貝到操做系統內核的緩衝區,而後纔會從操做系統內核的緩衝區拷貝到應用程序的地址空間中。因此說,當一個read操做發生時,它會經歷兩個階段:
第一個階段:等待數據準備。
第二個階段:將數據從內核拷貝到進程中
對於socket流而言:
第一步:一般涉及等待網絡上的數據分組到達,而後複製到內核的某個緩衝區。
第二步:把數據從內核緩衝區複製到應用進程緩衝區。
固然,若是內核空間的緩衝區中已經有數據了,那麼就能夠省略第一步。至於爲何不能直接讓磁盤控制器把數據送到應用程序的地址空間中呢?最簡單的一個緣由就是應用程序不能直接操做底層硬件。
網絡應用須要處理的無非就是兩大類問題,網絡IO,數據計算。相對於後者,網絡IO的延遲,給應用帶來的性能瓶頸大於後者。網絡IO的模型大體分爲以下五種:
一、阻塞IO
二、非阻塞IO
三、多路複用IO
四、信號驅動IO
五、異步IO
前四種都是同步,只有最後一種是異步IO。下面的模型介紹先以生活中的例子來講明概念:週末和女朋友去商場逛街,到了晚上飯點,準備吃完飯再去逛街,可是週末人多,新白鹿飯店須要排隊,因而有以下幾種方案可供選擇:
場景描述:
在飯店領完號後,前面還有n桌,不知道何時到咱們,可是又不能離開,由於過號以後必須從新取號。只好在飯店裏等,一直等到叫號到咱們才吃完晚飯,而後去逛街。中間等待的時間什麼事情都不能作。
網絡模型:
在這個模型中,應用程序爲了執行這個read操做,會調用相應的一個system call,將系統控制權交給內核,而後就進行等待(這個等待的過程就是被阻塞了),內核開始執行這個system call,執行完畢後會嚮應用程序返回響應,應用程序獲得響應後,就再也不阻塞,並進行後面的工做。
優勢:
可以及時返回數據,無延遲。
缺點:
對用戶來講處於等待就要付出性能代價。
場景描述:
等待過程是在太無聊,因而咱們就去逛商場,每隔一段時間就回來詢問服務員,叫號是否到咱們了,整個過程來來回回好屢次。這就是非阻塞,可是須要不斷的詢問。
網絡模型:
當用戶進程發出read操做時,調用相應的system call,這個system call會當即從內核中返回。可是在返回的這個時間點,內核中的數據可能尚未準備好,也就是說內核只是很快就返回了system call,只有這樣纔不會阻塞用戶進程,對於應用程序,雖然這個IO操做很快就返回了,可是它並不知道這個IO操做是否真的成功了,爲了知道IO操做是否成功,應用程序須要主動的循環去問內核。
優勢:
可以在等待的時間裏去作其餘的事情。
缺點:
任務完成的響應延遲增大了,由於每過一段時間去輪詢一次read操做,而任務可能在兩次輪詢之間的任意時間完成,這對致使總體數據吞吐量的下降。
三、IO多路複用
場景描述:
與第二個常常相似,飯店安裝了電子屏幕,顯示叫號的狀態,因此在逛街的時候,就不用去詢問服務員,而是看下大屏幕就能夠了。(不只僅是咱們不用詢問服務員,其餘全部的人均可以不用詢問服務員)
網絡模型:
和第二種同樣,調用system call以後,並不等待內核的返回結果而是當即返回。雖然返回結果的調用函數是一個異步的方式,但應用程序會被像select、poll和epoll等具備多個文件描述符的函數阻塞住,一直等到這個system call有結果返回了,再通知應用程序。這種狀況,從IO操做的實際效果來看,異步阻塞IO和第一種同步阻塞IO是同樣的,應用程序都是一直等到IO操做成功以後(數據已經被寫入或者讀取),纔開始進行下面的工做。不一樣點在於異步阻塞IO用一個select函數能夠爲多個文件描述符提供通知,提供了併發性。舉個例子:例若有一萬個併發的read請求,可是網絡上仍然沒有數據,此時這一萬個read會同時各自阻塞,如今用select、poll、epoll這樣的函數來專門負責阻塞同時監聽這一萬個請求的狀態,一旦有數據到達了就負責通知,這樣就將一萬個等待和阻塞轉化爲一個專門的函數來負責與管理。
多路複用技術應用於JAVA NIO的核心類庫多路複用器Selector中,目前支持I/O多路複用的系統調用有select、pselect、poll、epoll,在linux編程中有一段時間一直在使用select作輪詢和網絡事件通知的,可是select支持一個進程打開的socket描述符(FD)收到了限制,通常爲1024,因爲這一限制,如今使用了epoll代替了select,而epoll支持一個進程打開的FD不受限制。
異步IO與同步IO的區別在於:同步IO是須要應用程序主動地循環去詢問是否有數據,而異步IO是經過像select等IO多路複用函數來同時檢測多個事件句柄來告知應用程序是否有數據。
瞭解了前面三種IO模式,在用戶進程進行系統調用的時候,他們在等待數據到來的時候,處理的方式是不同的,直接等待、輪詢、select或poll輪詢,兩個階段過程:
第一個階段有的阻塞,有的不阻塞,有的能夠阻塞又能夠不阻塞。
第二個階段都是阻塞的。
從整個IO過程來看,他們都是順序執行的,所以能夠歸爲同步模型,都是進程自動等待且向內核檢查狀態。
高併發的程序通常使用同步非阻塞模式,而不是多線程+同步阻塞模式。要理解這點,先弄明白併發和並行的區別:好比去某部門辦事須要依次去幾個窗口,辦事大廳的人數就是併發數,而窗口的個數就是並行度。就是說併發是同時進行的任務數(如同時服務的http請求),而並行數就是能夠同時工做的物理資源數量(如cpu核數)。經過合理調度任務的不一樣階段,併發數能夠遠遠大於並行度。這就是區區幾個CPU能夠支撐上萬個用戶併發請求的緣由。在這種高併發的狀況下,爲每一個用戶請求建立一個進程或者線程的開銷很是大。而同步非阻塞方式能夠把多個IO請求丟到後臺去,這樣一個CPU就能夠服務大量的併發IO請求。
IO多路複用到底是同步阻塞仍是異步阻塞模型,這裏來展開說說:
同步是須要主動等待消息通知,而異步則是被動接受消息通知,經過回調、通知、狀態等方式來被動獲取消息。IO多路複用在阻塞到select階段時,用戶進程是主動等待並調用select函數來獲取就緒狀態消息,而且其進程狀態爲阻塞。因此IO多路複用是同步阻塞模式。
四、信號驅動式IO
應用程序提交read請求,調用system call,而後內核開始處理相應的IO操做,而同時,應用程序並不等內核返回響應,就會開始執行其餘的處理操做(應用程序沒有被IO阻塞),當內核執行完畢,返回read響應,就會產生一個信號或執行一個基於線程的回調函數來完成此次IO處理過程。在這裏IO的讀寫操做是在IO事件發生以後由應用程序來完成。異步IO讀寫操做老是當即返回,而不論IO是否阻塞,由於真正的讀寫操做已經有內核掌管。也就是說同步IO模型要求用戶代碼自行執行IO操做(將數據從內核緩衝區移動用戶緩衝區或者相反),而異步操做機制則是由內核來執行IO操做(將數據從內核緩衝區移動用戶緩衝區或者相反)。能夠這樣認爲,同步IO嚮應用程序通知的是IO就緒事件,而異步IO嚮應用程序通知的是IO完成事件。
五、異步IO
異步IO與上面的異步概念是同樣的, 當一個異步過程調用發出後,調用者不能馬上獲得結果,實際處理這個調用的函數在完成後,經過狀態、通知和回調來通知調用者的輸入輸出操做。異步IO的工做機制是:告知內核啓動某個操做,並讓內核在整個操做完成後通知咱們,這種模型與信號驅動的IO區別在於,信號驅動IO是由內核通知咱們什麼時候能夠啓動一個IO操做,這個IO操做由用戶自定義的信號函數來實現,而異步IO模型是由內核告知咱們IO操做什麼時候完成。