參考文獻:《ARM ArchitectureReference Manual ARMv7-A and ARMv7-R edition》
1、MMU
1.1 背景
??早期的內存是比較小的,一般是幾十k,不過相應的程序也是比較小的,這時程序可以直接加載到內存中運行。后來為了支持多個程序的并行,內存中出現了固定分區,在編譯階段將不同程序,劃分在不同的內存區域上。這種方式存在不少問題:一是內存分區大小與程序大小要匹配,二是地址空間無法動態的增長。于是內存動態分區思想便誕生了,內存上線劃出一塊區域給操作系統,然后剩余的內存空間給用戶進程使用,這樣用戶程序所使用的內存空間,跟隨程序大小及數目進行變動。
??不論是靜態分析,還是動態分區,都存在一些問題:進程地址空間安全問題、內存使用效率低。為了能夠讓多程序安全、高效地并行運行,物理內存中需要存放多個程序的代碼及數據,這時虛擬內存便誕生了。不得不說虛擬內存是一個偉大的發明,一方面它讓每個程序認為自己是獨自、連續的使用內存,另一方面,每個程序之間的內存形成了安全隔離,避免程序破壞彼此的內存。
??后來隨著軟件的快速發展,一個程序的大小變得很大,這時物理內存大小跟不上程序大小增加的速度。這樣便不能將整個程序加載到物理內存中,一是物理內存沒有這么大,二是如果將整個程序加載到內存,為了多程序并行,就需要將大量的數據及代碼換入、換出,這導致程序運行效率低下。虛擬內存并沒解決高效使用內存的問題,好在程序運行遵循時間、空間局部性原理,進而出現了分頁機制。分頁機制從根本上解決了高效使用物理內存的問題。每次只需要將幾頁的代碼、數據從磁盤中加載到內存,程序就能正常運行。當程序運行的過程中,需要新的代碼、數據會產生缺頁異常,這些代碼、數據就會從磁盤加載到內存,然后程序從異常恢復正常運行。
??對于支持虛擬內存,分頁機制的系統,處理器直接尋址虛擬地址,這個地址不會直接發給內存控制器,而是先發給內存管理單元(Memory Manager Unit,MMU)。MMU 就是負責將虛擬地址轉換和翻譯成物理地址的一個硬件模塊,其實 MMU 所做的事,完全可以通過 CPU 來實現。為啥還要一個 MMU 硬件模塊呢?就是為了提升虛擬地址到物理地址轉換的速度,減少轉換所消耗的時間。MMU 包含兩個模塊TLB(Translation Lookaside Buffer)和TWU(Table Walk Unit)。TLB 是一個高速緩存,用于緩存頁表轉換的結果,從而縮短頁表查詢的時間。TWU 是一個頁表遍歷模塊,頁表是由操作系統維護在物理內存中,但是頁表的遍歷查詢是由 TWU 完成的,這樣減少對 CPU 資源的消耗。
??虛擬內存及分頁機制的出現,解決了進程地址空間安全性的問題和內存使用效率低的問題,但是也引入了系統性能變差的問題。本來 CPU 可以直接通過訪存執行程序,但是現在引入了虛擬地址到物理地址的轉換。MMU 硬件模塊的出現,就是為了解決這個性能問題。因此,幾 G 運行內存的電腦,可以并行運行幾十G的多程序,讓你在聽歌的同時,能夠并行處理編輯文檔,下載電影,收發郵件等。
1.2 主要功能
- 地址翻譯
- 在用戶訪問內存時,將用戶訪問的虛擬地址翻譯為實際的物理地址,以便 CPU 對實際的物理地址進行訪問。
- 訪問權限控制
- 可以對一些虛擬地址進行訪問權限控制,以便于對用戶程序的訪問權限和范圍進行管理,如代碼段一般設置為只讀,如果有用戶程序對代碼段進行寫操作,系統會觸發異常。
- 引申的物理內存管理
- 對系統的物理內存資源進行管理,為用戶程序提供物理內存的申請、釋放等操作接口。
2、Translation tables
2.1 Linux 中的頁表
??Linux 內核通過多級頁表管理虛擬地址到物理地址的轉換。不同處理器架構可根據需求選擇頁表級數,主要組件包括:
縮寫 | 全稱 | 作用描述 | 典型x86_64位偏移 |
---|---|---|---|
PGD | Page Global Directory | 頂級頁表結構 | 47-39位 |
P4D | Page 4th-level Directory | 第四級目錄(4.11新增) | 未固定 |
PUD | Page Upper Directory | 上層目錄 | 38-30位 |
PMD | Page Middle Directory | 中間目錄 | 29-21位 |
PTE | Page Table Entry | 最終頁表項 | 指向物理頁幀 20-12位 |
Linux 4.11 之前:
- 最大支持4級頁表(PGD→PUD→PMD→PTE)
實際使用級數由 CPU 架構決定:
- 4級:PGD+PUD+PMD+PTE(如x86_64常規4KB頁)
- 3級:PGD+PMD+PTE
- 2級:PGD+PTE (常見的 ARMv7)
Linux 4.11 及之后:
- 引入P4D 級(位于 PGD 和 PUD 之間),擴展至 5 級頁表
- 新增級數應對48位以上地址空間(如 5 級分頁應對 57 位地址)
注意:
PGD、PUD、PMD、PTE 這些,都是 Linux 中的稱呼。在不同 CPU 架構手冊中,叫法可能各有不同。例如,在 ARMv7 中,對于只支持二級頁表的情況下,PGD 稱之為 First-level table,PTE 稱之為 Second-level table。
??在下面的文章中,為了符合 ARM 手冊,將 translation table,翻譯成轉換表,可以理解成,就是頁表的含義。
2.2 ARMv7 中的頁表
ARMv7-A 定義了兩種轉換表格式 :
Short-descriptor format
這是一種基礎格式,是未包含 大物理地址擴展(LPAE) 的實現中唯一支持的格式。它在轉換表中使用了 32 位的描述符條目,并提供了:
- 提供兩級地址轉換
- 32-bit 輸入地址
- 輸出地址可高達 40-bit
- 通過使用 supersections 實現 32 位以上地址的支持,最小內存單位為 16MB
- 支持無訪問域、客戶端域和管理器域
- 32-bit 頁表項
Long-descriptor format
這是一種 可選格式。大型物理地址擴展增加了對這種格式的支持。它在轉化表中使用了64位的描述符條目,并提供了:
- 最多三級地址查找
- 第二級轉換時,輸入地址可以高達40位
- 輸出地址高達40位
- 4KB assignment granularity across the entire PA range.
- 不支持域,所有存儲區都被視為在客戶域中
- 64-bit 位頁表項
- 固定 4kB 的表大小,除非輸入地址被截斷
??本篇文章,皆以常見的 Short-descriptor format 為例進行講解。關于 Long-descriptor format ,有興趣的可以自行閱讀 ARM 手冊相關內容。
轉換表格式,其實就是頁表格式。所謂的 descriptor,描述的就是各級 頁表 的 頁表項
3. Short-descriptor format
3.1 Short-descriptor translation table format descriptors
??短描述符轉換表格式支持以內存段(memory sections)或內存頁(memory pages)為基礎的內存映射
- Spersections: 16MB 的內存塊(memory block)
- Sections: 1MB 的內存塊
- Large pages: 64KB 的內存塊
- Small pages: 4KB 的內存塊
這其中,除了 Small pages,其余僅使用單個 TLB 表項映射大區域內存。
Short-descriptor format 中是否支持 Spersections 是 implementation DEFINED
當使用 Short-descriptor translation table format 時,內存中保留兩級轉換表:
First-level table
一級表存儲一級描述符,每個描述符包含:
- 段(Section)和超級段(Supersection)的基地址與轉換屬性
- 大頁(Large page)或小頁(Small page)的轉換屬性及指向二級表的指針
Second-level tables
二級表存儲二級描述符,包含:
- 大頁(Large page)或小頁(Small page)基地址與轉換屬性
在短描述符格式(Short-descriptor format)中,二級表可稱為頁表(Page tables)
注:每個二級表需占用 1KB 內存空間。一條頁表項 4 字節,共有 2^8 個頁表項
First-level table 在 Linux 中又叫做一級頁表,PGD
Second-level tables 在 Linux 中又叫做二級頁表,PTE
轉換表中的描述符通常分為以下類型:
- 無效條目/故障條目(Invalid or fault entry)
- 頁表條目(Page table entry):指向下一級翻譯表
- 頁條目/段條目(Page or section entry):定義內存訪問屬性
- 保留格式(Reserved format)
從上圖也可以看到,Large pages、 Small pages 才支持二級頁表。Spersections 和 Sections 只支持一級頁表
3.1.1 一級轉換表
最后兩位 bits[1:0] 決定描述符類型
- 0b00, Invalid
- 0b01, Page table
- 0b10, Section or Supersection
- 0b11, Section or Supersection, if the implementation supports the PXN attribute
- 0b11, Reserved, UNK/SBZP, if the implementation does not support the PXN attribute
舉個例子,當操作系統全部使用 Section 作為一級頁表中的最小尋址單位時,共 12 位地址表示。因為 Section 不涉及到二級頁表,所以頁表項最多為 212。而頁內偏移為 [19:0] ,所以頁大小為 220。綜上,可以表示的最大虛擬空間大小為:212 * 220
3.1.2 二級轉換表
最后兩位 bits[1:0] 決定描述符類型
- 0b00, Invalid
- 0b01, Large page
- 0b1x, Small page
舉個例子,使用 Small Page 作為二級頁表的最小尋址單位時,共 20 位地址表示,所能表示的最大虛擬地址范圍就是 220* 212 = 4GB。頁內偏移為 [11:0] ,所以頁大小為 212
3.2 Short-descriptor translation table format descriptors 中的內存屬性
??把內存屬性單獨拿出一個章節來講,是因為內存屬性很重要.。
- TEX[2:0], C, B
- 內存區域屬性位
- 詳見第 5 章節
- XN bit
- 全局不可執行。如果執行,會觸發 Permission fault
- PXN bit, when supported
- Privileged Execute-Never,特權模式不可執行。在支持的情況下,PXN 位決定處理器在 PL1 特權級時是否可執行該內存區域的代碼。如果執行,會觸發 Permission fault
- NS bit
- NS = Non-Secure,只有在 啟用了 TrustZone 安全擴展 的系統中才有意義
- 只會出現在一級頁表項中
- 詳見 3.4 章節
- Domain
- Domains 是一種粗粒度的內存訪問控制機制,屬于 ARMv7 MMU 的一部分
- 詳見 3.3 章節
- AP[2], AP[1:0]
- 訪問權限位
- 詳見 3.5 章節
- S bit
- 可共享位。確定尋址區域是否為可共享內存
- nG bit
- 非全局位。確定如何在 TLB 中標記轉換
- Bit[18], when bits[1:0] indicate a Section or Supersection descriptor
- 0 Descriptor is for a Section.
- 1 Descriptor is for a Supersection
3.3 Domains
在 ARMv7 的 VMSA 中,僅存在于 Short-descriptor format:
- 總共有 16 個 Domains(編號 0 ~ 15)
- 每個 Section/Page 在頁表中被指定屬于哪個 Domain(用 4-bit 域表示)(不支持 Supersections)
- 僅有一級頁表條目有 Domains 域,二級轉換表條目從父級一級頁表條目繼承域設置
- CPU 中有一個 DACR(Domain Access Control Register),用于配置每個 Domain 的訪問權限
DACR
是一個 32-bit 寄存器,每兩個 bit 控制一個 domain 的訪問權限。每個域的訪問權限可以設置為:
值 | 含義 | 解釋 |
---|---|---|
0b00 | No access | 訪問該 domain 的內存會導致 fault |
0b01 | Client | 根據頁表中的 AP bits 進行訪問權限檢查(標準權限檢查方式) |
0b11 | Manager | 不檢查訪問權限,直接允許訪問(相當于繞過 AP 位) |
0b10 | 保留(未使用) | 訪問該 domain 的內存會導致 fault |
? 工作原理總結
訪問內存時,ARM 的 MMU 進行如下步驟:
- 使用頁表將虛擬地址翻譯為物理地址,并獲得該頁屬于哪個 Domain(通過頁表項的 bits[8:5])
- 查 DACR 寄存器,查看該 Domain 的訪問權限設定
- 根據權限設定決定行為:
- No access → 拋出異常
- Client → 繼續檢查頁表中的 AP(Access Permission)位
- Manager → 直接允許訪問(忽略 AP 位)
Domains 域現在不常用了。粒度太粗、管理復雜,ARMv8+ 已廢棄 Domain 概念,使用更加細粒度的 EL 權限模型、權限表(PTE) 來管理訪問權限。
在 ARMv7 的 VMSA 中,Domains 是 MMU 機制的一部分,不能繞過或關閉。通常可以將所有 domain 設置為 Client,配合合理的頁表 AP
3.4 Control of Secure or Non-secure memory access
??在 ARMv7 架構中,針對安全(Secure)與非安全(Non-secure)PL1&0 階段的第 1 階段地址轉換,其轉換表基址寄存器(TTBR0、TTBR1)及轉換表基址控制寄存器(TTBCR)均采用安全與非安全 雙 bank 設計。處理器執行內存訪問時所處的安全狀態(Security state)將自動選擇對應版本的寄存器組。
??當 CPU 當前處于 Secure 狀態(比如運行 Secure OS 或 TrustZone secure world 中的代碼)時:頁表中設置的 NS 位會影響這個訪問是訪問到:
- Secure memory(NS=0)
- 還是 Non-Secure memory(NS=1)
??當 CPU 當前處于 Non-secure 狀態時(比如運行 Linux 等普通 OS):頁表中設置的 NS 位根本不起作用,會被忽略。因為 Non-secure world 不能訪問 Secure memory。
3.5 Memory access control
??在轉換表描述符中,訪問權限位(Access permission bits)用于控制對相應內存區域的訪問。短描述符轉換表格式(Short-descriptor translation table format)支持兩種定義訪問權限的方式:
- 三比特模式(AP[2:0]):
- 通過 AP[2:0] 三個比特位定義訪問權限。
- 兩比特模式(AP[2:1] + 訪問標志位):
- 通過 AP[2:1] 兩個比特位定義訪問權限,
- AP[0] 可作為訪問標志(Access flag)使用。
系統控制寄存器 SCTLR.AFE
用于選擇訪問權限的配置模式:
- 若將該位置 1(啟用訪問標志功能),則自動選擇 AP[2:1] 定義訪問權限的模式。
3.6 轉換表的工作流程
以 Small page 為例:
- 首先輸入的虛擬地址,可以大致分為 3 部分
- 一級頁表偏移
- 二級頁表偏移
- 頁內偏移
- 根據輸入地址的范圍(也就是 N 的取值,這一塊可以先閱讀第 4 章節),找到頁表基址(TTBR0 or TTBR1)
- 根據 頁表基址 + 一級頁表偏移,找到一級頁表的 “頁表項描述符”
- 頁表基址,實際上就是 PGD 表的基址;一級頁表偏移指的就是 PGD 表中的某個頁表項
- 根據上一步找到 “一級頁表項描述符” 中包含的二級頁表的基址[31:10] + 二級頁表偏移,找到二級頁表的 “頁表項描述符”
- 二級頁表的基址,實際上就是 PTE 表的基址;二級頁表偏移指的就是 PTE 表中的某個頁表項
- 在二級頁表項描述符中,就包含了最終的物理地址的 [31:12],再加上 頁內偏移,最終就找到了物理地址
- 二級頁表項描述符,指的就是 PTE 表中的某個頁表項
4、關于頁表基址 TTBR
?? ARMv7 中,有兩個存放頁表(一級頁表)基地址的寄存器,TTBR0 和 TTBR1。那 MMU 進行地址翻譯(translation table walk)的時候到底是選擇哪一個寄存器的值作為基地址呢?
?? 那么這個時候就需要用到 TTBCR 寄存器,這個寄存器的格式如下圖:
其中最后 [2-0] 位(值為N)決定了用 TTBR0 還是 TTBR1,手冊里面已經說的很清楚了,這里直接貼出來。
- 當 N == 0 是,使用 TTBR0 作為基址。關閉/禁用 TTBR1 作為基址
- 當 N>0 時,如果給的虛擬地址 [31:32-N] 位都是 0,那么用 TTBR0,一旦這其中的某一位是 1 了,就用 TTBR1。
- 又因為 N 的取值只有 0 到 7,所以每個 N 的取值都對應了一個用 TTBR0 或者 TTBR1 的分界線。
- 比如當 N=2 時,如果給的虛擬地址第 [31:30] 位為 0,即不超過 0x40000000,那么就用 TTBR0,一旦等于或者超過 0x40000000 那么就用 TTBR1。
下面還有一張 N 值不同時,對應的地址分界線圖:
理論上,一個頁表基址寄存器也是能正常工作,但兩個可以帶來更好的性能和系統隔離性
為什么需要兩個基址寄存器?
? 好處 1:用戶態和內核態分離
- 操作系統常常希望:
- 用戶進程只能訪問用戶空間
- 內核可以訪問全部空間
- 有了兩個頁表:
- 切換進程時,只需切換 TTBR0(用戶空間部分)
- TTBR1(內核空間頁表)不變,提升性能并確保安全
? 好處 2:性能優化
- 避免頻繁清空整個 TLB(Translation Lookaside Buffer):
- 進程切換時只換 TTBR0 → 用戶空間換了
- TTBR1 → 內核頁表不變,TLB 中內核頁表項仍有效
? 好處 3:內核共享
- 所有進程共用一套內核映射(TTBR1)
- 每個進程擁有自己獨立的用戶映射(TTBR0)
5、關于內存屬性
??除輸出地址外,指向內存頁或區域的頁表項通常還包含用于定義目標內存屬性字段(Memory Region Attribute Fields),用于控制:
- 內存類型(如Normal或Device內存)
- 緩存訪問策略(如Write-Back、Write-Through)
- 可共享性(Shareable),即多核間的一致性(Coherency)
為深入理解內存屬性字段的配置機制,需區分以下兩種模式:
- Short-descriptor translation table format (無 TEX 重映射)
- 直接通過TEX[2:0]、C(Cacheable)、B(Bufferable)位組合定義內存行為,適用于大多數標準場景
- Short-descriptor translation table format (啟用 TEX 重映射)
- 通過 CP15 寄存器重映射 TEX 位語義,支持更靈活的緩存策略定制,常見于定制化硬件設計
5.1 without TEX remap
??使用 Short-descriptor translation table formats 時,可以使用 SCTLR.TRE
禁用 TEX 重映射。
(without TEX remap 場景下的的 Shareability 與 S 位)
轉換表項(Translation Table Entry)中還包含一個 S 位(Shareable位),其作用如下:
- 若表項指向設備內存(Device)或強序內存(Strongly-ordered):
- 該位被忽略(無論S=0或1均不生效)
- 若表項指向普通內存(Normal Memory):
- 該位決定內存區域的可共享性(Shareability):
- S=0:該普通內存區域為非可共享(Non-shareable)。
- S=1:該普通內存區域為可共享(Shareable)
- 該位決定內存區域的可共享性(Shareability):
5.2 with TEX remap
??當使用短描述符轉換表格式(Short-descriptor translation table formats)時,若將系統控制寄存器 SCTLR.TRE
置為1,則啟用 TEX 重映射(TEX Remap)功能。在此配置下:
- 軟件配置要求
- 定義轉換表的軟件必須編程設置
PRRR
(Primary Region Remap Register)和NMRR
(Normal Memory Remap Register),以指定 7 種可能的內存區域屬性 - 轉換表描述符中的 TEX[0]、C(Cacheable)和 B(Bufferable)位通過索引
PRRR
和NMRR
來定義內存區域屬性。 - 硬件不會使用 TEX[2:1] 位
- 定義轉換表的軟件必須編程設置
- TEX 重映射生效時的行為
- 對于 TEX[0]、C 和 B 位的 8 種可能組合中的 7 種,其內存區域屬性由
PRRR
和NMRR
的字段定義(如本節所述)。 - 第 8 種組合的含義由具體實現定義(IMPLEMENTATION DEFINED)。
PRRR
中的 4 個比特位用于定義該區域是否可共享(Shareable)
- 對于 TEX[0]、C 和 B 位的 8 種可能組合中的 7 種,其內存區域屬性由
- 寄存器字段映射關系
- 對于轉換表項中TEX[0]、C 和 B 位的所有可能編碼組合,表 B3-12 列出了
PRRR
和NMRR
寄存器中描述內存區域屬性的對應字段。
- 對于轉換表項中TEX[0]、C 和 B 位的所有可能編碼組合,表 B3-12 列出了
?? 乍一看,有點迷糊,這塊不太好理解。總結來說,在 without TEX remap 的場景下,在 PTE 中直接寫 TEX[2:0]、B、C 對應的 bit 位,就可以控制頁面的內存區域屬性。但是在 with TEX remap 情況下,TEX、B、C 的值不再直接表示區域內存屬性。所有可能的區域內存屬性的值,都在 PRRR
和 NMRR
寄存器中。而 TEX、B、C 三個 bit 組成的 8 種組合,就是在這兩個寄存器中的偏移。
PRRR, Primary Region Remap Register, VMSA
所有 TEX[0]、B、C 可能的組合如下:對應的 n 是幾,使用的就是 NOS(n) + TR(n) 的組合
關于 PRRR
寄存器,以下是詳細解釋:
- NOS 位是控制內存區域的可共享屬性。
- NS1, bit[19]
- NS0, bit[18]
- DS1, bit[17]
- DS0, bit[16]
?? 這些位控制的是 Normal memory 和 Device memory 的共享性(shareability)屬性,也就是說,它們不是控制緩存方式,而是控制 是否是 shareable 類型。
名稱 | 控制的情況 | 含義(bit 值) |
---|---|---|
NS0 (bit 18) | 頁表中 S=0 且是 Normal memory | 0 ? Non-shareable 1 ? Shareable |
NS1 (bit 19) | 頁表中 S=1 且是 Normal memory | 0 ? Non-shareable 1 ? Shareable |
NS0 (bit 18) | 類似邏輯,作用于 Device memory,S=0/S=1 映射 |
- TR 位用于控制內存類型
NMRR, Normal Memory Remap Register, VMSA
關于 NMRR
寄存器這里不再詳細解釋了,有興趣的可以自行研究。
6、Linux 下的頁表映射
??上面講的都是理論,我們對應到 Linux 源碼中,看看內存區域屬性是如何配置的。我們查看下 ARM 架構下的 ioremap 的實現。
arch\arm\mm\ioremap.c:
void __iomem *ioremap(resource_size_t res_cookie, size_t size)
{return arch_ioremap_caller(res_cookie, size, MT_DEVICE,__builtin_return_address(0));
}
EXPORT_SYMBOL(ioremap);void __iomem *ioremap_cache(resource_size_t res_cookie, size_t size)
{return arch_ioremap_caller(res_cookie, size, MT_DEVICE_CACHED,__builtin_return_address(0));
}
EXPORT_SYMBOL(ioremap_cache);void __iomem *ioremap_wc(resource_size_t res_cookie, size_t size)
{return arch_ioremap_caller(res_cookie, size, MT_DEVICE_WC,__builtin_return_address(0));
}
??關于 MT_DEVICE
、MT_DEVICE_CACHED
、MT_DEVICE_WC
,就是內存區域屬性,最終會被翻譯成我們在第 5 章節所講的 TEX、B、C 的值寫到 PTE 頁表項中。
arch\arm\mm\mmu.c
static struct mem_type mem_types[] __ro_after_init = {[MT_DEVICE] = { /* Strongly ordered / ARMv6 shared device */.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED |L_PTE_SHARED,.prot_l1 = PMD_TYPE_TABLE,.prot_sect = PROT_SECT_DEVICE | PMD_SECT_S,.domain = DOMAIN_IO,},[MT_DEVICE_NONSHARED] = { /* ARMv6 non-shared device */.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_NONSHARED,.prot_l1 = PMD_TYPE_TABLE,.prot_sect = PROT_SECT_DEVICE,.domain = DOMAIN_IO,},[MT_DEVICE_CACHED] = { /* ioremap_cache */.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_CACHED,.prot_l1 = PMD_TYPE_TABLE,.prot_sect = PROT_SECT_DEVICE | PMD_SECT_WB,.domain = DOMAIN_IO,},[MT_DEVICE_WC] = { /* ioremap_wc */.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_WC,.prot_l1 = PMD_TYPE_TABLE,.prot_sect = PROT_SECT_DEVICE,.domain = DOMAIN_IO,},............
}
上表中提到的 L_PTE_MT_DEV_SHARED
、L_PTE_SHARED
頁表項 PTE 相關的宏,定義在 pgtable-2level.h 頭文件中:
arch\arm\include\asm\pgtable-2level.h
/** These are the memory types, defined to be compatible with* pre-ARMv6 CPUs cacheable and bufferable bits: n/a,n/a,C,B* ARMv6+ without TEX remapping, they are a table index.* ARMv6+ with TEX remapping, they correspond to n/a,TEX(0),C,B** MT type Pre-ARMv6 ARMv6+ type / cacheable status* UNCACHED Uncached Strongly ordered* BUFFERABLE Bufferable Normal memory / non-cacheable* WRITETHROUGH Writethrough Normal memory / write through* WRITEBACK Writeback Normal memory / write back, read alloc* MINICACHE Minicache N/A* WRITEALLOC Writeback Normal memory / write back, write alloc* DEV_SHARED Uncached Device memory (shared)* DEV_NONSHARED Uncached Device memory (non-shared)* DEV_WC Bufferable Normal memory / non-cacheable* DEV_CACHED Writeback Normal memory / write back, read alloc* VECTORS Variable Normal memory / variable** All normal memory mappings have the following properties:* - reads can be repeated with no side effects* - repeated reads return the last value written* - reads can fetch additional locations without side effects* - writes can be repeated (in certain cases) with no side effects* - writes can be merged before accessing the target* - unaligned accesses can be supported** All device mappings have the following properties:* - no access speculation* - no repetition (eg, on return from an exception)* - number, order and size of accesses are maintained* - unaligned accesses are "unpredictable"*/
#define L_PTE_MT_UNCACHED (_AT(pteval_t, 0x00) << 2) /* 0000 */
#define L_PTE_MT_BUFFERABLE (_AT(pteval_t, 0x01) << 2) /* 0001 */
#define L_PTE_MT_WRITETHROUGH (_AT(pteval_t, 0x02) << 2) /* 0010 */
#define L_PTE_MT_WRITEBACK (_AT(pteval_t, 0x03) << 2) /* 0011 */
#define L_PTE_MT_MINICACHE (_AT(pteval_t, 0x06) << 2) /* 0110 (sa1100, xscale) */
#define L_PTE_MT_WRITEALLOC (_AT(pteval_t, 0x07) << 2) /* 0111 */
#define L_PTE_MT_DEV_SHARED (_AT(pteval_t, 0x04) << 2) /* 0100 */
#define L_PTE_MT_DEV_NONSHARED (_AT(pteval_t, 0x0c) << 2) /* 1100 */
#define L_PTE_MT_DEV_WC (_AT(pteval_t, 0x09) << 2) /* 1001 */
#define L_PTE_MT_DEV_CACHED (_AT(pteval_t, 0x0b) << 2) /* 1011 */
#define L_PTE_MT_VECTORS (_AT(pteval_t, 0x0f) << 2) /* 1111 */
#define L_PTE_MT_MASK (_AT(pteval_t, 0x0f) << 2)
對于 ARMv7 來說,我們只需要關注上表的后三位。分別對應的就是 TEX[0]、C、B。
6.1 關于 PRRR 和 NMRR 寄存器的值
arch\arm\mm\proc-v7-2level.S
/** Memory region attributes with SCTLR.TRE=1** n = TEX[0],C,B* TR = PRRR[2n+1:2n] - memory type* IR = NMRR[2n+1:2n] - inner cacheable property* OR = NMRR[2n+17:2n+16] - outer cacheable property** n TR IR OR* UNCACHED 000 00* BUFFERABLE 001 10 00 00* WRITETHROUGH 010 10 10 10* WRITEBACK 011 10 11 11* reserved 110* WRITEALLOC 111 10 01 01* DEV_SHARED 100 01* DEV_NONSHARED 100 01* DEV_WC 001 10* DEV_CACHED 011 10** Other attributes:** DS0 = PRRR[16] = 0 - device shareable property* DS1 = PRRR[17] = 1 - device shareable property* NS0 = PRRR[18] = 0 - normal shareable property* NS1 = PRRR[19] = 1 - normal shareable property* NOS = PRRR[24+n] = 1 - not outer shareable*/
.equ PRRR, 0xff0a81a8 // 初始化了 PRRR 寄存器的值
.equ NMRR, 0x40e040e0 // 初始化了 NMRR 寄存器的值
對應的,將 PRRR
翻譯結果如下:
寄存器偏移 | 對應值 | 屬性 |
---|---|---|
TR0 | 00 | Strongly-ordered |
TR1 | 10 | Normal memory |
TR2 | 10 | Normal memory |
TR3 | 10 | Normal memory |
TR4 | 01 | Device |
TR5 | 00 | Strongly-ordered |
TR6 | 00 | Strongly-ordered |
TR7 | 10 | Normal memory |
寄存器偏移 | 對應值 | 屬性 |
---|---|---|
NOS0 | 1 | Memory region is Inner Shareable |
NOS1 | 1 | Memory region is Inner Shareable |
NOS2 | 1 | Memory region is Inner Shareable |
NOS3 | 1 | Memory region is Inner Shareable |
NOS4 | 1 | Memory region is Inner Shareable |
NOS5 | 1 | Memory region is Inner Shareable |
NOS6 | 1 | Memory region is Inner Shareable |
NOS7 | 1 | Memory region is Inner Shareable |
NMRR
翻譯結果如下:
寄存器偏移 | 對應值 | 屬性 |
---|---|---|
OR0 | 00 | Region is Non-cacheable |
OR1 | 00 | Region is Non-cacheable |
OR2 | 01 | Region is Write-Back, Write-Allocate |
OR3 | 11 | Region is Write-Back, no Write-Allocate |
OR4 | 00 | Region is Non-cacheable |
OR5 | 00 | Region is Non-cacheable |
OR6 | 00 | Region is Non-cacheable |
OR7 | 01 | Region is Write-Back, Write-Allocate |
6.2 案例
函數調用關系鏈:
ioremap+-> arch_ioremap_caller+-> __arm_ioremap_pfn_caller+-> get_mem_type+-> ioremap_page_range+-> kmsan_ioremap_page_range+->__vmap_pages_range_noflush+->vmap_range_noflush+->vmap_p4d_range+->vmap_pud_range+->vmap_pmd_range+->vmap_pte_range+->set_pte_at+->cpu_v7_set_pte_ext
總結來說,在 ARMv7 架構下,調用 ioremap 接口,默認會將地址映射成 MT_DEVICE
屬性。
void __iomem *ioremap(resource_size_t res_cookie, size_t size)
{return arch_ioremap_caller(res_cookie, size, MT_DEVICE,__builtin_return_address(0));
}
EXPORT_SYMBOL(ioremap);
在調用 get_mem_type
接口時,會將 MT_DEVICE
轉換成架構相關的內存區域屬性相關的宏 :
PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED | L_PTE_SHARED
static struct mem_type mem_types[] __ro_after_init = {[MT_DEVICE] = { /* Strongly ordered / ARMv6 shared device */.prot_pte = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED |L_PTE_SHARED,.prot_l1 = PMD_TYPE_TABLE,.prot_sect = PROT_SECT_DEVICE | PMD_SECT_S,.domain = DOMAIN_IO,},............
}const struct mem_type *get_mem_type(unsigned int type)
{return type < ARRAY_SIZE(mem_types) ? &mem_types[type] : NULL;
}
而這其中,和硬件相關的就是 PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED
#define L_PTE_SHARED (_AT(pteval_t, 1) << 10) /* shared(v6), coherent(xsc3) */#define L_PTE_MT_DEV_SHARED (_AT(pteval_t, 0x04) << 2) /* 0100 */
以 L_PTE_MT_DEV_SHARED
宏為例,介紹如何通過這個宏,去控制內存區域屬性的。
- 對應的 TEX[0]、C、B,組成的 index 值為 0x4
- 對應
RRR
、NMRR
寄存器中,使用的就是- NOS4 + TR4 + OR4 + IR4 的組合、
最終,會調用 set_pte_at
接口,將這些宏設置到 PTE 頁表項中。
該函數的三個入參:
- r0: pte 頁表項的地址
- r1: Linux 版二級頁表項的內容(也就是
PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED | L_PTE_SHARED
這些值的組合) - r2: 通常是 0,除非需要特殊設置
- 輸出 r3:硬件頁表項(詳情見 6.3 章節)
ENTRY(cpu_v7_set_pte_ext)
#ifdef CONFIG_MMUstr r1, [r0] @ linux version //把 r1(linux 的 PTE)存入 r0 地址bic r3, r1, #0x000003f0 //先清除 r1 的 bit[9:4](通常為 memory type / cache屬性)bic r3, r3, #PTE_TYPE_MASK //然后再清除最低 2 位(bit[1:0],即頁表項類型 Small Page / Section)orr r3, r3, r2 //將擴展屬性(ext)合并進 r3orr r3, r3, #PTE_EXT_AP0 | 2 //設置 AP0 位(Access permission)和最低兩位為 0b10,表示 Small Pagetst r1, #1 << 4 //如果原始 PTE 的 bit4 被置位,則設置硬件頁表項的 TEX=1orrne r3, r3, #PTE_EXT_TEX(1)eor r1, r1, #L_PTE_DIRTYtst r1, #L_PTE_RDONLY | L_PTE_DIRTYorrne r3, r3, #PTE_EXT_APX //根據頁表中的只讀和臟頁標志,設置 APX(Access Permission Extension)位tst r1, #L_PTE_USERorrne r3, r3, #PTE_EXT_AP1 //如果頁表設置了 user-mode 訪問權限,設置 AP1(即允許用戶空間訪問)tst r1, #L_PTE_XNorrne r3, r3, #PTE_EXT_XN //設置執行禁止位(XN)tst r1, #L_PTE_YOUNGtstne r1, #L_PTE_VALIDeorne r1, r1, #L_PTE_NONEtstne r1, #L_PTE_NONEmoveq r3, #0 //這段邏輯是做 PTE 的“有效性”判斷:如果不是 Young 或 Valid,或者被標記為 None(無效項),則把 r3 清零(頁表項無效)ARM( str r3, [r0, #2048]! ) //真正的“硬件頁表項”是寫在 r0 + 2048 字節偏移處!THUMB( add r0, r0, #2048 )THUMB( str r3, [r0] )ALT_SMP(W(nop))ALT_UP (mcr p15, 0, r0, c7, c10, 1) @ flush_pte
#endifbx lr
ENDPROC(cpu_v7_set_pte_ext)
6.3 硬件頁表項和軟件頁表項
??在 ARM 架構下,尤其是 Linux 運行于 ARMv6/v7 等 MMU 支持的平臺時,頁表(Page Table)是內存管理的核心組件。為了兼顧內核的抽象管理與硬件 MMU 的訪問需求,Linux 使用了“雙版本頁表”的設計思路,即:
- 軟件版本(Software Version)
- 硬件版本(Hardware Version)
??這兩者雖共享數據結構和邏輯關聯,但服務于不同的對象和目的,理解它們的區別對于調試頁表異常、實現頁表擴展、或閱讀內核代碼都至關重要。
軟件版本頁表(Software Version)
軟件版本是 Linux 內核自己維護和使用的頁表項,它主要用于:
- 內核自身管理的權限與狀態位(如 Dirty、Accessed、Writeback)
- Linux 的抽象內存屬性(如 L_PTE_DIRTY, L_PTE_YOUNG, L_PTE_RDONLY 等)
- 與用戶空間接口(如 mprotect, mmap)之間的語義協同
這些軟件位通常嵌入在標準頁表項結構中(如 pteval_t),但并不一定直接被 ARM MMU 硬件識別。在 ARM Linux 中,這些位通過宏定義編碼進 PTE 中,僅用于 Linux 內核邏輯判斷,硬件則會忽略它們。
軟件頁表項位通常保存在:
- pte_t 類型的結構體中
- 頁表內存中的 [r0] 位置(見 cpu_v7_set_pte_ext 中第一條 str r1, [r0])
硬件版本頁表(Hardware Version)
硬件版本是寫入到 ARM MMU 實際訪問的頁表內存地址 中的內容。它必須符合 ARM 架構定義的格式,包含如下內容:
- 頁表項類型(small page, section, supersection 等)
- 權限位(AP[2:0], APX)
- 緩存控制位(TEX[2:0], C, B)
- 共享屬性(S, nG 等)
- 執行權限(XN)
其實就是我們上面所講到的一些 ARM 寄存器相關的知識
在執行頁表設置時,Linux 會將軟件版本的頁表項翻譯成硬件格式并寫入對應物理地址供 MMU 使用。
硬件頁表項最終保存在:
- 頁表物理頁中,通常偏移 +2048 字節
- 通過 cpu_v7_set_pte_ext 函數中 str r3, [r0, #2048]! 寫入
arch\arm\include\asm\pgtable-2level.h 中的宏,對于硬件版本和軟件版本有很好的體現:
/** "Linux" PTE definitions.** We keep two sets of PTEs - the hardware and the linux version.* This allows greater flexibility in the way we map the Linux bits* onto the hardware tables, and allows us to have YOUNG and DIRTY* bits.** The PTE table pointer refers to the hardware entries; the "Linux"* entries are stored 1024 bytes below.*/
#define L_PTE_VALID (_AT(pteval_t, 1) << 0) /* Valid */
#define L_PTE_PRESENT (_AT(pteval_t, 1) << 0)
#define L_PTE_YOUNG (_AT(pteval_t, 1) << 1)
#define L_PTE_DIRTY (_AT(pteval_t, 1) << 6)
#define L_PTE_RDONLY (_AT(pteval_t, 1) << 7)
#define L_PTE_USER (_AT(pteval_t, 1) << 8)
#define L_PTE_XN (_AT(pteval_t, 1) << 9)
#define L_PTE_SHARED (_AT(pteval_t, 1) << 10) /* shared(v6), coherent(xsc3) */
#define L_PTE_NONE (_AT(pteval_t, 1) << 11)//以下都是硬件版本,上面都講過
/** These are the memory types, defined to be compatible with* pre-ARMv6 CPUs cacheable and bufferable bits: n/a,n/a,C,B* ARMv6+ without TEX remapping, they are a table index.* ARMv6+ with TEX remapping, they correspond to n/a,TEX(0),C,B** MT type Pre-ARMv6 ARMv6+ type / cacheable status* UNCACHED Uncached Strongly ordered* BUFFERABLE Bufferable Normal memory / non-cacheable* WRITETHROUGH Writethrough Normal memory / write through* WRITEBACK Writeback Normal memory / write back, read alloc* MINICACHE Minicache N/A* WRITEALLOC Writeback Normal memory / write back, write alloc* DEV_SHARED Uncached Device memory (shared)* DEV_NONSHARED Uncached Device memory (non-shared)* DEV_WC Bufferable Normal memory / non-cacheable* DEV_CACHED Writeback Normal memory / write back, read alloc* VECTORS Variable Normal memory / variable** All normal memory mappings have the following properties:* - reads can be repeated with no side effects* - repeated reads return the last value written* - reads can fetch additional locations without side effects* - writes can be repeated (in certain cases) with no side effects* - writes can be merged before accessing the target* - unaligned accesses can be supported** All device mappings have the following properties:* - no access speculation* - no repetition (eg, on return from an exception)* - number, order and size of accesses are maintained* - unaligned accesses are "unpredictable"*/
#define L_PTE_MT_UNCACHED (_AT(pteval_t, 0x00) << 2) /* 0000 */
#define L_PTE_MT_BUFFERABLE (_AT(pteval_t, 0x01) << 2) /* 0001 */
#define L_PTE_MT_WRITETHROUGH (_AT(pteval_t, 0x02) << 2) /* 0010 */
#define L_PTE_MT_WRITEBACK (_AT(pteval_t, 0x03) << 2) /* 0011 */
#define L_PTE_MT_MINICACHE (_AT(pteval_t, 0x06) << 2) /* 0110 (sa1100, xscale) */
#define L_PTE_MT_WRITEALLOC (_AT(pteval_t, 0x07) << 2) /* 0111 */
#define L_PTE_MT_DEV_SHARED (_AT(pteval_t, 0x04) << 2) /* 0100 */
#define L_PTE_MT_DEV_NONSHARED (_AT(pteval_t, 0x0c) << 2) /* 1100 */
#define L_PTE_MT_DEV_WC (_AT(pteval_t, 0x09) << 2) /* 1001 */
#define L_PTE_MT_DEV_CACHED (_AT(pteval_t, 0x0b) << 2) /* 1011 */
#define L_PTE_MT_VECTORS (_AT(pteval_t, 0x0f) << 2) /* 1111 */
#define L_PTE_MT_MASK (_AT(pteval_t, 0x0f) << 2)
為什么需要兩個版本?
- 可擴展性:軟件頁表項可以攜帶更多信息,如“是否訪問過”、“是否臟”等,硬件頁表項不一定有足夠位支持這些信息
- 兼容性:Linux 在不同架構上運行時,仍可以通過抽象的軟件表示層實現統一邏輯
- 性能調優:部分頁表標志如 young/old, dirty 可由軟件策略控制,而不依賴硬件