MMU-AArch32


MMU即内存管理单元,是操作系统对内存进行分段分页管理的硬件基础,它是一个或者一组芯片,一般位于协处理器中,它的重点任务是完成虚拟地址到物理地址的转化,同时还会对页面进行权限管理,它与CPU Core之间通信方式是通过PageFault-“缺页异常”。
PageTable即页表,OS内核中供MMU做内存寻址使用的软件数据结构,是虚拟地址与物理地址的转换关系表,同时记录了页面访问权限。
TLB即页表缓存,又被称为快表,减少MMU访问内存页表,加快MMU寻址速度。

本文着重记录AArch32平台架构上MMU相关的一些常备知识点


MMU的作用

从一个简单的例子说起

下面这幅图描述了一个抽象出来的最简单的MMU与OS内核交互过程,二者配合完成CPU对内存寻址。

对该图说明如下:
① 这是一个简单的16位的CPU通过MMU查询页表做内存寻址的过程概要,并不是某架构/某OS上的精确描述,比如页表项等不同OS内核实现是并不一致的;
② 16bit的CPU能寻址虚拟内存范围是2^16=64KB,如上图左边“虚拟内存”显示; 依旧按照最常用的4KB分页对内存进行分页管理;
③ 中间紫色分割线:以上部分,是正常寻址过程;以下部分,是触发缺页异常后做页换出、更新页表寻址过程;
④ 物理内存只有32KB,显然不能满足对64KB虚拟内存进行地板式的一一映射(flat mapped),因此需要OS有缺页异常处理能力、以及对页面的swap能力;
⑤ 这里暂时并未考虑存在TLB的情况,只是一个裸的MMU从内存取PageTable并完成页转换;
⑥ 虚拟地址==>物理地址映射 是以Page页(这里是4KB)作为最小单位的,即每次最小映射1页

⑦ 正常寻址过程:

  • MMU从TTBR0/1寄存器读入PGD物理地址
  • MMU从虚拟地址最高4位读入一级页表的页表项偏移PageIndex
  • MMU将PGD+PageIndex找到该Page页对应的物理地址,也即一级页表项的内容
  • MMU对该Page页物理地址进行权限检查(检查最低bit位P是否==1,相等表示有效不等表示无效),满足后,根据页框物理地址找到对应物理页框起始(或取最高4bit找到对应的物理页框号,根据页框号和物理地址起始值找到对应物理页框)
  • MMU用物理页框对应的物理地址+虚拟地址记录的PageInsideOffset页内偏移,找到该虚拟地址对应的物理地址,并将该物理地址发到地址总线上,内存控制器按照该物理地址进行访存操作

⑧ 触发缺页异常过程:

  • MMU前几个操作与⑦中正常流程一样
  • MMU从权限检查开始,当检查到最低位P!=1说明页表中记录的页框物理地址无效,MMU触发PageFault缺页异常
  • CPU接收到该异常后,跳转到PageFault异常处理流程,调用OS内核的handler进行PageFault处理:OS内核进行权限检查看vma虚拟地址空间描述符中记录的权限是否能够满足,能够满足则根据LRU算法(最近最少使用原则)选择一个物理页将该页swap换出到磁盘,然后修改与该物理页对应的虚拟页页表,将P位置为0标识该页表项无效;最后修改触发PageFault的虚拟页对应的页表,将该物理页地址写入表项,并将P置为1标识表项有效,到此旧物理页换出、旧页表失效、新页表建立均已经完成。MMU已经可以按照新页表项进行正常寻址了。
  • MMU后面寻址的过程与⑦中又一致了

以上就是抽象出来的一个简单的MMU功能描述,它与OS内核配合完成CPU的内存寻址过程。

硬件位置

在ARM架构中,一般MMU位于CP15协处理器中,一般CP15协处理器中与MMU相关的寄存器如下:

