天天看點

(理論篇)從基礎檔案IO說起虛拟記憶體,記憶體檔案映射,零拷貝

  為了快速建構項目,使用高性能架構是我的職責,但若不去深究底層的細節會讓我失去對技術的熱愛。

  探究的過程是痛苦并激動的,痛苦在于完全了解甚至要十天半月甚至沒有機會去應用,激動在于技術的相同性,新的架構不再是我焦慮。

  每一個底層細節的攻克,就越發覺得自己對計算機一無所知,這可能就是對知識的敬畏。

新IO和傳統IO-intsmaze

  新IO和傳統IO都是用于進行輸入/輸出。 

  新IO采用了記憶體映射的方式來處理輸入/輸出,新IO将檔案或檔案的一段區域映射到記憶體中,這樣就可以像通路記憶體一樣通路檔案了,通過這種方式比傳統的輸入/輸出要快的多。通過記憶體映射機制操作檔案比使用正常方法和使用FileChannel讀寫高效的多。

傳統IO操作-intsmaze

  傳統的檔案IO操作中,調用作業系統提供的底層标準IO系統調用函數 read()、write() ,調用此函數的程序(在JAVA中即java程序)由目前的使用者态切換到核心态,然後OS的核心代碼負責将相應的檔案資料讀取到核心的IO緩沖區,然後再把資料從核心IO緩沖區拷貝到程序的私有位址空間中去,這樣便完成了一次IO操作。

傳統IO的優化-intsmaze

  為了減少磁盤的IO操作,同時程式通路一般都帶有局部性,局部性原理,OS根據局部性原理會在一次 read()系統調用過程中預讀更多的檔案資料緩存在核心IO緩沖區中,當繼續通路的檔案資料在緩沖區中時便直接拷貝資料到程序私有空間,避免了再次的低效率磁盤IO操作。 

其過程如下:

(理論篇)從基礎檔案IO說起虛拟記憶體,記憶體檔案映射,零拷貝

為什麼要搞一個核心IO緩沖區把原本隻需一次拷貝資料的事情搞成需要2次資料拷貝呢?

  這麼做是為了減少磁盤的IO操作,為了提高性能而考慮的,程式通路一般都帶有局部性,局部性原理,在這裡主要是指的空間局部性,即我們通路了檔案的某一段資料,那麼接下去很可能還會通路接下去的一段資料,由于磁盤IO操作的速度比直接通路記憶體慢了好幾個數量級,是以OS根據局部性原理會在一次 read()系統調用過程中預讀更多的檔案資料緩存在核心IO緩沖區中,當繼續通路的檔案資料在緩沖區中時便直接拷貝資料到程序私有空間,避免了再次的低效率磁盤IO操作。

新IO-intsmaze

  講新IO前先講講背景知識,虛拟空間。

(理論篇)從基礎檔案IO說起虛拟記憶體,記憶體檔案映射,零拷貝

虛拟空間-intsmaze

  很久很久以前的存儲管理技術必須将作業全部裝入記憶體才能執行且作業常駐記憶體直到運作結束,難以滿足較大作業或較多作業進入記憶體執行。 為了能讓作業的一部分裝入就可以運作的存儲管理技術叫做虛拟記憶體管理技術。 

  現代作業系統中的程序在使用記憶體的時候,都不是直接通路記憶體實體位址的,程序通路的都是虛拟記憶體位址,然後虛拟記憶體位址再轉化為記憶體實體位址。 虛拟記憶體就是硬碟中的一塊區域,它用來存放記憶體裡使用頻率不高的頁面檔案,讓使用頻率高的頁面檔案活動在記憶體區域中,提高CPU對資料操作的速度。 

  程序看到的所有位址組成的空間,就是虛拟空間。虛拟空間是某個程序對配置設定給它的所有實體位址(已經配置設定的和将會配置設定的)的重新映射。 在Linux中,這個區域叫做swap,一般大小應設定為實體記憶體的2倍。詳情見 https://blog.csdn.net/fengxinlinux/article/details/52071766

