1. 程式人生 > >Linux分頁機制之分頁機制的演變--Linux記憶體管理(七)

Linux分頁機制之分頁機制的演變--Linux記憶體管理(七)

1 頁式管理

1.1 分段機制存在的問題

分段,是指將程式所需要的記憶體空間大小的虛擬空間,通過對映機制對映到某個實體地址空間(對映的操作由硬體完成)。分段對映機制解決了之前作業系統存在的兩個問題:

  1. 地址空間沒有隔離
  2. 程式執行的地址不確定

不過分段方法存在一個嚴重的問題:記憶體的使用效率低。

分段的記憶體對映單位是整個程式;如果記憶體不足,被換入換出到磁碟的空間都是整個程式的所需空間,這會造成大量的磁碟訪問操作,並且嚴重降低了執行速度.

事實上,很多時候程式執行所需要的資料只是很小的一部分,加入到記憶體的資料大小可能會很小,並沒有必要整體的寫入和寫出.

分頁機制解決了上面分段方法所存在的一個記憶體使用效率問題;其核心思想是系統為程式執行檔案中的第x頁分配了記憶體中的第y頁,同時y頁會新增到程序虛擬空間地址的對映表中(頁表),這樣程式就可以通過對映訪問到記憶體頁y了。

1.2 分頁儲存的基本內容

分頁的基本方法是將地址空間人為地等分成某一個固定大小的頁;每一頁大小由硬體來決定,或者是由作業系統來決定(如果硬體支援多種大小的頁)。目前,以大小為4KB的分頁是絕大多數PC作業系統的選擇.

  • 邏輯空間等分為頁;並從0開始編號
  • 記憶體空間等分為塊,與頁面大小相同;從0開始編號
  • 分配記憶體時,以塊為單位將程序中的若干個頁分別裝入

關於程序分頁. 當我們把程序的虛擬地址空間按頁來分割,常用的資料和程式碼會被裝在到記憶體;暫時沒用到的是資料和程式碼則儲存在磁碟中,需要用到的時候,再從磁碟中載入到記憶體中即可.

這裡需要了解三個概念:

  1. 虛擬頁(VP, Virtual Page),虛擬空間中的頁;
  2. 物理頁(PP, Physical Page),實體記憶體中的頁;
  3. 磁碟頁(DP, Disk Page),磁碟中的頁。

虛擬記憶體的實現需要硬體的支援,從Virtual Address到Physical Address的對映,通過一個叫MMU(Memory Mangement Unit)的部件來完成

2 分頁機制支援

2.1 硬體分頁支援

分頁單元(paging unit)把線性地址轉換成實體地址。其中的一個關鍵任務就是把所請求的訪問型別與線性地址的訪問許可權相比較,如果這次記憶體訪問是無效的,就產生一個缺頁異常。

  • : 為了更高效和更經濟的管理記憶體,線性地址被分為以固定長度為單位的組,成為頁。頁內部連續的線性地址空間被對映到連續的實體地址中。這樣,核心可以指定一個頁的實體地址和對應的存取許可權,而不用指定全部線性地址的存取許可權。這裡說頁,同時指一組線性地址以及這組地址包含的資料

  • 頁框:分頁單元把所有的 RAM 分成固定長度的頁框(page frame)(有時叫做物理頁)。每一個頁框包含一個頁(page),也就是說一個頁框的長度與一個頁的長度一致。頁框是主存的一部分,因此也是一個儲存區域。區分一頁和一個頁框是很重要的,前者只是一個數據塊,可以存放在任何頁框或磁碟中。
  • 頁表:把線性地址對映到實體地址的資料結構稱為頁表(page table)。頁表存放在主存中,並在啟用分頁單元之前必須由核心對頁表進行適當的初始化。

2.2 常規的32bit分頁

常規4KB分頁,32位的線性地址被分成3個域

Directory(目錄) Table(頁表) Offset(偏移量)
最高10位 中間10位 最低12位

線性地址的轉換分為兩步完成,每一步都基於一種轉換表,第一種轉換表稱為頁目錄表(page directory),第二種轉換表稱為頁表(page table)。

為什麼需要兩級呢?

目的在於減少每個程序頁表所需的 RAM 的數量。如果使用簡單的一級頁表,將需要高達2^20 個表項來表示每個程序的頁表,即時一個程序並不使用所有的地址,二級模式通過職位程序實際使用的那些虛擬記憶體區請求頁表來減少記憶體容量。

每個活動的程序必須有一個頁目錄,但是卻沒有必要馬上為所有程序的所有頁表都分配 RAM,只有在實際需要一個頁表時候才給該頁表分配 RAM。

頁目錄項和頁表項有同樣的結構,每項都包含下面的欄位:

欄位 描述
Present標誌 如果被置為1,所指的頁(或頁表)就在主存中;如果該標誌為0,則這一頁不在主存中,此時這個表項剩餘的位可由作業系統用於自己的目的。如果執行一個地址轉換所需的頁表項或頁目錄項中Present標誌被清0,那麼分頁單元就把該線性地址轉換所需的頁表項或頁目錄項中Present標誌被清0,那麼分頁單元就把該線性地址存放在控制暫存器cr2中,併產生14號異常:缺頁異常。
--- 包含頁框實體地址最高20位的欄位。由於每一個頁框有4KB的容量,它的實體地址必須是4096的倍數,因此實體地址的最低12位總為0.如果這個欄位指向一個頁目錄,相應的頁框就含有一個頁表;如果它指向一個頁表,相應的頁框就含有一頁資料
Accessed標誌 每當分頁單元對相應頁框進行定址時就設定這個標誌。當選中的頁被交換出去時,這一標誌就可以由作業系統使用。分頁單元從來不重置這個標誌,而是必須由作業系統去做
Dirty標誌 只應用於頁表項中。每當對一個頁框進行寫操作時就設定這個標誌。與Accessed標誌一樣,當選中的頁被交換出去時,這一標誌就可以由作業系統使用。分頁單元從來不重置這個標誌,而是必須由作業系統去做。
Read/Write標誌 含有頁或頁表的存取許可權(Read/Write或Read)。
User/Supervisor標誌 含有訪問頁或頁表所需的特權級。
PCD和PWT標誌 控制硬體快取記憶體處理頁或頁表的方式。
Page Size標誌 只應用於頁目錄項。如果設定為1,則頁目錄項指的是2MB或4MB頁框。
Global標誌 只應用於頁表項。這個標誌是在Pentium Pro中引入的,用來防止常用頁從TLB快取記憶體中刷新出去。只有在cr4暫存器的頁全域性啟用(Page Global Enable, PGE)標誌置位時這個標誌才起作用。

正在使用的頁目錄的實體地址存放在控制暫存器CR3中。

瞭解了以上結構之後,我們看看如何從線性地址轉換到實體地址的 :

  • 線性地址中的 Directory 欄位決定頁目錄中的目錄項,目錄項指向適當的頁表
  • 線性地址中的 Table 欄位又決定頁表的頁表項,頁表項含有頁所在頁框的實體地址
  • 線性地址中的 Offset 地段決定了頁框內的相對位置,由於 offset 為 12 為,所以一頁含有 4096 位元組的資料

Directory欄位和Table欄位都是10位長,因此頁目錄和頁表都可以多達1024項。那麼一個頁目錄可以定址到高達1024*1024*4096=232個儲存單元,這和32位地址所期望的一樣。

2.3 實體地址擴充套件(PAE)分頁機制和擴充套件分頁(PSE)

處理器所支援的RAM容易受到連線到地址總線上的地址管腳樹限制. 早期Intel處理器從80386到Pentium使用32位實體地址.

從理論上講, 這樣的系統可以使用高達2^32=4GB的RAM, 而實際上, 由於使用者程序現行地址空間的需要, 4GB的虛擬地址按照1:3的比例劃分給核心虛擬地址空間和程序虛擬地址空間. 則核心只能直接對1GB的線性地址空間進行定址.

然而, 大型伺服器需要大於4GB的RAM來同時執行數以錢計的程序, 所以必須擴充套件32位80x86架構所支援的RAM容量.

Intel通過在它的處理器上把管腳數從32增加到36滿足這樣的需要, 從Pentinum Pro開始, Intel所有處理器的定址能力可達到2^36=64GB, 但是隻有引入一種新的分頁機制才能把32位現行地址轉換為36位實體地址才能使用所增加的實體地址.

從Pentinum Pro處理器開始, Intel引入一種叫做實體地址擴充套件(Physical Address Extension, PAE)的機制.

從Pentium模型開始,80x86微處理器引入了擴充套件分頁(externded paging),也叫頁大小擴充套件[Page Size Extension], 它允許頁框大小為4MB而不是4KB。擴充套件分頁用於把大段連續的線性地址轉換成相應的實體地址,在這種情況下,核心可以不用中間頁表進行地址轉換,從而節省記憶體並保留TLB項。

但是Linux並沒有採用這種機制

正如前面所述,通過設定頁目錄項的Page Size標誌啟用擴充套件分頁功能。在這種情況下,分頁單元把32位線性地址分成兩個欄位:

  • Directory:最高10位。
  • Offfset:其餘22位。

擴充套件分頁和正常分頁的頁目錄項基本相同,除了

  • Page Size標誌必須被設定。
  • 20位實體地址欄位只有最高10位是有意義的。這是因為每一個實體地址都是在以4MB為邊界的地方開始的,故這個地址的最低22位為0。