Register Name Description Usage
寄存器1 它的“某些位”用于配置MMU中一些操作 系统控制
寄存器2 保存内存中页表的基地址 页表基址
寄存器3 设置域的访问控制属性 内存访问权限
寄存器4 保留 用于GIC<->CPU交互接口
寄存器5 内存访问失效状态指示 PageFault时错误原因
寄存器6 内存访问失效时失效的地址 PageFault时访问的错误地址
寄存器8 控制与清除TLB内容相关的操作 TLB控制
寄存器10 控制与锁定TLB内容相关的操作 内存模型与TLB控制
寄存器13 进程上下文与线程PID寄存器 用于线程切换

如下是ARMv8 SPEC中描述CP15的32-bit控制寄存器的截图

功能

  • 地址转换
  • 权限检测
  • 异常中止(PageFault)
  • 刷新TLB,锁定TLB中某些页表项

地址转换
MMU地址转换的过程就是通过PageTable以虚拟地址查找物理地址的过程。

① MMU支持存储访问基于段(sections)或页(pages)

  • 段(Sections) 大小为1MB的存储块
  • 大页(Large pages) 大小为64KB的存储块
  • 小页(Small pages) 大小为4KB的存储块
  • 极小页(Tiny pages) 大小为1KB的存储块

段和大页支持允许在TLB中只使用一个条目来映射一大块存储区域。通过采用额外的访问控制机制,可以将小页分成大小为1KB的子页,将大页分成大小为16KB的子页,极小页不能再分了,只能以1KB大小的整页为大小进行访问控制;

② 通常情况,段式地址转换过程只需要一级页表,而页式地址转换过程需要二级以上页表
Linux内核,按照其实现并不需要段式映射,只需要完成页式映射,而且最多能支持4级页表,工程中一般会选择如下组合:

  • AArch32平台:使用二级页表+4KB Page
  • AArch64平台:使用三级页表/四级页表+4KB Page,或者二级页表/三级页表+64KB Page

③ 无论如何选择映射方式、页大小、内存模型、以及CPU位数,MMU均根据OS选择的页表实现来完成“虚拟地址”=>“物理地址”的转换
页表实现见下一节<PageTable的作用>

根据页表具体实现,MMU寄存器设置:

  • C1 - S(System)和R(ROM)位来控制二级页目录项中AP bit(段/页)的访问权限
  • C2 - bit[31:14]存放一级页表基地址,bit[13:0]为0。因此,一级转换表的基地址必须是16KB对齐的;

    Tips:
    这也间接决定了32bit虚拟地址中“一级页表内offset”所占长度是12bit(12=32-18-2)
    而虚拟地址高12bit就是一级页表内offset,也即决定了:每个“一级页目录项”索引“1M虚拟地址空间”(这时因为它每加1,相当于整体地址空间增加2^20)
    如下图所示:

    参考代码 TODO...

  • C3 - TODO...
  • C4 - TODO...
  • C5 - TODO...
  • C6 - TODO...
  • C8 - TODO...
  • C10 - TODO...
  • C13 - TODO...

权限控制

MMU对于权限的检查可以分成两个级别,权限检查是从①=>②的过程
① 通过对一级页目录项中“bit[8:5]域字段”去索引到CP15的“DACR寄存器(32bit)”中对应的域上,查看权限:

Tips:
域是指段、大页和小页的集合,AArch32支持16个域。

DACR寄存器是一个32bit的,被分成了16个Domain,每个Domain占2个bit,对应一个域,每个域在寄存器初始化时会被赋予一个默认值,如下:

bit[8:5]这4个bit是以上16个Domain的索引,通过该值可以知道该一级页目录项索引的这1M虚拟空间属于那个Domain,然后查看对应bit[n+1:n]来确认该Domain的访问权限。

DACR寄存器每个Domian控制权限描述如下:

Value Meaning Note
0b00 没有访问权限 任何访问将产生域错误
0b01 客户类型 检查段或页描述符的访问权限位来决定访问权限
0b10 保留 使用这个值会产生不可预知结果
0b11 管理员权限 不考虑段或页描述符的访问权限位,不会产生访问失效

