轉自:https://www.jianshu.com/p/ef1e93e9d65b
一、前言
在 嵌入式Linux 開發中,往往會聽到 MMU 這個詞,但大多數情況下並不會去了解它,因為操作系統已經做好了關於 MMU 的一切操作,我們只需要在操作系統的框架下直接使用即可。但了解 MMU 有助於幫助我們理解操作系統,理解進程等,讓我們對 嵌入式Linux 的理解上升一個層次。本文將簡單地講述一下關於 MMU 的基本信息。
注意:本文將按照ARMv7的二級頁表映射進行講述
二、MMU
2.1 MMU基本信息
MMU 全稱為 Memory Management Unit,即 內存管理單元。在 帶有MMU的嵌入式Linux 中,CPU 訪問的地址都是 虛擬地址,而 MMU 負責將程序中 代碼或數據 的 虛擬地址 翻譯為 物理地址,以便程序訪問內存。
在執行操作時,MMU 會自動轉換 CPU發出的虛擬地址,無法人工進行操作,只需要配置好 MMU 相關屬性即可。
虛擬地址 是在 編譯和鏈接 時定義的,可以簡單地理解為 由鏈接器和鏈接器腳本 指定虛擬地址。
除了 翻譯虛擬地址,MMU 還可以配置 內存區域 的各項配置,如內存區域的訪問權限,內存區域是否使能cache等功能。
總結 MMU 的功能,如下:
- 翻譯虛擬地址
- 配置內存區域的相關屬性
2.2 MMU基本概念
看到 MMU 的相關文章時,總會提及幾個概念如 頁,頁框(頁幀),頁表,頁表項,TLB等等,下面我們逐個拆分來講述。
2.2.1 頁
MMU 管理 虛擬地址空間 時,是按照 頁 為單位來進行管理。在 ARMv7 的 MMU ,頁大小 一共有 16M(Super Section)、1M(Section) 、64K(Large Page) 4K(Page)。頁大小 可以通過 協處理器CP15 進行配置,越小的頁意味着內存的顆粒度越小,內存使用時的浪費會越小,但也意味着使用的TLB行越多。越大的也內存的顆粒度月大,內存的使用浪費也可能月大,但使用的TLB行越少。比如只需要申請 7K 大小的 物理內存,如果使用 7K大小 的內存,我們可以分配 2 個 4K頁,如果分配 64K的大頁,則浪費的空間就比較大。
2.2.2 頁框
因為 虛擬地址空間 需要有所對應的 物理地址,這樣才能在 虛擬地址 中存儲數據。所以 MMU 管理 物理地址空間 時,按照 頁幀 為單位進行管理。其大小分為 64K 和 4K。一段 虛擬地址空間 有可能存在着多個 頁,這些 頁 對應着多個 頁幀。
按照筆者理解,頁 和 頁幀 是 不同地址空間下的關於內存空間大小的概念。
2.2.3 頁表及頁表項
MMU 在進行 地址轉換 時,需要一些信息,存放這些信息的就是 頁表。每個 頁表 的最小單位就是 頁表項。
頁表 存儲在 物理地址空間 中,且一個 頁表項 對應着一個 頁。
在 切換頁表 時,通過將 頁表的物理首地址 設置到 協處理器CP15 中的 TTBR寄存器(Translation Table Base Register)。此后 MMU 會通過該地址自動去 物理地址空間 中找到對應的 頁表,從而完成 虛擬地址到物理地址的映射。
在不考慮 TLB 和 多級頁表 的情況下,可以簡單地如下圖所示:

2.2.4 TLB
TLB 全程為 Translation Lookaside Buffer,即 旁路轉換緩沖。它是 MMU 的專屬 全相聯cache,用於臨時存放 虛擬地址到物理地址映射 所需要的信息。
下面按照步驟說明 TLB 的作用:
- CPU 訪問 虛擬地址 到 MMU。
- MMU 根據規則(規則在下文講述)查看 虛擬地址 是否在 TLB 中。
- 如果在 TLB 中,則稱為 TLB命中。從 TLB 中直接獲取 物理地址 對內存進行訪問
- 如果不在 TLB 中,則稱為 TLB失效。此時 MMU 將進行 translation table walking,即通過 訪問頁表來獲取 物理地址。並將該 虛擬地址 的信息存入 TLB,以便下次使用。
值得注意的是:ARM架構的TLB只存儲有效的頁表項,對於無效的頁表項TLB並不會存儲
TLB 由許多 TLB行 組成,如下圖所示:

TLB行 由 3個 部分組成,分別為 標簽、 ASID 和 描述符
- 標簽:該部分由 虛擬地址的一部分bit 組成,MMU 通過將 虛擬地址的一部分bit 和 TLB 的所有標簽對比進行搜索。
- ASID:全稱為 Address Space ID,一般用於 多進程系統,下文會詳細講述。
- 描述符:由 2個 部分組成,分別為 物理地址(一部分bit) 和 內存區域屬性 組成。可以理解為 cache 中的數據。
一般情況下,切換 進程 時會切換 頁表,因為隨着進程的切換, 虛擬地址 到 物理地址 的映射已經改變。此時需要 清理TLB(即無效化TLB中的數據) 來保持 TLB一致性。清理TLB 一般通過 協處理器CP15 來完成,在 Linux內核 中,有 flush_tlb_all() 和 flush_tlb_range() 函數來完成該工作。
2.3 MMU組成
如下圖所示:

MMU 的工作流程可以總結為下面 2 種情況:
- 訪問 虛擬地址 時,MMU 通過查找 TLB 來找出對應的 頁幀,從而訪問 物理地址,如圖中的 頁1、頁2 和 頁3。
- 如果 MMU 在 TLB 中沒找到對應的 TLB行 時,將進行 traslation table working。即從 物理地址空間 的 頁表中 找出對應的 頁表項,並根據 頁表項 找到對應的 物理地址。並將該 頁表項 更新到 TLB 中,以備下次使用。
2.4 MMU工作過程
ARMv7 下的 MMU 具有 2級頁表,分為 1級頁表 和 2級頁表。
2.4.1 1級頁表
1級頁表 也稱 主頁表 和 段頁表,下面簡稱 L1頁表。它將 4GB 的地址空間划分為 4096 個 1MB 大小的 段,每個段的地址為 32bit。所以 1級頁表 擁有 4096 個 32bit 的 頁表項。
2.4.1.1 一級頁表項
L1頁表 使用了 短描述符頁表(Short-descriptor translation table),其 頁表項 具有以下特征:
- 32bit 的頁描述符
- 具有 2級 以上的 頁表
- 支持 32bit 的 物理地址
- 支持 4種 內存大小:
- 16MB/1M,稱為 段
- 64KB/4KB,稱為 頁
在前面說了 TTBR寄存器 是存放 頁表物理地址 的寄存器,需要注意的是:存放在TTBR寄存器的地址需要16KB對齊
一級頁表項 一共有 4種 格式,如下圖所示:

每種格式都由 物理地址部分+屬性部分 組成,可以直接在圖中看出 物理地址部分 的示意,這里不多贅述。各種格式的含義如下:
- 1MB段轉換頁表項(Section) ,映射到 1MB 的物理地址范圍。其 物理地址部分 即為所需要映射的 物理基地址。
- 物理地址部分 指向 2級頁表 的 物理基地址。
- 16MB段(SuperSection) 轉換頁表項,是一種特殊的 1MB段轉換頁表項。其 物理地址部分 即為所需要映射的 物理基地址。
- 無效頁表項,當訪問該頁表項時,將觸發 指令取指異常 或 取數據異常
下面簡單說下各個字段的含義:
- Ignored:忽略
- Level 2 Descriptor Base Address:二級頁表物理基地址
- Section Base Address:1MB段基地址
- Supersection Base Address:16MB段基地址
- SBZ:全稱 should be zero,無效屬性字段
- AP:全稱 Access Permissions,內存區域訪問權限
- Domain:用於權限控制,下文講述。
- TEX:全稱 Type extension,設置內存區域類型
- B:全稱 Bufferable,是否設置 寫緩沖。
- C:全稱 Cacheable,是否設置 cache。
- nG:全稱 non-Global。如果 頁表項的nGbit 被設置,那么該 頁表項 對應的 內存區域 將只能被 特定的進程 使用。當MMU 使用該 頁表項 進行映射時,也需要使用到 ASID。
- S:全稱 Shareable,共享設置項。
- bit[18]:該 bit 決定 段頁表項 是 1MB頁表項 還是 16MB頁表項。
- bit[1:0]:這 2個bit 決定頁表項的類型,如下:
- 00:無效頁表項
- 01:轉換表頁表項
- 10:段頁表項
2.4.1.2 一級頁轉換
以 1MB段 舉例,假設 L1頁表 的物理地址為 0x12300000,現在有一個虛擬地址 0x00100000。其轉換過程如圖所示:


- 查表過程:將 虛擬地址高12bit,即0x001 乘以 4 得到 0x004。0x004 即為 該虛擬地址所在段的頁表項在頁表中的偏移,所以 該虛擬地址對應的頁表項的物理地址為0x12300000+0x004=0x12300004。
- 根據查到的 頁表項,將 頁表項高12bit 和 虛擬地址低30bit 結合,即為 該虛擬地址在該1MB段內的物理地址
值得注意的是:例子中,高12位一共是4096個頁表項,那么4096x4一共是16384字節的大小,因為每個頁表項是32位。所以4096個頁表項需要16K大小的內存來存儲頁表。也是因為如此,每個虛擬地址的高12bit都需要乘以4.
下圖為例子的完整轉換過程,其余類型的 頁表項 轉換過程類似、

2.4.2 二級頁表
2級頁表 一共有 256 個 4字節大小 的 頁表項,總共占據 1KB大小 的內存空間。L2頁表 的大部分內容與 L1頁表 類似,相同部分下文將不再贅述
2.4.2 二級頁表項
二級頁表項 一共有 3種 格式,如下圖所示:

每種格式與 L1頁表項 一樣由 物理地址部分+屬性部分 組成,可以直接在圖中看出 物理地址部分 的示意,格式如下:
2級頁表項 具有以下特征:
- 粗頁表項: 其 物理地址部分指向 64KB大小 的 物理基地址。
- 細頁表項: 其 物理地址部分指向 4KB大小 的 物理基地址
- 無效頁表項,當訪問該頁表項時,將觸發 指令取指異常 或 取數據異常
屬性字段 的含義請參考 1級頁表 章節。
2.4.2 二級頁表轉換
L2頁表 的轉換過程與 L1頁表 的轉換過程一脈相承。以 4KB 為例子,如下圖所示:

由上圖可以看出其轉換步驟如下:
- 通過 虛擬地址 找出 L1頁表項 並轉換為 L2頁表 的 基地址。
- 根據 L2頁表基地址 並集合 虛擬地址的[19:12]bit 找出 虛擬地址 對應的 L2頁表項。
- 將 虛擬地址[11:0]bit 和 L2頁表項 的 物理地址部分 結合得出具體的 物理地址
結合 L1頁表 的完整轉化過程如下圖所示:

2.5 MMU內存屬性
2.5.1 內存區域權限
每個 內存區域 都有自己的權限,不符合訪問權限的 內存訪問 都會引發 異常。如果是 數據訪問 則引發 數據異常。如果是 指令訪問,且該指令在執行前沒有被 flush,將引發 預取指異常。
引發的 異常原因 將會被設置在 CP15 的 the fault address and fault status registers
內存區域權限 由 AP、APX 和 Domain(域) 共同控制,如下:
- AP/APX:字段 AP 和 APX 的不同組合將形成不同的 訪問權限,如圖所示。按照筆者理解,
Privileged 指的是 CPU 處於 svc 等狀態,而 Unprivileged 則為 CPU 處於 user 狀態。
訪問權限組合表 - Domain:這是一種 ARM架構 不常用的 內存全權限控制 方式。MMU 可以將所有 內存區域 分配到 16個域 中,每一個 域 都有自己的 訪問權限。被分配到 域 中的 內存區域 必須遵循該 域 的訪問權限。可以通過設置 頁表項 中的 Domain字段 來實現 域 的分配。
在 CP15協處理器 中有一個 DACR寄存器(Domain Access Control Register),該寄存器為 32bit,每個 域 的 訪問權限 由 2個bit 設置,一共設置 16個域。域 的權限設置如下:- 不可訪問(no-access):對該 域 的 內存區域 進行訪問將引發 異常。
- 管理者(Manager mode):訪問不受任何控制,不產生 異常
- 用戶(Client mode):使用 頁表項 中的 AP/APX字段 進行控制
需要注意的是:內存區域控制以域控制為主,頁表項的AP/APX字段為次。ARMv7不建議使用域進行控制,所以建議把DACR寄存器設置為用戶模式
2.5.2 內存類型
ARM架構 實現了 3種內存類型,每種類型都是 互斥的,如下:
- Strongly-ordered
- Device
- Normal
每種 類型 的細節如下圖所示:

需要注意的是,Device類型的Shareable內存區域現在已經被棄用
內存區域類型 可以通過 TEX字段、C字段 和 B字段 來進行設置,如下圖所示

值得注意的是:按照筆者理解,inner cache是L1 cache,而outer cache是指在L1cache下面的cache,比如L2cache
操作系統如何使用頁表
2.6 進程與MMU
操作系統 會為 每個進程 分配一個 頁表,該 頁表 使用 物理地址 存儲。當 進程 使用類似 malloc 等需要 映射代碼或數據 的操作時,操作系統 會在隨后馬上 修改頁表 以加入新的 物理內存。當進程完成退出時,內核會將相關的頁表項刪除掉,以便分配給新的進程。
2.6.1 Address Space ID
在操作系統中, 多進程 是一種常態。那么多進程 的情況下,每次 切換進程 都需要進行 TLB清理。這樣會導致切換的效率變低。
為了解決問題,TLB 引入了 ASID(Address Space ID) 。ASID 的范圍是 0-255。
ASID 由操作系統分配,當前進程的ASID值 被寫在 ASID寄存器(使用CP15 c3訪問)。TLB 在更新 頁表項 時也會將 ASID 寫入 TLB。
如果設置了如果 當前進程的ASID,那么 MMU 在查找 TLB 時, 只會查找 TLB 中具有 相同ASID值 的 TLB行。且在切換進程是,TLB 中被設置了 ASID 的 TLB行 不會被清理掉,當下次切換回來的時候還在。所以ASID 的出現使得切換進程時不需要清理 TLB 中的所有數據,可以大大減少 切換開銷。
具體可以看參考鏈接《多核MMU和ASID管理邏輯》
2.6.2 TTBR0和TTBR1
前面講了 TTBR寄存器 是用於存放 頁表基地址,在 ARmv7 中一共有 2個 這樣的寄存器,分別是 TTBR0 和 TTBR1。
那么這里提出一個問題:在進行 Translation Table walking 的時候,選擇哪個TTBR寄存器,又如何選擇?
在 ARMv7 中,有一個寄存器為 TTBCR(TTB Control Register),即TTB控制寄存器。TTBCR寄存器 可以被設置為 0-7 這幾個值。
在進行 地址映射 時, MMU 會根據 TTBCR寄存器 中的值查看 虛擬地址 是高位地址,根據 高位地址 選擇對應的 TTBR寄存器。
舉個例子,假設 TTBCR寄存器 被設置為 4,則 MMU 會檢查 虛擬地址 的 高4bit,如果 高4bit 都為 0,則此時選擇 TTBR0。
需要注意的是:TTBCR被設置為 0 時,默認選擇 TTBR0。
下面我們看看使用和不使用 TTBR1 帶來的影響。
- 不使用:在 ARM32架構的操作系統中,不使用 TTBR1寄存器。此時,用戶空間 和 內核空間 共用一個 頁表。也就是說 用戶空間 和 內核空間 都使用 TTBR0 來記錄 頁表地址,這樣可以避免一個問題,就是進行 用戶空間和內核空間的切換時,可以避免切換頁表帶來的性能損耗。但與此同時也帶來一個問題,用戶空間 的 每個進程 都擁有 內核頁表副本,當 內核空間頁表 修改時,所有 進程 都需要同步修改其 內核頁表副本。造成一定的性能損失
- 使用:ARM64架構的操作系統中,虛擬地址空間 非常大。用戶空間 和 內核空間 都是 256T。用戶空間地址高位為0,內核空間地址高位為1。這樣的特性滿足 TTBR1寄存器 的使用條件。根據 用戶空間地址 和 內核空間地址 的不同,選擇對應的 TTBR寄存器。這樣就不需要為每個 進程 維護一份 內核頁表副本。
2.6.3 代碼實例
本小節簡單地講述一下 Linux 進行 MMU切換 時的代碼片段。以 ARMv7單核CPU 為例子。
根據筆者的理解,其調用圖譜如下:
->switch_mm ->check_and_switch_context ->cpu_switch_mm(processor.switch_mm) ->cpu_v7_switch_mm
筆者會將簡單的說明注釋在代碼中,不進行另外的說明。
/* arch/arm/include/asm/mmu_context.h */ static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, struct task_struct *tsk) { #ifdef CONFIG_MMU unsigned int cpu = smp_processor_id(); /* * __sync_icache_dcache doesn't broadcast the I-cache invalidation, * so check for possible thread migration and invalidate the I-cache * if we're new to this CPU. */ /* 這里應該是說進程如果調度到新的CPU,則需要將該CPU的cache給清理掉 */ if (cache_ops_need_broadcast() && !cpumask_empty(mm_cpumask(next)) && !cpumask_test_cpu(cpu, mm_cpumask(next))) __flush_icache_all(); if (!cpumask_test_and_set_cpu(cpu, mm_cpumask(next)) || prev != next) { /* 如果調度的進程不是本進程,則執行check_and_switch_context */ check_and_switch_context(next, tsk); if (cache_is_vivt()) cpumask_clear_cpu(cpu, mm_cpumask(prev)); } #endif } static inline void check_and_switch_context(struct mm_struct *mm, struct task_struct *tsk) { if (unlikely(mm->context.vmalloc_seq != init_mm.context.vmalloc_seq)) __check_vmalloc_seq(mm); if (irqs_disabled()) /* * cpu_switch_mm() needs to flush the VIVT caches. To avoid * high interrupt latencies, defer the call and continue * running with the old mm. Since we only support UP systems * on non-ASID CPUs, the old mm will remain valid until the * finish_arch_post_lock_switch() call. */ mm->context.switch_pending = 1; else /* 使用該函數進行MMU切換頁表 */ cpu_switch_mm(mm->pgd, mm); } /* arch/arm/include/asm/proc-fns.h */ /* 根據筆者找的代碼,cpu_switch_mm 應該直接調用了processor.switch_mm */ #define cpu_do_switch_mm processor.switch_mm #define cpu_switch_mm(pgd,mm) cpu_do_switch_mm(virt_to_phys(pgd),mm)
processor.switch_mm 是一個 回調函數,根據筆者找到的資料,應該是指向 ** arch/arm/mm** 目錄下的一些列 MMU 操作代碼。這里以 proc-v7-2level.S(即ARMv7 2級頁表) 進行說明
/* arch/arm/mm/proc-v7-2level.S */ /* 根據APCS,傳入的參數是存放在寄存器 r0和r1 */ ENTRY(cpu_v7_switch_mm) #ifdef CONFIG_MMU mmid r1, r1 @ get mm->context.id ALT_SMP(orr r0, r0, #TTB_FLAGS_SMP) ALT_UP(orr r0, r0, #TTB_FLAGS_UP) #ifdef CONFIG_PID_IN_CONTEXTIDR mrc p15, 0, r2, c13, c0, 1 @ read current context ID lsr r2, r2, #8 @ extract the PID bfi r1, r2, #8, #24 @ insert into new context ID #endif #ifdef CONFIG_ARM_ERRATA_754322 dsb #endif mcr p15, 0, r1, c13, c0, 1 @ set context ID isb /* 在這里,將r0所指向的頁表基地址設置到TTBR0中,完成頁表的切換 */ mcr p15, 0, r0, c2, c0, 0 @ set TTB 0 isb #endif bx lr ENDPROC(cpu_v7_switch_mm)
三、參考鏈接
《ARM Cortex-A Series Programmer’s Guide》
《Cortex-A7 MPCore Technical Reference Manual》
《多核MMU和ASID管理邏輯》
TLB的作用及工作過程
MMU和cache詳解(TLB機制)
inux-kernel – Linux內核ARM轉換表庫(TTB0和TTB1)
ARM TTBR0,TTBR1寄存器與ARM32頁表復制
選擇使用TTBR0或TTBR1做為translation table base地址寄存器
TLB中ASID和nG bit的關系
ASID
Linux arm 進程切換
ARM-LINUX的進程切換
作者:wipping的技術小棧
鏈接:https://www.jianshu.com/p/ef1e93e9d65b
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。