通過設定cr4處理器暫存器的PSE標誌能使擴充套件分頁與常規分頁共存

Intel為了支援PAE改變了分頁機制

  • 64GB的RAM被分成了2^24個頁框, 頁表項的實體地址欄位從20位擴充套件到了24位. 因為PAE頁表項必須包含12個標誌位和24個實體地址位, 總數之和為36, 頁表項大小從32位擴充套件到了64位, 結果, 一個4KB的頁表項包含512個表項而不是1024個表項
  • 引入一個頁目錄指標表(Page Directory Pointer Table, PDPT)的頁表新級別, 它由4個64位表項組成.
  • cr3控制暫存器包含一個27位的頁目錄指標表(PDPT)基地址欄位. 因為PDPT存放在RAM的前4GB中, 並在32位元組(2^5)的倍數上對其, 因此27位足以表示這種表的基地址
  • 當把線性地址對映到4KB的頁時(頁目錄項中的PS標準清0), 32位線性地址將按照如下方式解釋
欄位 描述 位數
cr3 指向一個PDPT crs暫存器儲存
PGD 指向PDPT中4個項中的一個 位31~30
PMD 指向頁目錄中512項中的一個 位29~21
PTE 指向頁表中512項中的一個 位20~12
page offset 4KB頁中的偏移 位11~0
  • 當把現行地址對映到2MB的頁時(頁目錄項中的PS標誌置為1), 32位線性地址按照如下方式解釋
欄位 描述 位數
cr3 指向一個PDPT crs暫存器儲存
PGD 指向PDPT中4個項中的一個 位31~30
PMD 指向頁目錄中512項中的一個 位29~21
page offset 2MB頁中的偏移 位20~0

總之, 一旦cr3被設定, 就可能定址高達4GB RAM, 如果我們期望堆更多的RAM進行定址, 就必須在cr3中放置一個新值, 或改變PDPT的內容.

但是PAE的主要問題是線性地址仍然是32位長, 這就需要核心黑客用同一線性地址對映不同的RAM區. 很顯然, PAE並沒有擴大程序的線性地址空間, 因為它只處理實體地址. 此外, 只有核心能夠修改程序的頁表, 所以在使用者態下執行的程式不可能使用大於4GB的實體地址空間. 另一方面, PAE允許核心使用容量高達64GB的RAM, 從而顯著的增加系統中的程序數目

2.4 64位系統中的分頁

32位處理器普遍採用兩級分頁。然而兩級分頁並不適用於採用64位系統的計算機。

原因如下 :

首先假設一個大小為4KB的標準頁,4KB覆蓋2^12個地址,所以offset欄位是12位。如果我們現在決定僅僅使用64位中的48位來定址(這個限制仍然能是我們自在地擁有256TB的定址空間!),剩下的48-12=36位被分配給Table和Directory欄位。如果我們決定為兩個欄位個預留18位,那麼每個程序的頁目錄和頁表都含有2^18個項,即超過256000個項。

由於這個原因,所有64位處理器的硬體分頁系統都使用了額外的分頁級別。使用的級別數量取決於處理器的型別。

平臺名稱 頁大小 定址使用位數 分頁級別 線性地址分級
alpha 8KB 43 3 10+10+10+13
ia64 4KB 39 3 9+9+9+12
ppc64 4KB 41 3 10+10+9+12
x86_64 4KB 48 4 9+9+9+9+12

注:ia64是intel的一門高階技術,不與x86_64系統相容
IA-32e Paging機制下線性地址對映到4KB的頁

2.5 硬體保護方案

與頁和頁表相關的特權級只有兩個,因為特權由前面“常規分頁”一節中所提到的User/Supervisor標誌所控制。若這個標誌為0,只有當CPL小於3(這意味著對於Linux而言,處理器處於核心態)時才能對頁定址;若該標誌為1,則總能對頁定址。

此外,與段的3種存取許可權(讀,寫,執行)不同的是,頁的存取許可權只有兩種(讀,寫)。如果頁目錄項或頁表項的Read/Write標誌等於0,說明相應的頁表或頁是隻讀的,否則是可讀寫的。

3 總結

假設每個程序都佔用了4G的線性地址空間,頁表共含1M個表項,每個表項佔4個位元組,那麼每個程序的頁表要佔據4M的記憶體空間。為了節省頁表佔用的空間,我們使用兩級頁表。每個程序都會被分配一個頁目錄,但是隻有被實際使用頁表才會被分配到記憶體裡面。一級頁表需要一次分配所有頁表空間,兩級頁表則可以在需要的時候再分配頁表空間。

3.1 為什麼使用多級頁表