以上字段标记了允许对整个域的访问进行使能和禁止,这样可以将整个内存空间包含在虚拟存储空间中,或排除其外。支持两种形式的域访问:

  • 客户类型 - 是域的用户(执行程序,访问数据),受限于组成域的各个段和页的访问权限。
  • 管理员类型 - 控制域的行为(域的当前段和页以及域访问),不受限于组成域的各个段和页的访问权限

一个程序可以是其中一些域的客户,以及另一些域的管理员,对其他域没有访问权限。这允许需要访问不同存储资源的程序具备非常灵活的存储保护措施。

② 通过检查二级页目录项中各个“权限访问控制位”-APx(2bit)以及CP15 C1寄存器的S/R bit来实现对各个段、页的权限控制:
(二级页目录项中这些bit详见下面一节<PageTable的作用>)

AP C1.S C1.R Supervisor Mode User Mode Notes
0b00 0 0 没有访问权限 没有访问权限 访问将产生PageFault
0b00 1 0 只读 没有访问权限 管理模式只读,管理模式写/用户模式访问将产生PageFault
0b00 0 1 只读 只读 写将产生PageFault
0b01 X X 读/写 没有访问权限 用户模式访问将产生PageFault
0b10 X X 读/写 只读 用户模式写将产生PageFault
0b11 X X 读/写 读/写
0bXX 1 1 不可预知 不可预知 保留字段

异常中止
有2种因为存储器访问限制而导致ARM处理器中止执行的机制:MMU失效、外部中止

Tips:
由MMU检测到的访问中止将发生在任何外部存储器访问之前,而引起外部中止的外部访问将由外部存储系统来停止。

如果存储访问中止发生在指令提取阶段,那么如果当处理器试图去执行非法访问对应的指令时,将产生指令预取中止异常.
如果存储访问中止是在数据访问阶段,就会产生一个数据中止异常。数据访问同时引起多种类型的数据中止时,它们是由优先级的.

① MMU失效:
MMU检测到存储访问失效,并向CPU发送PageFault缺页异常,分为如下4种类型

  • 对齐失效
  • 地址转换失效
  • 域失效
  • 权限检查失效

② 外部中止:
外部存储系统报告一个非法存储访问,分为如下3种类型

  • 行提取
  • 存储器访问(uncached/unbufferd访问)
  • 页表访问

协处理器CP15包含2个与发生数据访问中止相关的寄存器,记录了失效状态:FAR、FSR
失效地址寄存器(FAR):引起数据异常的“虚拟地址”
失效状态寄存器(FSR):包含一个4位失效状态(FS[3:0])和所属域编号

Tips:
指令预取中止时,这些寄存器不会发生变化。只有发生数据访问异常时,才会发生改变。

失效状态标识表:

Priority Coming PageTable Position FS[3:0] 域字段 FAR
最高 极端异常 - 0b0010 无效 实现定义
- 中断向量异常 - 0b0000 无效 有效
- 地址对齐 - 0b00x1 无效 有效
- 转换时外部存储异常 一级页目录 0b1100 无效 有效
- 转换时外部存储异常 二级页目录 0b1110 有效 有效
- 地址转换 0b0101 无效 有效
- 地址转换 0b0111 有效 有效
- 0b1001 有效 有效
- 0b1011 有效 有效
- 权限 0b1101 有效 有效
- 权限 0b1111 有效 有效
- 行提取时外部存储异常 0b0100 有效 有效
- 行提取时外部存储异常 0b0110 有效 有效
- 非行提取时外部存储异常 0b1000 有效 有效
最低 非行提取时外部存储异常 0b1010 有效 有效

Linux内核异常处理代码

1
TODO...

TLB控制
TODO...

MMU初始化

CP15的寄存器Reg1的bit[0]标识使能/禁止MMU,CPU复位时该位被清0,所以复位后MMU是禁止状态