局部性原理-intsmaze

  大多數程式執行時,在一個較短的時間内僅能使用程式代碼的一部分,相應的,程式所通路的存儲空間也局限于某個區域,這就是程式執行的局部性原理。

  基于局部性原理,在程式裝入時可以将程式的一部分放入記憶體,而将其餘部分放在外存,然後啟動程式(部分裝入)。在程式執行期間,當所通路的資訊不在記憶體中,再由作業系統将所需的部分調入記憶體(請求調入)。另外,系統将記憶體中暫時不用的内容置換到外存上,騰出空間存放将要調入記憶體的資訊(置換功能)。

頁式虛拟位址與記憶體頁面實體位址轉-intsmaze

  虛拟位址轉化為真實位址的時候,不一定會對應記憶體位址,還可能對應硬碟位址。 記憶體的一個位址一般對應1byte,硬碟的一個位址一般對應512byte(一個磁盤扇區). 

  記憶體和硬碟裡的資料做交換時,也就是把一個記憶體位址對應的資料拷貝到硬碟裡或者反過來把硬碟資料拷貝到記憶體裡,想要友善處理作業系統會統一機關(傳說中的頁對齊)。 頁就是一個統一的機關,頁的大小總是磁盤扇區大小的倍數,通常是2次幂,比如1024位元組。

  有了頁這個統一機關,接下來我們說的虛拟位址、記憶體位址、磁盤位址都是對應的一個頁。頁式虛拟位址與記憶體實體位址建立一一對應的頁表(硬體位址變換機構來執行轉換)。将邏輯位址上連續的頁号映射到實體記憶體中稱為離散的多個實體塊(頁面),将頁面和實體塊一一對應,展現在頁表。(頁表由頁号和塊号組成)

  虛拟位址空間可以大于實際的記憶體空間,比如實際記憶體大小是1G,但是虛拟位址空間可以是4G。這樣在作業系統中的普通應用程式看來,就好像是有4G的可用記憶體。 

  虛拟位址空間可以大于實際記憶體空間,這是怎麼實作的呢? 

  比如我實際記憶體1G,虛拟記憶體設成了4G,現在往4G的虛拟記憶體裡放了4G的資料,那麼目前隻有1G的資料在真實記憶體中,另外的3G因為裝不下就隻能以檔案形式放到硬碟裡,這個存放記憶體内容的硬碟檔案就叫頁面檔案。 

  虛拟記憶體的空間=實體記憶體+頁面檔案。

頁式管理-intsmaze

  各程序的虛拟空間被劃分為等的頁若幹個長度相,頁長1K—4K。程序虛拟位址變為由頁号P與頁内位址W組成。 同時也把記憶體分成與頁面大小相等的區域,稱為頁面。使用者程序在記憶體空間除了在每個頁面内位址連續之外,每個頁面之間不再連續。

作業系統層面優化提升程式執行效率-intsmaze

1,設定虛拟記憶體大小-intsmaze

swap空間就是虛拟記憶體,在實體記憶體不足時,有較大的用處。

檢視記憶體空間大小:free -m // m表示顯示的位元組機關是m(megabytes)

用指令free檢視系統内 Swap 分區大小。

free -m
total used free shared buffers cached
Mem: 1002 964 38 0 21 410
-/+ buffers/cache: 532 470
Swap: 951 32 929
可以看到 Swap 隻有951M      

如何修改百度即可。

2,設定實際記憶體和虛拟記憶體進行資料交換的傾向性-intsmaze

  vm.swappiness是Linux核心的一個參數,範圍是0~100。它表示實際記憶體和虛拟記憶體區域進行資料交換的傾向性大小,數值越大表示傾向性越大,即交換的頁面檔案越多,反之亦然。一般預設值為60。可用'cat /proc/sys/vm/swappiness’檢視。

  這個值應該設定成多大才能提高Linux的性能呢?

  以下摘自 https://blog.csdn.net/liu870915/article/details/51860932

