Linux虛擬內存技術初窺

1. 為什么要用虛擬內存

總所周知,從做系統的主要作用是對計算機資源的管理以及程序調度,者其中就包括對內存的管理。現在很多的系統都是用虛擬內存技術來對內存的管理,所謂虛擬內存,就是一種讓應用程序覺得它擁有一個很大的內存可以使用,例如對于一個64位的操作系統,操作系統會給應用程序制造一種它可以有2^64Bytes那么大的內存可以使用的假象,雖然實際情況可能是這個電腦上只有4G的內存。
對于一個多任務操作系統,如果不適用虛擬內存,計算機所擁有的那點內存顯然不夠分,雖然我們可以選擇增加物理內存的方式讓程序擁有更多的內存可以使用,但是內存的價格畢竟在哪里。
另外,即便你并不在乎價格,多大的內存都能隨便買得起,但也并不意味這你裝了多大內存就有多少內存可以使用,不同CPU的架構限制了它對內存地址的訪問能力。例如一個64位架構的CPU可能它只實現了44位,那么意味著它能訪問的物理內存只有2^44Bytes這么大。
除了上面所說的資源抽象的作用,虛擬內存還具有以下兩個優點:

  1. 信息隔離:虛擬內存使得每個進程都有自己的一個地址空間,每個進程之間不能相互訪問對方的地址空間,這就增加了安全性;
  2. 錯誤隔離:每個進程內部的錯誤只會影響到該進程,而不會危及別的進程。

2. 術語

2.1. 地址空間

地址空間簡單來說就是一個進程能夠訪問的所有虛擬地址,例如在64位系統中一個程序擁有的地址空間為0~0xFFFFFFFFFFFFFFFF

2.2 虛擬地址

虛擬地址(Virtual Address,VA),就是地址空間中的任意一個地址;

2.3. 物理地址

物理地址(Physical Address,PA),CPU訪問內存所使用的真實地址;

2.4 頁

虛擬內存中一段連續的內存區域,整個虛擬內存被分成多個大小相等的頁,不同架構的CPU定義的頁的大小可能不同,例如4KB、8KB等。

2.5 頁幀

物理內存中一段連續的區域,整個物理內存被分成多個大小相等的頁幀,不同架構的CPU定義的頁幀的大小可能不同,例如4KB、8KB等。一般情況下頁和頁幀的大小是一樣的,但在某些架構的CPU上頁和頁幀的大小可能不一樣。

2.4 頁表

保存虛擬地址與物理地址映射關系的一個表,Linux中頁表分為多級結構,每一級都占據整個頁幀。頁幀只存在與物理內存上并且不能交換,因此頁幀的大小的大小其實也限制了地址空間的大小。例如一個64位的操作系統,雖然理論上它的地址空間應該是0-2^64 Byte,但是在頁幀大小為4KB,擁有三級頁表的情況下,地址空間只有0-2^39那么大。這是怎么算出來的呢?
在三級頁表中,這三級分別稱為PGD(Page Global Directory)、PMD(Page Middle Directory)和PTE(Page Table Entry),他們的關系如圖1所示,PGD指向PMD,PMD指向PTE,PTE最終指向頁幀。PGD和PMD中存儲的下一級的物理地址,而PTE中存儲的內容包括PFN(Page Frame Number)、標志位等信息。

圖1 頁表結構示意圖

由于頁幀的大小已經是4KB,64位系統中也表中的每一項大小為64位也就是8Byte,那么一個頁幀能存儲的表項的數目為512條。由于只有一個頁幀存儲PGD,那么只需要使用9位就能夠索引到該頁幀中的所有表項,因此虛擬地址中使用9位表示PGD中某一項。同理,PMD與PTE也只用9位,因此PGD、PMD與PTE占了虛擬地址中的27位。當最終通過PTE找到存儲數據的真正頁幀,由于該頁幀中保存的是實際的數據,通常以Byte為最小地址索引單元,因此4KB就能分成1024*4這么多小塊,需要12位才能索引完,合起來就需要39位。為了能使用更大的地址空間,就需要通過采取更大頁幀或者增加頁表級數的方法,例如如果使用8KB的頁幀,則地址空間可以擴展到43位,而如果再擴展一級頁表,則可以有48位地址空間可以使用。

2. 怎么表示物理內存