假設每個程序都佔用了4G的線性地址空間,頁表共含1M個表項,每個表項佔4個位元組,那麼每個程序的頁表要佔據4M的記憶體空間。為了節省頁表佔用的空間,我們使用兩級頁表。每個程序都會被分配一個頁目錄,但是隻有被實際使用頁表才會被分配到記憶體裡面。一級頁表需要一次分配所有頁表空間,兩級頁表則可以在需要的時候再分配頁表空間。

3.2 x86_32兩級頁表結構

兩級表結構的第一級稱為頁目錄,儲存在一個4K位元組的頁面中。頁目錄表共有1K個表項,每個表項為4個位元組,並指向第二級表。線性地址的最高10位(即位31~位32)用來產生第一級的索引,由索引得到的表項中,指定並選擇了1K個二級表中的一個表。

兩級表結構的第二級稱為頁表,也剛好儲存在一個4K位元組的頁面中,包含1K個位元組的表項,每個表項包含一個頁的物理基地址。第二級頁表由線性地址的中間10 位(即位21~位12)進行索引,以獲得包含頁的實體地址的頁表項,這個實體地址的高20位與線性地址的低12位形成了最後的實體地址,也就是頁轉化過程輸出的實體地址。

  • 第31~12位是20位頁表地址,由於頁表地址的低12位總為0,所以用高20位指出32位頁表地址就可以了。因此,一個頁目錄最多包含1024個頁表地址。
  • 第0位是存在位,如果P=1,表示頁表地址指向的該頁在記憶體中,如果P=0,表示不在記憶體中。
  • 第1位是讀/寫位,第2位是使用者/管理員位,這兩位為頁目錄項提供硬體保護。當特權級為3的程序要想訪問頁面時,需要通過頁保護檢查,而特權級為0的程序就可以繞過頁保護。

  • 第3位是PWT(Page Write-Through)位,表示是否採用寫透方式,寫透方式就是既寫記憶體(RAM)也寫快取記憶體,該位為1表示採用寫透方式

  • 第4位是PCD(Page Cache Disable)位,表示是否啟用快取記憶體,該位為1表示啟用快取記憶體。

  • 第5位是訪問位,當對頁目錄項進行訪問時,A位=1。

  • 第7位是Page Size標誌,只適用於頁目錄項。如果置為1,頁目錄項指的是4MB的頁面,請看後面的擴充套件分頁。

  • 第9~11位由作業系統專用,Linux也沒有做特殊之用。

80386的每個頁目錄項指向一個頁表,頁表最多含有1024個頁面項,每項4個位元組,包含頁面的起始地址和有關該頁面的資訊。頁面的起始地址也是4K的整數倍,所以頁面的低12位也留作它用.

第31~12位是20位物理頁面地址,除第6位外第0~5位及9~11位的用途和頁目錄項一樣,第6位是頁面項獨有的,當對涉及的頁面進行寫操作時,D位被置1.

4GB的記憶體只有一個頁目錄,它最多有1024個頁目錄項,每個頁目錄項又含有1024個頁面項,因此,記憶體一共可以分成1024×1024=1M個頁面。由於每個頁面為4K個位元組,所以,儲存器的大小正好最多為4GB.

3.3 線性地址到實體地址的轉換

  1. CR3包含著頁目錄的起始地址,用32位線性地址的最高10位A31~A22作為頁目錄的頁目錄項的索引,將它乘以4,與CR3中的頁目錄的起始地址相加,形成相應頁表的地址。

  2. 從指定的地址中取出32位頁目錄項,它的低12位為0,這32位是頁表的起始地址。用32位線性地址中的A21~A12位作為頁表中的頁面的索引,將它乘以4,與頁表的起始地址相加,形成32位頁面地址。

  3. 將A11~A0作為相對於頁面地址的偏移量,與32位頁面地址相加,形成32位實體地址。

3.4 擴充套件分頁

從奔騰處理器開始,Intel微處理器引進了擴充套件分頁,它允許頁的大小為4MB

在擴充套件分頁的情況下,分頁機制把32位線性地址分成兩個域:最高10位的目錄域和其餘22位的偏移量。

3.5 頁面快取記憶體

由於在分頁情況下,每次儲存器訪問都要存取兩級頁表,這就大大降低了訪問速度。所以,為了提高速度,在386中設定一個最近存取頁面的快取記憶體硬體機制,它 自動保持32項處理器最近使用的頁面地址,因此,可以覆蓋128K位元組的儲存器地址。當進行儲存器訪問時,先檢查要訪問的頁面是否在快取記憶體中,如果在, 就不必經過兩級訪問了,如果不在,再進行兩級訪問。平均來說,頁面快取記憶體大約有98%的命中率,也就是說每次訪問儲存器時,只有2%的情況必須訪問兩級分頁機構。這就大大加快了速度.