這個當然要由具體的環境來定了。在一台CentOS機器上,分别把值設為0,60,100,下面是運作'vmstat -S M 5’的三次資料報告。(vmstat指令是用來檢視虛拟記憶體狀況的,參數-S M表示以M為機關,5表示每5秒鐘産生一次報告。)這裡主要關注bi,bo和wa這三個值,bi代表每秒鐘從硬碟讀入資料的塊數(因為硬碟是塊裝置),bo表示每秒鐘寫入硬碟資料的塊數,wa表示CPU等待IO裝置就緒的時間。
當值為100時,wa基本為50左右的值,這表示50%的CPU時間都在等待IO裝置就緒(大好的CPU資源就這樣被浪費了!)現在你明白瓶頸在哪裡了吧?對,就是硬碟。
說明我實驗的這台機器硬碟IO的處理能力是最影響性能的了。那麼該怎麼解決呢?當然了,換個轉速更快的硬碟當然可以,還有呢?增加記憶體有可能也可以。增加了記憶體以後,再把swappiness的值設小點,以減少硬碟IO的操作。記憶體夠大時,無論頁面檔案的使用頻率是高還是低都放在記憶體裡,無須使用虛拟記憶體。
但是在這個例子中,swpd的值始終為0,這表示沒有虛拟檔案被使用。這說明記憶體容量是足夠的,即使再增加記憶體,作用也不大。最好的辦法就是更換硬碟了。
如何改變swapiness的值?你可以運作'echo 數值 > /proc/sys/vm/swapiness’ 或者 'sysctl –w vm.swappiness = 數值' 來修改核心中的實時參數。如果想機器在重新開機之後仍然保持這個數值的話,就需要在'/etc/sysctl.conf’檔案中加上'vm.swappiness = 數值' 這一行。      

新IO-記憶體映射檔案-intsmaze

  傳統IO中當對檔案進行操作的時候,一般總是先打開檔案,然後申請一塊記憶體用做緩沖區,再将檔案資料循環讀入并處理,當檔案長度大于緩沖區長度的時候需要多次讀入。 

  記憶體映射檔案是将一個檔案直接映射到程序的程序空間中(“映射”就是建立一種對應關系,這裡指硬碟上檔案的位置與程序邏輯位址空間中一塊相同區域之間一 一對應,這種關系純屬是邏輯上的概念,實體上是不存在的),這樣可以通過記憶體指針用讀寫記憶體的辦法直接存取檔案内容。 

  在記憶體映射過程中,并沒有實際的資料拷貝,檔案沒有被載入記憶體,隻是邏輯上放入了記憶體,具體到代碼,就是建立并初始化了相關的資料結構,這個過程由系統調用mmap()實作,是以映射的效率很高.

  經驗表明,記憶體映射IO允許加載不能直接通路的潛在巨大檔案,在大檔案處理方面性能更加優異。它的不足是增加了頁面錯誤的數目(由于作業系統隻将一部分檔案加載到記憶體,如果一個請求頁面沒有在記憶體中,它将導緻頁面錯誤)。

  映射檔案區域的能力取決于于記憶體尋址的大小。在32位機器中,你不能一次通路超過4GB或2 ^ 32(以上的檔案),隻能分批映射。

記憶體映射檔案優化本質-intsmaze

  mmap()是系統調用,沒有進行資料拷貝,資料拷貝是在缺頁中斷處理時進行的,由于mmap()将檔案直接映射到使用者空間,是以中斷處理函數根據這個映射關系,直接将檔案從硬碟拷貝到使用者空間(沒有拷貝到核心空間),隻進行了一次資料拷貝 。 

  從硬碟上将檔案讀入記憶體,都是要經過資料拷貝,并且資料拷貝操作是由檔案系統和硬體驅動實作的,理論上來說,拷貝資料的效率是一樣的。 

  記憶體映射檔案的效率比标準IO高的重要原因就是因為少了把資料拷貝到OS核心緩沖區這一步,記憶體映射隻拷貝一次效率要比read/write 拷貝兩次高。

