owned this note
owned this note
Published
Linked with GitHub
# Arm Programmer's Guide IX - The Memory Management Unit 學習筆記
<h1>1. 前言 </h1>
此筆記為學習 [ARM® Cortex™-A Series Programmer's Guide
Version: 4.0 中第九章 MMU ](https://developer.arm.com/documentation/den0013/d/The-Memory-Management-Unit?lang=en) 的心得筆記。其中說明了 Armv7 是如何建立並利用翻譯表格 (Translation table) 將虛擬位置 (Virtual Address) 轉成實體位置 (Physical Address)。最後也會以一個簡單的組合語言程式碼來提供更實際的範例。
<h1>2. Memory Management Unit (MMU) </h1>
MMU 是用來管理記憶體的一個硬體單元。他能將實體記憶體空間抽象化成虛擬記憶體空間並呈獻給 CPU 使用,方法是查找記憶體中一塊事先填寫好的映射表 (translation table, 而這查找行為叫做 translation table walking)。這樣的好處大致有以下三點:
1. 程式 (program 或 task) 只需要專注在自己的記憶體使用上就可以:對不同的程式來說他們都可以覺得自己擁有完整的記憶體可以使用,但實際上 MMU 會將他們映射到不同的實體記憶體上。
2. 管理記憶體空間:能夠對不同的虛擬記憶體區段設定其存取權限,若程式試圖存取其沒有權限的記憶體區段,就會引發 Data Abort (試圖讀寫非法記憶體位置的值) 或 Prefetch Abort (試圖執行非法記憶體區段)。
```clike=
// Data Abort
unsigned int u32IllegalAddr = 0x12340000;
*((unsigned char*)u32IllegalAddr) = 0;
// Prefetch Abort
unsigned int u32IllegalAddr = 0x12340000;
void (*foo)(void) = (void*)u32IllegalAddr;
foo();
```
4. 減少記憶體破碎化:隨著系統執行時間拉長,記憶體在不斷地分配 (allocate) 和釋放 (release) 後,會變得越來越破碎、連續可用的記憶體空間會越來越少。這時虛擬記憶你可以將不連續的可用實體空間透過映射的方式,使其在虛擬空間中是連續可用的。
<h2>2.1 MMU 啟用方法 </h2>
啟用 BIT 位於 SCTLR (System Control Register) 的 BIT M (BIT[0]) 中,而 SCTLR 是[協處理器 P15 的 C1 暫存器](https://developer.arm.com/documentation/den0013/d/ARM-Processor-Modes-and-Registers/Registers/System-control-register--SCTLR-)。
```c=
MRC p15, 0, R1, c1, C0, 0 ;Read control register
ORR R1, #0x1 ;Set M bit
MCR p15, 0,R1,C1, C0,0 ;Write control register and enable MMU
```
<h1>3. Translation Lookaside Buffer (TLB) </h1>
MMU 又是由兩個單元所組成:TLB 和 Table Walk Unit。TLB 其實就是一個 Cache (通常可以存放 64 個 Translation Table 欄位)。每當 MMU 有一個虛擬記憶體需要翻譯時,MMU 會先在 TLB 中找是否有該記憶體位置的欄位,如果有的話 (且該欄位為 valid) 則稱做 "TLB hit",這時 TLB 可以幾乎馬上回傳該虛擬位置的實體位置;如果 TLB 找不到指定欄位,這情況叫做 "TLB miss",那麼 MMU 就會需要 Table Walk Unit 去完整的 Tranlsation Table 中查找,找到後會在將它放進 TLB 中,以便下次使用時能快速反應。
其實還有一個叫做 micro-TLB、用來幫助 instruction 和 data Cache 的 Cache。它在兩邊分別可以存 8 個欄位。如果在 micro-TLB 中 miss 了,才會去 TLB 中查找,這樣的情況會需要付出一些時間代價。下圖為 TLB 欄位的概念圖,其中 ASID 為 Address Space ID 是用來指定記憶體區間給特定 Task ID 用的。

所以說在選擇 Page Size 時,選擇越大的尺寸可以提升 TLB 命中的機率、進而提升系統效能;而越小的尺寸可以提升記憶體的使用效率,減少一個頁面只使用一小部分、其餘大部分都是未使用的情況。
<h1>4. Translation Table</h1>
MMU 將記憶體等分成好幾個頁 (page) 來管理。
Page 的大小越大,則 TLB 會越容易打中,查找的效率較高,但較小的 Page 可以提升記憶體的使用效率,使未被利用的記憶體空間較少。
Translation Table 有兩層,第一層管理的 page size 為 1MB、第二層則又將第一層的 1MB 再細分為各個 4KB 區段。因為 32Bit 總共可以定位 2^32 也就是 4GB 的記憶體空間,所以第一層的 Translation Table 組共會有 4GB / 1MB = 4096 個欄位(每個欄位佔 4 Byte);第二層則會有 1MB / 4KB = 256 個欄位 (每個欄位佔 4 Byte)。
<h2>4.1 First Level Translation Table</h2>
第一層 Table 又稱為「頁全局目錄表」(PGD, Page Global Directory, Table),其位置 - 也就是 Translation Table Base Address - 需要被設定在[協處理器 P15 的 C2](https://developer.arm.com/documentation/ddi0198/e/programmer-s-model/register-descriptions/translation-table-base-register-c2?lang=en) 當中,C2 也稱做 Translation Table Base Register (TTBR)。從該位置開始的 4 Byte x 4096 欄位 = 16KB 的空間都屬於 First Level Translation Table。而其中第 N 欄位也就對應存取虛擬位置第 N MB 位置的映射欄位。

舉例來說,將虛擬位置 0x12340000 右移 20 BIT (或者除以 1M 、或者取高 12 BIT) 就可以得到上圖的 Index: 0x123 ,對應的欄位就是第 0x123 欄,所以要去記憶體位置 Translation Table Base Address (假設為 0x200000000) + (0x123 x 4 Byte) = 0x20000048C 位置取得其實體記憶體是第幾 MB,再加上原本虛擬位置低位 20 BIT 的 Offset (也就是 0x40000) 後就是實體記憶體位置。如下圖所示:

<h3>4.1.1 Entry format </h3>
第一層 Table 的欄位格式如下 (也就是上圖中間那欄):

(SBZ: Should Be Zero, nG: Non-Global, AP: Access Permission, XN: Execute Never, C: Cachable, B: Bufferable)
第一層 Translation table entry 總共有三種類型,由最右邊的兩個 BIT (BIT[1:0]) 決定:
1. 00: Fault entry。
2. 01: Translation entry, 該欄位指向第二層 Translation Table。
3. 10: Section entry, 該欄位直接指向一個實體記憶體區段。其中 BIT18 就將 Section entry 分為 normal section entry 和 supersection entry。
* Normal Section: BIT[18] 為 0,映射到一個 1MB 大小的實體記憶體空間。
* Supersection: BIT[18] 為 1,映射到一塊 16MB 的記憶體空間。需要注意的是 Supersection 要求連續 16 個一樣的欄位來對齊每一個欄位都對應 1MB 的虛擬記憶體位置。而 Supoersection 的存在原因如同 [3. Translation Lookaside Buffer (TLB)](https://hackmd.io/@uMqav-XESBCsrtZEx_Jpug/Arm_Programmer_Guide_Memory_Management_Unit_Studying_Note#3-Translation-Lookaside-Buffer-TLB) 最後提到的,較大的 Page Size 可以提升 TLB 命中機率,進而提升效率。
<h2>4.2 Second Level Translation Table</h2>
第二層 Translation Table 又將第一層欄位描述的 1MB 記憶體空間細分成 256 個更小的 4KB 空間 (所以 2nd level translation table 會占用 256 x 4 byte = 1K 的空間)。
利用虛擬記憶體的高 12 BIT 在 1st level translation table 中找到指定的 2nd level translation table base address 後,可以利用虛擬記憶體的 BIT[19:12] 八個 BIT 來進一步定位出是該 MB 中的哪一個 4KB,進而取得 2nd level translation table 的欄位:

最終取得實體記憶體位置:

<h2>4.3 Attribute </h2>
<h3>4.3.1 Memory Access Permission</h3>
Memory Access Permission 由 APX 和 AP 總共 3 BIT 決定:
|APX| AP | Privileged | Unprivileged |
|---|----|------------|--------------|
| 0 | 00 | No access | No access |
| 0 | 01 | Read/Write | No access |
| 0 | 10 | Read/Write | Read |
| 0 | 11 | Read/Write | Read/Write |
| 1 | 00 | Reserved | Reserved |
| 1 | 01 | Read | No access |
| 1 | 10 | Read | Read |
| 1 | 11 | Reserved | Reserved |
<h3>4.3.2 Memory Types</h3>
Memory Type 有分以下三種:
1. Normal
2. Strong-ordered
3. Device
|Memory Type | Shareable| Cacheable | Description |
|-----------------|----------|-----------|-------------|
| Normal | Yes | Yes | 能在不同核心之間共享的記憶體區段,其執行順序是不被保證的。 |
| Normal | No | Yes | 只給特定單一核心使用的記憶體區段,其執行順序是不被保證的。 |
| Device | - | No | 針對 Memory-mapping 周邊 (Peripheral) 設計的記憶體類型,其實際執行順序是保證和程式呼叫順序一致 |
| Strongly-ordered | - | No | 實際執行順序是保證和程式呼叫順序一致。所有的 Strongly-ordered 記憶體區段在核間都是共享的 |
(所謂的 memory-mapping peripheral 是指可以被映射到記憶體空間的設備,CPU 可以透過一般的記憶存取指令來操作該設備。相對於 memory-mapping peripheral 的是 private port peripheral,也就是需要透過 IN / OUT 指令來操作的周邊設備。)
Normal Memory type 沒有辦法保證實際的存取順序,但是 Device 和 Strong-ordered 可以。也就是說,如果有兩個區段的存取,只要他們至少其中之一是 Noraml memory type,他們的存取順序就是無法保證的;相反的,只要他們都不是 Normal memory type,那他們就會依照執行順序去存取記憶體。
更詳細的 Memory type 說明可以參考原文第十章:[ARM memory ordering model](https://developer.arm.com/documentation/den0013/d/Memory-Ordering/ARM-memory-ordering-model?lang=en)。
Translation table entry 中使用 TEX、C 和 B 欄位來同時控制 Memory Type 和 Cache Policy:
| TEX | C | B | Description | Memory Type |
|-----|---|---|-------------------------------|------------------|
| 000 | 0 | 0 | Strongly-ordered | Strongly-ordered |
| 000 | 0 | 1 | Shareable device | Device |
| 000 | 1 | 0 | Outer and Inner write-though, no allocate on write | Normal |
| 000 | 1 | 1 | Outer and Inner write-back, no allocate on write | Normal |
| 001 | 0 | 0 | Outer and Inner non-cacheable | Normal |
| 001 | - | - | Reserved | - |
| 010 | 0 | 0 | Non-shareable device | Device |
| 010 | - | - | Reserved | - |
| 011 | - | - | Reserved | - |
| 1XX | Y | Y | Cached memory (XX = Outer policy, YY = Inner policy) | Normal |
上表提到的 Outer / Inner write-though / back 或 no allocate on write 都是指 Cache 的策略。
1. No allocate on write: 在寫的時候,如果 Cache 中沒有目標資料,則直接寫入記憶體中,不會新增於 Cache 中。適用在寫入的資料在未不太可能會被用到的時候。
2. Outer Cache: 較高層的 Cache,也就是較接近記憶體、較晚被查詢的 Cache (L2 或 L3)。
3. Inner Cache: 高低層的 Cache,也就是較遠離記憶體、較先被查詢的 Cache (L1 或 L2)。
4. Write-though: 在更新資料時,連同 Cache 和記憶體中的值一起更新。較花時間但對於資料的一致性維持的較好。
5. Write-back: 在更新資料時,只更新 Cache 中的值,而不更新記憶體中的值。記憶體中的值只有到必要的時候才會更新。這樣可以提升效率。
詳細的 Cache 策略可以參考原文第八章:[Caches](https://developer.arm.com/documentation/den0013/d/Caches?lang=en)。
<h3>4.3.3 Excecute Never</h3>
BIT4 為 XN (Execute Never) BIT,如果它為一,則該記憶區段無法被執行,任何試圖去執行的指令都會產生 Prefetch Abort。
<h3>4.3.4 Domains</h3>
這是一個高於前面提到的 Memory Access Permission 的權限設定。總共有以下三種:
1. No-access: 忽略 Memory Access Permission 並禁止所有存取權限。
2. Manager: 忽略 Memory Access Permission 並開放所有存取權限。
3. Client: 依照 Memory Access Permission 來設定存取權限。
這個值的查找方式很特別, Domains 在 Translation table entry 中總共有 4 個 bit (BIT5 ~ BIT8),依照它的值可以有 16 種不同的 Domain ID,稱他們為 D0 ~ D15。至於這 D0 ~ D15 是 No-access 、Manger 還是 Client,要看協處理器 [CP15 的 C3 - Domain Access Control Register (DACR)](https://developer.arm.com/documentation/ddi0595/2020-12/AArch32-Registers/DACR--Domain-Access-Control-Register?lang=en#fieldset_0-31_0)。32 Bit 的 DACR 每兩個 bit 會被分成一個 Domain ID 的定義,也就是 BIT0、BIT1 組成 D0,BIT2、BIT3 組成 D1 ... 以此類推。
|BIT | 31 30 | 29 28 | 27 26 | ... | 5 4 | 3 2 | 1 0 |
|---------|-------|-------|-------|-----|-----|-----|-----|
|Domain ID| D15 | D14 | D13 | ... | D2 | D1 | D0 |
而其值和權限的對應關係如下表:
|D<n> | Meaning |
|-----|-----------|
|0b00 | No access |
|0b01 | Client |
|0b11 | Manager |
|0b10 | Reserved |
所以假設有一個 Translation table entry 的 Domains 為 0b1010,也就是十進位的 10,那就要去看 DACR 的 BIT[22:21] 為多少,如果是 00b 那就是 No access、如果是 01b 就是 Client、如果是 11b 就是 manager。
但 Domain 這個功能在 Armv7 中已經被棄用,但仍然需要將所有 Entry 的 Domain 設成 0,並且在 DACR 中將所有 D<n> 設定成 "Client"。
<h3>4.3.5 nG (non-Global)</h3>
如果 nG BIT 為 1,表示該欄位為特定 task 所用。於最後 4.4.2 章節有更詳細說明。
<h2>4.4 多工下的 Translation Table</h2>
在多工的環境下 (例如 Linux ),不同的 task 之間需要做頻繁的切換。為了讓他們都能專注在自己的虛擬記憶體、而不需要去在乎是否會和其他人互踩記憶體空間等等的問題,我們需要為每一個 task 建立其專屬的 Translation table。
<h3>4.4.1 Translation Table Base Register 0 and 1</h3>
為每一個 task 都給予一個其專屬的 translation table 會有一個問題,就是各 table 之間會有大量重複的欄位(像是作業系統所使用的記憶體空間在各 Task 之間都是一樣的),這樣不但浪費空間而且當共用的欄位需要修改時,會需要全面得修改。為了避免這樣的情況,Arm 這邊有提供兩個 TTBR - TTBR0 和 TTBR1,以及一個控制此兩個 TTBR 的 Register - [TTBCR (Translation Table Base Control Register)](https://developer.arm.com/documentation/ddi0601/2025-03/AArch32-Registers/TTBCR--Translation-Table-Base-Control-Register?lang=en)。他們都是協處理器 P15 的 C2 暫存器,須由 MCR/MRC 的最後一個參數指定:
```c=
// TTBR0
MCR/MRC p15, 0, Rt, c2, c0, 0
// TTBR1
MCR/MRC p15, 0, Rt, c2, c0, 1
// TTBCR
MCR/MRC p15, 0, Rt, c2, c0, 2
```
TTBR1 用來存取共用記憶體區段的 Translation Table,而 TTBR0 則是用來存去 Task 專屬的記憶體區段 Translation Table。這樣系統在做 context switch 的時候只需要切換 TTBR0 和 ASID (會在 4.4.2 章詳細說明) 就好。
TTBCR 的 N 欄位 (BIT[2:0]) 用來控制 TTBR0 和 TTBR1 的分工,它的值表示當虛擬位置最高位的 N 位 BIT 都是 0 時,Translation table 是使用 TTBR0;相反的話則使用 TTBR1。
舉例來說,如果 TTBCR 的 N 欄位為 4,則所有虛擬位置小於等於 0x0FFFFFFF (256MB)都會使用 TTBR0 來做差找,而 0x10000000 到 0xFFFFFFFF 的空間則會是使用 TTBR1。
<h3>4.4.2 Address Space ID (ASID) </h3>
ASID 位於協處理器 P15 的 c13,它的值為當下 task 的 ID。Translation table entry Attribute 中的 nG (non-Global) BIT 為 1 時,表示該 Entry 為某特定 Task 所用,在 TLB 中就會將該 Entry 加上其 Task 的 ASID 一同記錄起來。在隨後的 TLB 查詢當中,就只會在 ASID 等於當下 ASID 的欄位中做搜尋。
這樣做的好處是,在頻繁的 Context switch 間,不需要去清除 TLB cache,進而提升效率。下圖顯示出因為有 ASID 做區分,所以 TLB 可以存放許多不同 ASID 但是同樣虛擬位置的欄位。

<h1>5. 範例</h1>
這邊提供一個實際的例子來示範如何建立 Translation Table 來將虛擬記憶體 0x20000000 開始的 4MB 空間映射到實體記憶體 0x10000000。
```c=
ldr r5, =0xFFF0000 // Mask for clear attribute bits
// Set TTBR0
ldr r0, __TTBR0_BASE
mcr p15, 0, r0, c2, c0, 0
/*************** Create translation table entries ***************/
ldr r1, 0x20000000 // Virtual Address
// Step 1: Get the translation table index, that is, index = (VA >> 20);
orr r1, r0, r1, LSR #18
bic r1, r1, #0x3
// Step 2: Create the value of the entry
mov r2, 0x10000000 // Phsical Address
and r2, r2, r5 // Clear the attribute bits
ldr r3, =0x0C02 // Set attribute bits:
// APX (BIT[15]) = 0b
// TEX (BIT[14:12]) = 000b
// AP (BIT[11:10]) = 11b
// Domain (BIT[8:5]) = 0000b
// XN (BIT[4]) = 0b
// C (BIT[3]) = 0b
// B (BIT[2]) = 0b
// Entry Type (BIT[1:0]) = 10b
orr r2, r2, r3
// Now r1/r2 is the address/value of the first entry
// Step 3: Write 4 entries.
mov r4, #4
next:
str r2, [r1], #4
add r2, r2, #0x100000
subs r4, r4, #1
cmp r4, #0
bne next
/*****************************************************************/
// Set DACR's D0 to "Client"
mov r0, #0x3
mcr p15, 0, r0, c3, c0, 0
// Ensure all previous commands are done
dsb ish
isb
// Enable MMU
mrc p15, 0, r0, c1, c0, 0
orr r0, r0, #0x1
mcr p15, 0, r0, c1, c0, 0
// Ensure MMU enable immediately
isb
```
<h1>6. 問題</h1>
1. Memory type 中 Strongly-ordered 和 Device 差在哪?
答: 請看 [Arm Programmer's Guide X - Memory Ordering 學習筆記的 2.1 Strongly-Ordered 和 Device Memory](https://hackmd.io/bseubK1WRnGApFmTR2OKiQ?view#21-Strongly-Ordered-%E5%92%8C-Device-Memory)
---
上一篇: [Arm Programmer's Guide VIII - Caches 學習筆記](https://hackmd.io/@uMqav-XESBCsrtZEx_Jpug/Arm_Programmer_Guide_Caches_Study_note)
下一篇: [Arm Programmer's Guide X - Memory Ordering 學習筆記](https://hackmd.io/@uMqav-XESBCsrtZEx_Jpug/Arm_Programmer_Guide_Memory_Ordering_Studying_Note)