当“禁止MMU”之后,CPU做内存寻址有如下访问规则
① 是否支持cache和buffer由各个芯片平台具体实现决定,但访存规则是:

  • 如果芯片设计当禁止MMU时也禁止cache和写buffer,C、B控制位的值忽略
  • 如果芯片设计当禁止MMU时允许cache和写buffer,那么:
    • 数据访问被视为uncacheable和unbufferable(C=0,B=0)
    • 使用同一的TLB时,指令访问视为uncachable(C=0);使用分开的TLB时,指令访问视为cachable(C=1)

② CPU访存不进行权限控制,MMU也不会生成存储访问中止信号
③ 所有的物理地址和虚拟地址相同(即平板地址映射模式)

在设置CP15的Reg1[0]=1前,即“开启MMU”之前:
① CP15各个寄存器必须已经完成初始化
② OS内核必须已经完成了页表的建立

在“开启MM”U之后,MMU会改变地址映射关系,因此:
① 使能/禁止MMU的代码必须是位置无关的(物理地址==虚拟地址),防止跑飞
② 开后要立即对cache做一次invalid
③ 开启后要立即对TLB做一次invalid

Linux内核中对MMU初始化参考代码如下

1
TODO...

PageTable的作用

PageTable,页表又被称为translation table,按照分页机制又被分成二级、三级、四级这些具体实现。

页表结构

不同类型页表各级结构中的项,定义了1KB/4KB/16KB/64KB/1MB/16MB等大小区域属性,主要包含以下三个方面:

  • 地址转换关系(base addr/offset)
  • 内存页访问权限(域/AP)
  • Cachability和Bufferability控制(C位和B位)

Tips:
以二级页表为例,页表完整结构包含:
* 全局页目录地址PGD => 保存了索引的一级页表基址(物理地址)、权限控制bit
* 一级页表 => 一块保存了N个一级页目录项的区域,每个项保存了索引的二级页表基址(物理地址)、权限控制bit
* 二级页表 => 一块保存了N个二级页目录项的区域,每个项保存了索引的物理页框号(物理地址)、权限控制bit

在32位架构上,地址线理论宽度是32bit的,也就是理论上能寻址最大物理地址范围是0GB~4GB,在实际实现用由于寄存器/IO空间等都需要占用地址范围,存储器自身实际物理大小一定是达不到4GB的。只是这应该也不会影响到我们对虚拟地址空间0GB~4GB划分的理解。

虚拟内存模型基本一致选择如下
AArch32 Linux memory layout with 4KB pages + 2 levels

Start End Size Use
00000000 bfffffff 3GB (32-bit) user
c0000000 ffffffff 1GB (30-bit) kernel

MMU支持多种地址转换模式组合

  • 段式(段/超级段) + 一级页表 + 1M段
  • 页式 + 二级页表(粗粒度) + 64KB大页/4KB小页
  • 页式 + 二级页表(细粒度) + 64KB大页/4KB小页/1KB极小页

一级页目录项格式:(5条路径)

① 无效路径:(缺页)

比特位 取值 描述
bit[1:0] 0b00 该一级页表项无效,MMU触发PageFault,OS内核kernel panic掉
bit[31:2] - IGN,硬件忽略位,软件可以考虑根据情况使用

② 超级段式寻址路径:这个与段式只是寻址大小不同,其它类似见下面段式路径
③ 段式寻址路径:

比特位 取值 描述
bit[1:0] 0b10 段描述符
bit[2] 据实际 B位,控制该内存区域是否经过Write-Buffer
bit[3] 据实际 C位,控制该内存区域是否经过Cache
bit[4] 据实际 IMP,根据具体实现定义
bit[8:5] 据实际 域位,指定该内存区域所在的“域”,域号0~15
bit[9] 0b0 SBZ,未使用应该为0
bit[11:10] 据实际 访问权限,控制段访问是否合法
bit[19:12] 0b00000000 SBZ,未使用应该为0
bit[31:20] 据实际 段区域基址,是1MB对齐的,区域起始“物理地址”

