Java NIO 之I/O基本概念(一)


緩衝區操作

    緩衝區,以及緩衝區如何工作,是所有 I/O 的基礎。所謂“輸入/輸出”講的無非就是把數據移
進或移出緩衝區。
進程執行 I/O 操作,歸結起來,也就是向操作系統發出請求,讓它要麼把緩衝區裏的數據排幹
(寫),要麼用數據把緩衝區填滿(讀)。


    進程使用 read( )統調用,要求其緩衝區被填滿。內核隨即向磁盤控制硬件發出命令,要求其從磁盤讀取數據。磁盤控制器把數據直接寫入內核內存緩衝區,這一步通過 DMA 完成,無需主 CPU 協助。一旦磁盤控
制器把緩衝區裝滿,內核即把數據從內核空間的臨時緩衝區拷貝到進程執行 read( )調用時指定的緩衝區。

    注意用戶空間和內核空間的概念。用戶空間是常規進程所在區域。 JVM 就是常規進程,駐守於用戶空間。用戶空間是非特權區域:比如,在該區域執行的代碼就不能直接訪問硬件設備。內核空間是操作系統所在區域。內核代碼有特別的權力:它能與設備控制器通訊,控制着用戶區域進程的運行狀態,等等。最重要的是,所有 I/O 都直接(如這裏所述)或間接通過內核空間。當進程請求 I/O 操作的時候,它執行一個系統調用(有時稱爲陷阱)將控制權移交給內核。
    C/C++程序員所熟知的底層函數 open( )read( )write( )close( )要做的無非就是建立和執行適當的系統調用。當內核以這種方式被調用,它隨即採取任何必要步驟,找到進程所需數據,並把數據傳送到用戶空間內的指定緩衝區。內核試圖對數據進行高速緩存或預讀取,因此進程所需數據可能已經在內核空間裏了。如果是這樣,該數據只需簡單地拷貝出來即可。如果數據不在內核空間,則進程被掛起,內核着手把數據讀進內存。
您可能會覺得,把數據從內核空間拷貝到用戶空間似乎有些多餘。爲什麼不直接讓磁盤控制器把數據送到用戶空間的緩衝區呢?這樣做有幾個問題。首先,硬件通常不能直接訪問用戶空間。其次,像磁盤這樣基於塊存儲的硬件設備操作的是固定大小的數據塊,而用戶進程請求的可能是任意大小的或非對齊的數據塊。在數據往來於用戶空間與存儲設備的過程中,內核負責數據的分解、再組合工作,因此充當着中間人的角色。

    發散/匯聚
    許多操作系統能把組裝/分解過程進行得更加高效。根據發散/匯聚的概念,進程只需一個系統調用,就能把一連串緩衝區地址傳遞給操作系統。然後,內核就可以順序填充或排幹多個緩衝區,讀的時候就把數據發散到多個用戶空間緩衝區,寫的時候再從多個緩衝區把數據匯聚起來這樣用戶進程就不必多次執行系統調用(那樣做可能代價不菲),內核也可以優化數據的處理過程,因爲它已掌握待傳輸數據的全部信息。如果系統配有多個 CPU,甚至可以同時填充或排幹多個緩衝區。
    虛擬內存


        所有現代操作系統都使用虛擬內存。虛擬內存意爲使用虛假(或虛擬)地址取代物理(硬件RAM)內存地址。這樣做好處頗多,總結起來可分爲兩大類:
1. 一個以上的虛擬地址可指向同一個物理內存地址。
2. 虛擬內存空間可大於實際可用的硬件內存。
前一節提到,設備控制器不能通過 DMA 直接存儲到用戶空間,但通過利用上面提到的第一項,則可以達到相同效果。把內核空間地址與用戶空間的虛擬地址映射到同一個物理地址,這樣,DMA 硬件(只能訪問物理內存地址)就可以填充對內核與用戶空間進程同時可見的緩衝區

    這樣真是太好了,省去了內核與用戶空間的往來拷貝,但前提條件是,內核與用戶緩衝區必須使用相同的頁對齊,緩衝區的大小還必須是磁盤控制器塊大小(通常爲 512 字節磁盤扇區)的倍數。操作系統把內存地址空間劃分爲頁,即固定大小的字節組。內存頁的大小總是磁盤塊大小的倍數,通常爲 2 次冪(這樣可簡化尋址操作)。典型的內存頁爲 1,024、 2,048 和 4,096 字節。虛擬和物理內存頁的大小總是相同的。圖 1-4 顯示了來自多個虛擬地址的虛擬內存頁是如何映射到物理內存的。
  內存頁面調度
    爲了支持虛擬內存的第二個特性(尋址空間大於物理內存),就必須進行虛擬內存分頁(經常稱爲交換,雖然真正的交換是在進程層面完成,而非頁層面)。依照該方案,虛擬內存空間的頁面能夠繼續存在於外部磁盤存儲,這樣就爲物理內存中的其他虛擬頁面騰出了空間。從本質上說,物理內存充當了分頁區的高速緩存;而所謂分頁區,即從物理內存置換出來,轉而存儲於磁盤上的內存頁面。

    把內存頁大小設定爲磁盤塊大小的倍數,這樣內核就可直接向磁盤控制硬件發佈命令,把內存頁寫入磁盤,在需要時再重新裝入。結果是,所有磁盤 I/O 都在頁層面完成。對於採用分頁技術的現代操作系統而言,這也是數據在磁盤與物理內存之間往來的唯一方式。

    現代 CPU 包含一個稱爲內存管理單元( MMU)的子系統,邏輯上位於 CPU 與物理內存之間。該設備包含虛擬地址向物理內存地址轉換時所需映射信息。當 CPU 引用某內存地址時, MMU負責確定該地址所在頁(往往通過對地址值進行移位或屏蔽位操作實現),並將虛擬頁號轉換爲物理頁號(這一步由硬件完成,速度極快)。如果當前不存在與該虛擬頁形成有效映射的物理內存頁, MMU 會向 CPU 提交一個頁錯誤。


    頁錯誤隨即產生一個陷阱(類似於系統調用),把控制權移交給內核,附帶導致錯誤的虛擬地址信息,然後內核採取步驟驗證頁的有效性。內核會安排頁面調入操作,把缺失的頁內容讀回物理內存。這往往導致別的頁被移出物理內存,好給新來的頁讓地方。在這種情況下,如果待移出的頁
