緩衝區如何工做是IO的基礎,所謂「輸入/輸出」講的無非就是把數據移進或移出緩衝區。進程的IO操做簡單的說就是向操做系統發出請求,要麼緩衝區裏的數據排幹(寫),要麼用數據把緩衝區填滿(讀)。程序員
下圖簡單描述了數據從外部磁盤向運行中的進程的內存區域移動的過程。進程使用 read( )系統調用,要求其緩衝區被填滿。內核隨即向磁盤控制硬件發出命令,要求其從磁盤讀取數據。磁盤控制器把數據直接寫入內核內存緩衝區,這一步經過 DMA 完成,無需主 CPU 協助。一旦磁盤控制器把緩衝區裝滿,內核即把數據從內核空間的臨時緩衝區拷貝到進程執行 read( )調用時指定的緩衝區。數據庫
注意圖中用戶空間和內核空間的概念。用戶空間是常規進程所在區域。 JVM 就是常規進程,駐守於用戶空間。用戶空間是非特權區域:好比,在該區域執行的代碼就不能直接訪問硬件設備。內核空間是操做系統所在區域。內核代碼有特別的權力:它能與設備控制器通信,控制着用戶區域進程的運行狀態,等等。最重要的是,全部 I/O 都直接(如這裏所述)或間接經過內核空間。數組
當進程請求 I/O 操做的時候,它執行一個系統調用(有時稱爲陷阱)將控制權移交給內核。C/C++程序員所熟知的底層函數 open( )、 read( )、 write( )和 close( )要作的無非就是創建和執行適當的系統調用。當內核以這種方式被調用,它隨即採起任何須要步驟,找到進程所需數據,並把數據傳送到用戶空間內的指定緩衝區。內核試圖對數據進行高速緩存或預讀取,所以進程所需數據可能已經在內核空間裏了。若是是這樣,該數據只需簡單地拷貝出來便可。若是數據不在內核空間,則進程被掛起,內核着手把數據讀進內存。緩存
您可能會以爲,把數據從內核空間拷貝到用戶空間彷佛多餘。爲何不直接讓磁盤控制器把數據送到用戶空間的緩衝區呢?這樣作有幾個問題。首先,硬件一般不能直接訪問用戶空間 (硬件設備一般不能直接使用虛擬內存地址)。其次,像磁盤這樣基於塊存儲的硬件設備操做的是固定大小的數據塊,而用戶進程請求的多是任意大小的或非對齊的數據塊。在數據往來於用戶空間與存儲設備的過程當中,內核負責數據的分解、再組合工做,所以充當着中間人的角色。服務器
發散/匯聚網絡
許多操做系統能把組裝/分解過程進行得更加高效。根據發散/匯聚的概念,進程只需一個系統調用,就能把一連串緩衝區地址傳遞給操做系統。而後,內核就能夠順序填充或排幹多個緩衝區,讀的時候就把數據發散到多個用戶空間緩衝區,寫的時候再從多個緩衝區把數據匯聚起來
函數
這樣用戶進程就沒必要屢次執行系統調用,內核也能夠優化數據的處理過程,由於它已掌握待傳輸數據的所有信息。若是系統配有多個 CPU,甚至能夠同時填充或排幹多個緩衝區。優化
全部現代操做系統都使用虛擬內存。虛擬內存意爲使用虛假(或虛擬)地址取代物理(硬件RAM)內存地址。這樣作好處頗多,總結起來可分爲兩大類:
1. 一個以上的虛擬地址可指向同一個物理內存地址。
2. 虛擬內存空間可大於實際可用的硬件內存。spa
前一節提到,設備控制器不能經過 DMA 直接存儲到用戶空間,但經過利用上面提到的第一項,則能夠達到相同效果。把內核空間地址與用戶空間的虛擬地址映射到同一個物理地址,這樣,DMA 硬件(只能訪問物理內存地址)就能夠填充對內核與用戶空間進程同時可見的緩衝區,如圖所示:操作系統
這樣真是太好了,省去了內核與用戶空間的往來拷貝,但前提條件是,內核與用戶緩衝區必須使用相同的頁對齊,緩衝區的大小還必須是磁盤控制器塊大小(一般爲 512 字節磁盤扇區)的倍數。操做系統把內存地址空間劃分爲頁,即固定大小的字節組。內存頁的大小老是磁盤塊大小的倍數,一般爲 2 次冪(這樣可簡化尋址操做)。典型的內存頁爲 1,02四、 2,048 和 4,096 字節。虛擬和物理內存頁的大小老是相同的。下圖顯示了來自多個虛擬地址的虛擬內存頁是如何映射到物理內存的。
爲了支持虛擬內存的第二個特性(尋址空間大於物理內存),就必須進行虛擬內存分頁(常常稱爲交換,雖然真正的交換是在進程層面完成,而非頁層面)。依照該方案,虛擬內存空間的頁面可以繼續存在於外部磁盤存儲,這樣就爲物理內存中的其餘虛擬頁面騰出了空間。從本質上說,物理內存充當了分頁區的高速緩存;而所謂分頁區,即從物理內存置換出來,轉而存儲於磁盤上的內存頁面。下圖顯示了分屬於四個進程的虛擬頁面,其中每一個進程都有屬於本身的虛擬內存空間。進程A 有五個頁面,其中兩個裝入內存,其他存儲於磁盤。
把內存頁大小設定爲磁盤塊大小的倍數,這樣內核就可直接向磁盤控制硬件發佈命令,把內存頁寫入磁盤,在須要時再從新裝入。結果是,全部磁盤 I/O 都在頁層面完成。對於採用分頁技術的現代操做系統而言,這也是數據在磁盤與物理內存之間往來的惟一方式。現代 CPU 包含一個稱爲內存管理單元( MMU)的子系統,邏輯上位於 CPU 與物理內存之間。該設備包含虛擬地址向物理內存地址轉換時所需映射信息。當 CPU 引用某內存地址時, MMU負責肯定該地址所在頁(每每經過對地址值進行移位或屏蔽位操做實現),並將虛擬頁號轉換爲物理頁號(這一步由硬件完成,速度極快)。若是當前不存在與該虛擬頁造成有效映射的物理內存頁, MMU 會向 CPU 提交一個頁錯誤。頁錯誤隨即產生一個陷阱(相似於系統調用),把控制權移交給內核,附帶致使錯誤的虛擬地址信息,而後內核採起步驟驗證頁的有效性。內核會安排頁面調入操做,把缺失的頁內容讀回物理內存。這每每致使別的頁被移出物理內存,好給新來的頁讓地方。在這種狀況下,若是待移出的頁已經被碰過了(自建立或上次頁面調入以來,內容已發生改變),還必須首先執行頁面調出,把頁內容拷貝到磁盤上的分頁區。若是所要求的地址不是有效的虛擬內存地址(不屬於正在執行的進程的任何一個內存段),則該頁不能經過驗證,段錯誤隨即產生。因而,控制權轉交給內核的另外一部分,一般致使的結果就是進程被強令關閉。一旦出錯的頁經過了驗證, MMU 隨即更新,創建新的虛擬到物理的映射(若有必要,中斷被移出頁的映射),用戶進程得以繼續。形成頁錯誤的用戶進程對此不會有絲毫察覺,一切都在不知不覺中進行。
文件 I/O 屬文件系統範疇,文件系統與磁盤迥然不一樣。磁盤把數據存在扇區上,一般一個扇區512 字節。磁盤屬硬件設備,對何謂文件一無所知,它只是提供了一系列數據存取窗口。在這點上,磁盤扇區與內存頁很有類似之處:都是統一大小,均可做爲大的數組被訪問。
文件系統是更高層次的抽象,是安排、解釋磁盤(或其餘隨機存取塊設備)數據的一種獨特方式。您所寫代碼幾乎無一例外地要與文件系統打交道,而不是直接與磁盤打交道。是文件系統定義了文件名、路徑、文件、文件屬性等抽象概念。
前一節講到,全部 I/O 都是經過請求頁面調度完成的。您應該還記得,頁面調度是很是底層的操做,僅發生於磁盤扇區與內存頁之間的直接傳輸。而文件 I/O 則能夠任意大小、任意定位。那麼,底層的頁面調度是如何轉換爲文件 I/O 的?
文件系統把一連串大小一致的數據塊組織到一塊兒。有些塊存儲元信息,如空閒塊、目錄、索引等的映射,有些包含文件數據。單個文件的元信息描述了哪些塊包含文件數據、數據在哪裏結束、最後一次更新是何時,等等。
當用戶進程請求讀取文件數據時,文件系統須要肯定數據具體在磁盤什麼位置,而後着手把相關磁盤扇區讀進內存。老式的操做系統每每直接向磁盤驅動器發佈命令,要求其讀取所需磁盤扇區。而採用分頁技術的現代操做系統則利用請求頁面調度取得所需數據。
操做系統還有個頁的概念,其大小或者與基本內存頁一致,或者是其倍數。典型的操做系統頁從 2,048 到 8,192 字節不等,且始終是基本內存頁大小的倍數。
採用分頁技術的操做系統執行 I/O 的全過程可總結爲如下幾步:
須要注意的是,這些文件系統數據也會同其餘內存頁同樣獲得高速緩存。對於隨後發生的 I/O請求,文件數據的部分或所有可能仍舊位於物理內存當中,無需再從磁盤讀取便可重複使用。
大多數操做系統假設進程會繼續讀取文件剩餘部分,於是會預讀額外的文件系統頁。若是內存爭用狀況不嚴重,這些文件系統頁可能在至關長的時間內繼續有效。這樣的話,當稍後該文件又被相同或不一樣的進程再次打開,可能根本無需訪問磁盤。這種狀況您可能也碰到過:當重複執行相似的操做,如在幾個文件中進行字符串檢索,第二遍運行得彷佛快多了。
相似的步驟在寫文件數據時也會採用。這時,文件內容的改變(經過 write( ))將致使文件系統頁變髒,隨後經過頁面調出,與磁盤上的文件內容保持同步。文件的建立方式是,先把文件映射到空閒文件系統頁,在隨後的寫操做中,再將文件系統頁刷新到磁盤。
傳統的文件 I/O 是經過用戶進程發佈 read( )和 write( )系統調用來傳輸數據的。爲了在內核空間的文件系統頁與用戶空間的內存區之間移動數據,一次以上的拷貝操做幾乎老是免不了的。這是由於,在文件系統頁與用戶緩衝區之間每每沒有一一對應關係。可是,還有一種大多數操做系統都支持的特殊類型的 I/O 操做,容許用戶進程最大限度地利用面向頁的系統 I/O 特性,並徹底摒棄緩衝區拷貝。這就是內存映射 I/O,如圖
內存映射 I/O 使用文件系統創建從用戶空間直到可用文件系統頁的虛擬內存映射。這樣作有幾個好處:
虛擬內存和磁盤 I/O 是緊密關聯的,從不少方面看來,它們只是同一件事物的兩面。在處理大量數據時,尤爲要記得這一點。若是數據緩衝區是按頁對齊的,且大小是內建頁大小的倍數,那麼,對大多數操做系統而言,其處理效率會大幅提高。
文件鎖定機制容許一個進程阻止其餘進程存取某文件,或限制其存取方式。一般的用途是控制共享信息的更新方式,或用於事務隔離。在控制多個實體並行訪問共同資源方面,文件鎖定是必不可少的。數據庫等複雜應用嚴重信賴於文件鎖定。
「文件鎖定」從字面上看有鎖定整個文件的意思(一般的確是那樣),但鎖定每每能夠發生在更爲細微的層面,鎖定區域每每能夠細緻到單個字節。鎖定與特定文件相關,開始於文件的某個特定字節地址,包含特定數量的連續字節。這對於協調多個進程互不影響地訪問文件不一樣區域,是相當重要的。
文件鎖定有兩種方式:共享的和獨佔的。多個共享鎖可同時對同一文件區域發生做用;獨佔鎖則不一樣,它要求相關區域不能有其餘鎖定在起做用。
共享鎖和獨佔鎖的經典應用,是控制最初用於讀取的共享文件的更新。某個進程要讀取文件,會先取得該文件或該文件部分區域的共享鎖。第二個但願讀取相同文件區域的進程也會請求共享鎖。兩個進程能夠並行讀取,互不影響。可是,假若有第三個進程要更新該文件,它會請求獨佔鎖。該進程會處於阻滯狀態,直到既有鎖定(共享的、獨佔的)所有解除。一旦給予獨佔鎖,其餘共享鎖的讀取進程會處於阻滯狀態,直到獨佔鎖解除。這樣,更新進程能夠更改文件,而其餘讀取進程不會由於文件的更改獲得先後不一致的結果。下圖描述了這一過程。
文件鎖有建議使用和強制使用之分。建議型文件鎖會向提出請求的進程提供當前鎖定信息,但操做系統並不要求必定這樣作,而是由相關進程進行協調並關注鎖定信息。多數 Unix 和類 Unix 操做系統使用建議型鎖,有些也使用強制型鎖或兼而有之。
強制型鎖由操做系統或文件系統強行實施,無論進程對鎖的存在知道與否,都會阻止其對文件鎖定區域的訪問。微軟的操做系統每每使用的是強制型鎖。假定全部文件鎖均爲建議型,並在訪問共同資源的各個應用程序間使用一致的文件鎖定,是明智之舉,也是惟一可行的跨平臺策略。依賴於強制文件鎖定的應用程序,從根子上講就是不可移植的。
並不是全部 I/O 都像前幾節講的是面向塊的,也有流 I/O,其原理模仿了通道。 I/O 字節流必須順序存取,常見的例子有 TTY(控制檯)設備、打印機端口和網絡鏈接。
流的傳輸通常(也沒必要然如此)比塊設備慢,常常用於間歇性輸入。多數操做系統容許把流置於非塊模式,這樣,進程能夠查看流上是否有輸入,即使當時沒有也不影響它幹別的。這樣一種能力使得進程能夠在有輸入的時候進行處理, 輸入流閒置的時候執行其餘功能。
比非塊模式再進一步,就是就緒性選擇。就緒性選擇與非塊模式相似(經常就是創建在非塊模式之上),可是把查看流是否就緒的任務交給了操做系統。操做系統受命查看一系列流,並提醒進程哪些流已經就緒。這樣,僅僅憑藉操做系統返回的就緒信息,進程就可使用相同代碼和單一線程,實現多活動流的多路傳輸。這一技術普遍用於網絡服務器領域,用來處理數量龐大的網絡鏈接。就緒性選擇在大容量縮放方面是必不可少的。