对应32bit虚拟地址布局:(能寻址的每个物理段大小是1MB=2^20Bytes)

④ 页式粗粒度页表(二级)路径:

比特位 取值 描述
bit[1:0] 0b01 粗粒度描述符
bit[4:2] 据实际 IMP,根据具体实现定义
bit[8:5] 据实际 域位,指定该内存区域所在的“域”,域号0~15
bit[9] 0b0 SBZ,未使用应该为0
bit[31:10] 据实际 二级页表区域基址,是1KB对齐的,区域起始“物理地址”

对应32bit虚拟地址布局:(能寻址的每个物理页大小是4KB=2^12Bytes)

⑤ 页式细粒度页表(二级)路径:

比特位 取值 描述
bit[1:0] 0b11 细粒度描述符
bit[4:2] 据实际 IMP,根据具体实现定义
bit[8:5] 据实际 域位,指定该内存区域所在的“域”,域号0~15
bit[11:9] 0b000 SBZ,未使用应该为0
bit[31:12] 据实际 二级页表区域基址,是4KB对齐的,区域起始“物理地址”

对应32bit虚拟地址布局:(能寻址的每个物理页大小是1KB=2^10Bytes)

二级页目录项格式:(3种页大小)

① 无效页:

比特位 取值 描述
bit[1:0] 0b00 该二级页表项无效,MMU触发PageFault,OS内核酌情处理(kernel panic/分配物理页)
bit[31:2] - IGN,硬件忽略位,软件可以考虑根据情况使用

② 64KB大页:

比特位 取值 描述
bit[1:0] 0b01 64KB大页描述符
bit[2] 据实际 B位,控制该内存区域是否经过Write-Buffer
bit[3] 据实际 C位,控制该内存区域是否经过Cache
bit[11:4] 据实际 AP3~0,权限访问控制位。大页分为4个子页,分别对应AP3~0访问权限
bit[15:12] 0b0 SBZ,未使用应该为0
bit[31:16] 据实际 大页基址,是64KB对齐的,区域起始物理页框号

对于粗粒度页表(虚拟地址布局见上面),因为页框内offset==12,因此该描述符需要在二级页目录项中重复16次(64KB=4KB×16);
对于细粒度页表,因为页框内offseet==10,因此该描述符需要在二级页目录中重复64次(64KB=1KB×64);

③ 4KB小页:

比特位 取值 描述
bit[1:0] 0b10 4KB小页描述符
bit[2] 据实际 B位,控制该内存区域是否经过Write-Buffer
bit[3] 据实际 C位,控制该内存区域是否经过Cache
bit[11:4] 据实际 AP3~0,权限访问控制位。小页分也为4个子页,分别对应AP3~0访问权限
bit[31:12] 据实际 小页基址,是4KB对齐的,区域起始物理页框号

对于粗粒度页表(虚拟地址布局见上面),因为页框内offset=12,因此该描述符需要在二级页目录项中只需要1次(4KB=4KB×1);
对于细粒度页表,因为页框内offset=10,因此该描述符需要在二级页目录中重复4次(4KB=1KB×4);

④ 1KB极小页:

比特位 取值 描述
bit[1:0] 0b11 1KB小页描述符
bit[2] 据实际 B位,控制该内存区域是否经过Write-Buffer
bit[3] 据实际 C位,控制该内存区域是否经过Cache
bit[5:4] 据实际 AP0,权限访问控制位
bit[9:6] 0b0 SBZ,未使用应该为0
bit[31:10] 据实际 小页基址,是1KB对齐的,区域起始物理页框号

对于粗粒度页表(虚拟地址布局见上面),因为二级页目录数量不够用(256个=1K/4Bytes)无法完成寻址,无法使用;
对于细粒度页表,因为页框内offset=10,因此该描述符需要在二级页目录只需要1次(1KB=1KB×1);

