ARMv7 MMU多级页表
Arm v7-A架构中规定的VMSA(virtual memory space address)可以支持最多两级页表,MMU是具体实现VMSA的器件,本文关注使用MMU实现虚拟地址转换的关键的软件细节。
ARMv7 MMU特性概览
- 支持最多2级页表。
- 支持长描述符和短描述符2种描述页表项的方式。
- 一级页表支持1M或者16M两种size.
- 二级页表支持4K或者64K两种size.
- 提供虚拟地址转换的同时提供内存区域的权限控制。
对上面的特性做出一些必要的解释:首先2级页表是可配置的,软件可以配置MMU只使用一级页表。其次,长描述符是针对一种叫大内存扩展(Large Physic Address Extension, LPE)的特殊扩展使用的,市面上常见的ARM处理器大概率都没有使用这种扩展。最后,虽然一级页表和二级页表的页尺寸好像是可选的,其实仍然是与LPE扩展有关的,大部分都只支持1M的一级页表和4K的二级页表。
后面的谈论都是在不涉及LPE扩展的前提下。
关键术语的再说明
页表及其相关术语比较通用,为了描述更加准确,ARM文档中做了一些术语的定义和区分:
- Translation Table和 Page Table. 常说的页表实际上只有最后一级是真正的页表,前面的若干级都是为了找到最后一级页表的页目录。不管是真正的页表还是页目录都被称为转换表。
- Translation Table Entry. 所有的转换表在代码中实现都是一维数组,数组中的每个项,就被称为一个entry.
- section和page. 一级转换表对应的范围叫section,二级转换表对应的范围叫page.
1st Translation Table
一级转换表的Entry有四种格式,这些格式可以共存在转换表中。实际使用是是四选一,也就是说,可以将MMU配置成只使用一级转换表(此时就是页表)就完成地址转为;也可以配置成需要查找到二级页表才完成转换。除此之外,当不期望使用某个虚拟地址范围时,可以将其对应的Entry配置成Fault格式,这样当处理器试图访问这些虚拟地址时就会产生异常。
关于一级转换表有下面一些细节:
- 转换表的地址必须按照16KB对齐(因为MMU的寄存器问题,只记录转换表地址的高18位)。
- 每个Entry长度为32,也就是一个word的长度。
- Entry最低两位表示Entry的类型,这表明不同类型的Entry可以同时存在,软件可以自由选择是否使用二级转换表。
- 将4G空间划分为4096个1M的section,也就是一级转换表一共有4096个entry.
- 关于supercetion,需要特别强调的是,相当于16个普通的section,需要16个完全相同的Entry,但是MMU中可以识别这是同一个supersection,这样能够加速MMU地址转换的过程(TLB hit),但对软件来说并不会节省管理内存。
//主要的几个格式的映射
unsigned L1_MMU_TT[4096] __attribute__((align(16*1024));
void mapping_L1_section(unsigned va_start, unsigned pa_start, unsigned size, unsigned attr)
{
unsigned index = Value(High_12_bits(va_start));
unsigned num_entry_needed = size/1024/1024; //或者size>>20,计算需要多少个1M section
for(auto i=0; i<num_entry_needed; i++)
{
L1_MMU_TT[index+i] = High_12_bits(pa_start) | LOW_20_bits(attr);
pa_start += 1024*1024;//物理地址移动1M
}
}
void mapping_L1_supersection(unsigned va_start, unsigned pa_start, unsigned size, unsigned attr)
{
unsigned index = Value(High_8_bits(va_start));
unsigned num_entry_needed = size/16/1024/1024;
for(auto i=0; i<num_entry_needed; i++)
{
for(int j=0;j<16;j++)
{
//需要连续16个entry,每个entry完全相同
L1_MMU_TT[index+i*16+j] = High_8_bits(pa_start) | LOW_24_bits(attr);
}
pa_start += 16*1024*1024; //物理地址移动16M
}
}
void mapping_L1_L2d(unsigned va_start, unsigned l2_addr, unsigned attr)
{
unsigned index = Value(High_12_bits(va_start));
L1_MMU_TT[index] = High_22_bits(l2_addr) | LOW_10_bits(attr);
}
2nd Translation Table
二级转换表的Entry有三种格式,同样地,这三种格式理论上是可以同时存在于二级转换表的,此外,每个entry也是三选一。Large Page是64K,Small Page是4K.
关于二级转换表,下面这些要注意细节:
- 二级转换表(页表)是有很多个的,每个一级页表都对应一个二级页表数组。
- 二级页表的每个entry为1个word,也就4字节。
- 每个二级页表都有256个entry(无论是large page还是small page),也就是说每个二级页表都占1KB内存。
- Large Page与前文的Super Section一样,同样需要占用多个entry,这里是16个二级页表的entry. 这样能够使得MMU TLB hit概率变大,加速地址转换过程,并不会节省页表大小。
//二级页表的主要映射操作
void mapping_L2_Small(unsigned *pageTableAddr, unsigned va_start, unsigned pa_start, unsigned size, unsigned attr)
{
unsigned index = Value(High_20_bits(va_start));
unsigned num_page_needed = size/4/1024;
for(auto i=0; i<num_page_needed; i++)
{
*(pageTableAddr+index+i) = High_20_bits(pa_start) | Low_12_bits(attr);
pa_start += 4*1024; //物理地址移动4K
}
}
void mapping_L2_Large(unsigned *pageTableAddr, unsigned va_start, unsigned pa_start, unsigned size, unsigned attr)
{
unsigned index = Value(High_20_bits(va_start));
unsigned num_page_needed = size/64/1024;
for(auto i=0; i<num_page_needed; i++)
{
for(auto j=0; j<16; j++)
{
//需要连续16个entry,每个entry完全相同
*(pageTableAddr+index+i*16+j) = High_20_bits(pa_start) | Low_12_bits(attr);
}
pa_start += 64*1024; //物理地址移动64KB
}
}
仅使用1st Translation Table映射过程
当entry的格式为section格式时,也就是低2位为10时,就发生如图的过程。图中TB时Taslation Table的缩写。整体过程再简单描述一下:
- 需要将一级转换表的基地址告诉给MMU的对应寄存器(需要软件显示操作MMU寄存器),应为这个寄存器只保留高18位,所有转换表的起始地址必须是16K对齐的(画图的时候算错了)。
- 转换时,拿到虚拟地址的高12位,作为index,找到对应的entry:l1_mmu_tt[index].
- 对应的entry如果最低2位为10,表示是section格式,也就是说直接替换虚拟地址的高12位即可得到物理地址。
- 从entry中取出对应的高12位,替换。
使用两级Translation Table映射过程
要使用2级Translation,也就是常说的2级页表,需要第一级对应的entry的最低2位为01,表示entry指向一个2级页表的描述符。整体过程再简单描述一下:
- 首先将一级页表的基地址告诉MMU的寄存器TTBR(需要软件显示操作MMU寄存器),注意一级页表需要按照16KB对齐,原因已经阐述过。
- 通过虚拟地址的高12位作为index,找到对应的L1 entry: l1_mmu_tt[index],对应的entry最低2位为01.
- 从L1 entry的高22位拿到对应的L2 Translation Table基地址.。因此L2 Table必须是按照4KB对齐的,因为L1 entry只记录其地址的高22位。
- 通过虚拟地址的中间10位作为index,找到对应的L2 entry.
- 将虚拟地址的高22位替换成为L2 entry的高22位即得到物理地址(如果是small page,entry的最低两位为1x(11或10)。large page类似,替换高16位即可。
下一步更新计划
- 页表配置时机与细节
代码是运行在虚拟地址空间中的,页表配置也是代码,需要考虑清楚一个问题,页表是什么时候配置的,不然就会面临鸡生蛋问题。 - MMU的TLB一致性问题
TLB是页表的缓存,当页表发生变化时,如果缓存没有跟着变化,那么就将发生错误的地址翻译。 - MMU硬件的操作指令。
- MMU与特权级的关系。