物理內存分為NUMA(Non-Uniform Memory Access)和UMA(Uniform Memory Access)兩種類型,雖然我們通常認為CPU訪問內存的各個區域的代價是一樣的,但是有時候并不是這樣。在Linux中,將訪問代價一樣的內存歸為一個Node,UMA只有一個Node,NUMA有多個Node,一般的PC都是UMA類型的。
由于架構的限制,CPU并不能平等使用所有的內存,例如某些DMA處理器只能訪問物理內存開始的16M內存。因此,需要將物理內存分為不同的區(Zone),通常可分為以下三個不同的區:ZONE_DMA、ZONE_NORMAL以及ZONE_HIGHMEM,每一個區都表示一塊連續的內存。
而每個區可以分為一個個頁幀,因此,在Linux中,物理內存的表示如下圖2所示。


圖2 Linux中物理內存的表示

3. 怎么通過虛擬內存地址找到物理內存地址

物理地址和虛擬地址的映射是通過頁表來實現的。那么我們怎么樣才能通過頁表將虛擬地址轉化為物理地址呢?如圖3所示,下面簡要介紹下頁表是怎么工作的。



在Linux中,每個進程有一個指向PGD的指針,通過該指針我們能找到PGD表。找到該PGD后,通過所給的地址的指定9位(如圖3中的page index),我們可以找到該虛擬地址所屬于的PGD項,該相中保存的是指向PMD的地址,這樣,我們就來到該虛擬地址所屬于的PMD中。與之前一樣,我們在PMD中通過pmd index找到了對應的PTE的地址,我們通過pte index找到了該虛擬地址所映射的頁幀。最后,通過offset我們就能確定該虛擬地址所指向的是所找到頁幀的哪一字節。

4. 如何實現

1949年10月1日,新中國成立的開國大典,很少人知道,當時天上飛過的不是26架戰機,而是17架。那時,我國總共有100多架戰機,其中很多已經破爛不堪,飛都很難飛起來,而且還是萬國牌。
如何讓場面看起來更壯觀些?
沉思良久后,周總理提出了一個建議:這17架飛機中有9架速度比較快,閱兵時讓它們飛前面,繞一圈后迅速回到隊伍最后面,這樣就能營造出一種有26架飛機參加閱兵式的感覺。

操作系統對內存的操作和這個飛機飛兩遍的操作非常相似。讓多個虛擬在 不同時間段與同一個頁幀做映射,就能然人柑橘能使用的內存比實際的大。
只是僅僅讓多個虛擬頁分別于頁幀分時映射還不行,還需要有一個地方用于保存他們各自的數據,這個地方就是硬盤。比如某個市場只有一個攤位,張三、李四和王五三班倒的用這個攤位賣東西。他們三人彼此并不知道對方的存在,顯然,除了這個攤位,他們三人必須還各種需要一個倉庫用于在他們不賣貨的時間段存放他們的貨物,否則如果他們都貨物都直接丟在攤位,那么張三很可能就把李四的東西給賣了。

如圖4所示,假設操作系統將兩個虛擬頁映射到了同一個頁幀,這兩個虛擬頁可以屬于同一個地址空間也可以屬于不同的地址空間,一般來說應該屬于不同的地址空間,假設它們分別稱為P1和P2,那么從感覺上這兩個虛擬頁都是獨立的一塊內存,雖然實際上他們映射的是同一個頁幀,假設這個頁幀成為F,但同一時間內只能有一個虛擬頁和頁幀綁定。此外,P1和P2在磁盤上各自有著一款與它們自身大小相等的空間作為備份,假設分別是B1與B2。某一時刻,F存儲的是P1的內容,現在程序訪問到了P2,由于P2的目前還沒和F綁定,就會觸發缺頁中斷(page fault)。操作系統先掛起當前程序,然后中斷處理程序(page fault handler)就會將目前物理內存幀內的內容存儲到磁盤B1,而把B2的內容讀到F,然后將P2綁定到F,最后恢復被掛起的程序的執行。通過這一換頁機制,程序就拿到了P2上的存儲的內容。當又需要P1的內容的時候,同樣的方法得到,而應用程序并不知道這些,在應用程序看來,它需要的數據一值都在內存中。

圖4 換頁機制

5. 總結

本文只是對虛擬內存做了簡單的介紹,內存是個有意思的東西,程序的一切操作都是圍繞它展開,理解了底層對內存的操作,對于理解上層程序對內存所做的各種操作也很有幫助。

歡1迎2關3注4個5人6公7眾8號:TensorBoy

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。