Tips:
页表的大小 - 以4KB页+二级粗粒度页表为例
虚拟地址范围bit[31:20][19:12][11:0],分别标识一级页表内offset、二级页表内offset、物理页内offset
bit[31:20]:范围是0 ~ 4096(4K=2^12),可以查找4K个一级页目录项,即:每个一级页表大小是16KB(4B×4K),寻址范围是1M(4G/4×4K)
bit[19:12]:范围是0 ~ 256(256=2^8),可以查找256个二级页目录项,即:每个二级页表大小是1KB(4B×256),寻址范围是4KB(1M/256)
bit[11:0]: 范围是0 ~ 4096(4K=2^12),可以查找4K个物理页框内地址,即:每个物理页框大小是4KB(1B×4K),对应地址是1Byte(4KB/4K)

转换示例
32bit内核选择了“页式映射 + 二级页表(粗粒度)+ 4KB小页”转换模式,页表转换过程

其它OS选择其它类型转换过程,基本都是类似的。

初始化

Linux内核PageTable初始代码

1
TODO...

存储

内核页表存储
swapper_pg_dir保存了Linux内核页表首地址,它是内核在编译时静态初始化的临时页全局目录,内核运行起来后就把它当做了内核页表起始。

正是因为编译时静态初始化的,因此我们在内核C代码中搜索定义时,只在/kernel/arch/arm/include/asm/pgtable.h中找到了一条:

1
extern pgd_t swapper_pg_dir[PTRS_PER_PGD];

其实它真正定义和初始化的地方在kernel/arch/arm/kernel/head.S中:

1
2
.globl  swapper_pg_dir
.equ    swapper_pg_dir, KERNEL_RAM_VADDR - PG_DIR_SIZE

定义全局变量,并且它设置到“Kernel虚拟地址-页表大小”的位置

TODO...

用户态页表存储
TODO...

工程实践

MetalDown漏洞
TODO...(是否只64bit上有meltdown漏洞??)


TLB的作用

Translation Lookaside Buffer,又称为快表,用于缓存页表项,集成在MMU内部。

分类

从硬件上讲,通常每个存储器接口中有一个TLB:

  • 只具备单个存储器接口的系统一般只有一个统一的TLB
  • 若一个系统具有独立的"指令存储器接口"和"数据存储器接口",则通常具有分开的指令TLB(指令MicroTLB)和数据TLB(数据MicroTLB)
  • 而在有的平台上,TLB也会被分成L1 TLB(一级TLB)和L2 TLB(二级TLB)等,比如在main TLB下还存在micro TLB专门给icache/dcache使用的,典型的micro TLB是8个条目、main TLB是64个条目;

Tips:
CPU对于“指令”和“数据”的区分是根据”时序“判断的:
在“取指周期”中从内存读出的信息流是指令流,流向控制器;
在“执行周期”中从内存取出的信息时数据流,流向运算器;

作用

包含TLB的MMU访存示意图:

增加TLB之后,CPU寻址流程大致变为:CPU => MMU => TLB => Memory PageTable => TLB => Memory

结构


其中:

  • ASID是address space id,用于标记这一条也表记录是属于哪个进程
  • VA Tag是valid tag,标记该条也表记录是否有效,只有它有效时这个TLB项才会记录虚拟地址、物理地址、页属性等等

更新

从软件角度,对于TLB的操作基本就是各种invalid方法,因为没有条目存在于TLB却不存在于PageTable的情况,因此也就不需要从TLB向PageTable去做flush动作。

TODO...


CPU内存寻址过程

这里就描述一个32bit的用户态进程,从通过malloc分配堆内存到真正拿到物理内存的全过程,如下图表述:
TODO...


参考文档

Kernel ARM Memory Layout
s3c2410 MMU详解
MMU与CACHE详解
MMU详解
ARMv8内存管理架构
ARMv8-A_Architecture_Reference_Manual_(Issue_A.a)
浅谈Cache和MMU
MMU与CACHE详解
页表机制
linux的swapper_pg_dir的初始化
Linux系统中ARM体系的内存分页认识

Comments
Write a Comment