已經被碰過了(自創建或上次頁面調入以來,內容已發生改變),還必須首先執行頁面調出,把頁內容拷貝到磁盤上的分頁區。
    如果所要求的地址不是有效的虛擬內存地址(不屬於正在執行的進程的任何一個內存段),則該頁不能通過驗證,段錯誤隨即產生。於是,控制權轉交給內核的另一部分,通常導致的結果就是進程被強令關閉。一旦出錯的頁通過了驗證, MMU 隨即更新,建立新的虛擬到物理的映射(如有必要,中斷被移出頁的映射),用戶進程得以繼續。造成頁錯誤的用戶進程對此不會有絲毫察覺,一切都在不知不覺中進行。

  文件I/O
    文件 I/O 屬文件系統範疇,文件系統與磁盤迥然不同。磁盤把數據存在扇區上,通常一個扇區512 字節。磁盤屬硬件設備,對何謂文件一無所知,它只是提供了一系列數據存取窗口。在這點上,磁盤扇區與內存頁頗有相似之處:都是統一大小,都可作爲大的數組被訪問。文件系統是更高層次的抽象,是安排、解釋磁盤(或其他隨機存取塊設備)數據的一種獨特方式。您所寫代碼幾乎無一例外地要與文件系統打交道,而不是直接與磁盤打交道。是文件系統定義了文件名、路徑、文件、文件屬性等抽象概念。
    前一節講到,所有 I/O 都是通過請求頁面調度完成的。您應該還記得,頁面調度是非常底層的操作,僅發生於磁盤扇區與內存頁之間的直接傳輸。而文件 I/O 則可以任意大小、任意定位。那麼,底層的頁面調度是如何轉換爲文件 I/O 的?
    文件系統把一連串大小一致的數據塊組織到一起。有些塊存儲元信息,如空閒塊、目錄、索引等的映射,有些包含文件數據。單個文件的元信息描述了哪些塊包含文件數據、數據在哪裏結束、最後一次更新是什麼時候,等等。當用戶進程請求讀取文件數據時,文件系統需要確定數據具體在磁盤什麼位置,然後着手把相關磁盤扇區讀進內存。老式的操作系統往往直接向磁盤驅動器發佈命令,要求其讀取所需磁盤扇區。而採用分頁技術的現代操作系統則利用請求頁面調度取得所需數據。操作系統還有個頁的概念,其大小或者與基本內存頁一致,或者是其倍數。典型的操作系統頁
從 2,048 到 8,192 字節不等,且始終是基本內存頁大小的倍數。採用分頁技術的操作系統執行 I/O 的全過程可總結爲以下幾步:

確定請求的數據分佈在文件系統的哪些頁(磁盤扇區組)。磁盤上的文件內容和元數
據可能跨越多個文件系統頁,而且這些頁可能也不連續。
在內核空間分配足夠數量的內存頁,以容納得到確定的文件系統頁。
18
在內存頁與磁盤上的文件系統頁之間建立映射。爲每一個內存頁產生頁錯誤。虛擬內存系統俘獲頁錯誤,安排頁面調入,從磁盤上讀取頁內容,使頁有效。一旦頁面調入操作完成,文件系統即對原始數據進行解析,取得所需文件內容或屬性信息。
需要注意的是,這些文件系統數據也會同其他內存頁一樣得到高速緩存。對於隨後發生的 I/O請求,文件數據的部分或全部可能仍舊位於物理內存當中,無需再從磁盤讀取即可重複使用。大多數操作系統假設進程會繼續讀取文件剩餘部分,因而會預讀額外的文件系統頁。如果內存爭用情況不嚴重,這些文件系統頁可能在相當長的時間內繼續有效。這樣的話,當稍後該文件又被相同或不同的進程再次打開,可能根本無需訪問磁盤。這種情況您可能也碰到過:當重複執行類似的操作,如在幾個文件中進行字符串檢索,第二遍運行得似乎快多了。類似的步驟在寫文件數據時也會採用。這時,文件內容的改變(通過 write( ))將導致文件系統頁變髒,隨後通過頁面調出,與磁盤上的文件內容保持同步。文件的創建方式是,先把文件映射到空閒文件系統頁,在隨後的寫操作中,再將文件系統頁刷新到磁盤。

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