(理論篇)從基礎檔案IO說起虛拟記憶體,記憶體檔案映射,零拷貝

虛拟記憶體與記憶體映射檔案的聯系-intsmaze

  虛拟記憶體是記憶體映射檔案的基礎,記憶體映射檔案的底層還是依賴虛拟記憶體。虛拟記憶體和記憶體映射檔案都是将一部分内容加載到記憶體,另一部分放在磁盤上,二者都是應用程式動态性的基礎,由于二者的虛拟性,對于使用者都是透明的. 

  虛拟記憶體是硬碟的一部分,是計算機RAM(随機存取存儲器)與硬碟的資料交換區,因為實際的實體記憶體可能遠小于程序的位址空間,這就需要把記憶體中暫時不用到的資料放到硬碟上一個特殊的地方,當請求的資料不在記憶體中時,系統産生缺頁中斷,記憶體管理器便将對應的記憶體頁重新從硬碟調入實體記憶體。 

  記憶體映射檔案是由一個檔案到一塊記憶體的映射,使應用程式可以通過記憶體指針對磁盤上的檔案進行通路,其過程就如同對加載了檔案的記憶體的通路,是以記憶體檔案映射非常适合于用來管理大檔案。

虛拟記憶體與記憶體映射檔案的差別-intsmaze

  虛拟記憶體實作的基礎是分頁機制和局部性原理,架構在實體記憶體之上,其引入是因為實際的實體記憶體運作程式所需的空間,即使現在計算機中的實體記憶體越來越大,将所有運作着的程式全部加載到記憶體中非常不現實。

  記憶體映射檔案虛拟性并不是由于局部性,而是使程序虛拟位址空間的某個區域建立映射磁盤檔案的全部或部分内容,通過該區域可以直接對被映射的磁盤檔案進行通路,而不必執行檔案I/O操作也無需對檔案内容進行緩沖處理。

  

  用圖來表示mmap,即為如下所示。mmap函數會在記憶體中找一段空白記憶體,然後将這部分記憶體與檔案的内容對應起來。我們對記憶體的所有操作都會直接反應到檔案中去。mmap的主要功能就是建立記憶體與檔案這種對應關系。是以才被命名為memory map。

(理論篇)從基礎檔案IO說起虛拟記憶體,記憶體檔案映射,零拷貝

  此圖為 Linux 中程序的虛拟存儲器,即程序的虛拟位址空間, 32 位作業系統,就有2^32 = 4G的虛拟位址空間, 

  圖中有一塊區域: “共享庫的記憶體映射區域” ,這段區域就是在記憶體映射檔案的時候将某一段的虛拟位址和檔案對象的某一部分建立起映射關系,此時并沒有拷貝資料到記憶體中去,而是當程序代碼第一次引用這段代碼内的虛拟位址時,觸發了缺頁異常,這時候OS根據映射關系直接将檔案的相關部分資料拷貝到程序的使用者私有空間中去。

請收看下節内容-intsmaze

  自此檔案IO的演化理論依據介紹完了,下一篇将會基于java的源碼去看各種實作。

  文章内容參考:

  《深入了解計算機系統(原書第三版3)》,《清華大學計算機系列教材:計算機作業系統教程(第4版)》,示例圖檔來源于其他部落格。

作者:intsmaze(劉洋)

暢銷書籍:《深入了解Flink核心設計與實踐原理》作者

出處:http://www.cnblogs.com/intsmaze/

老鐵,你的--->推薦,--->關注,--->評論--->是我繼續寫作的動力。

由于部落客能力有限,文中可能存在描述不正确,歡迎指正、補充!

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。