1.前言
1.1 虚拟地址页码和物理地址页码定义
SV39 分页机制被启用后,所有 S/U 特权级的访存被视为一个 39 位的虚拟地址,它们需要先经过 MMU 的地址转换流程,如果顺利的话,则会变成一个 56 位的物理地址来访问物理内存。
上图为RISC-V下SV39多级页表机制下的虚拟地址页码和物理地址页码格式示意图(Page Offset::页内偏移),可以得知虚拟地址页码的有效宽度为:38-12,物理地址页码的有效位数为:55-12。
1.2 页表项-PageTableEntry(PTE)
上图为 SV39 分页模式下的页表项,其中 [53:10] 这 44 位是物理页号,最低的 8 位 [7:0] 则是标志位,它们的含义如下:
仅当 V(Valid) 位为 1 时,页表项才是合法的;
R/W/X 分别控制索引到这个页表项的对应虚拟页面是否允许读/写/取指;
U 控制索引到这个页表项的对应虚拟页面是否在 CPU 处于 U 特权级的情况下是否被允许访问;
G 我们暂且不理会;
A(Accessed) 记录自从页表项上的这一位被清零之后,页表项的对应虚拟页面是否被访问过;
D(Dirty) 则记录自从页表项上的这一位被清零之后,页表项的对应虚拟页表是否被修改过。
由于PTE只有54位,每一个页表项我们用一个8字节的无符号整型来记录就已经足够。
2. usize转地址或地址页码
// 物理地址宽度
const PA_WIDTH_SV39: usize = 56;
// 虚拟地址宽度
const VA_WIDTH_SV39: usize = 39;
// 物理页号宽度(PAGE_SIZE_BITS数值为12)
const PPN_WIDTH_SV39: usize = PA_WIDTH_SV39 - PAGE_SIZE_BITS;
// 虚拟页号宽度(PAGE_SIZE_BITS数值为12)
const VPN_WIDTH_SV39: usize = VA_WIDTH_SV39 - PAGE_SIZE_BITS;
impl From<usize> for PhysAddr {
fn from(v: usize) -> Self {
Self(v & ((1 << PA_WIDTH_SV39) - 1))
}
}
// 入参已去除page offset的影响
impl From<usize> for PhysPageNum {
fn from(v: usize) -> Self {
Self(v & ((1 << PPN_WIDTH_SV39) - 1))
}
}
impl From<usize> for VirtAddr {
fn from(v: usize) -> Self {
Self(v & ((1 << VA_WIDTH_SV39) - 1))
}
}
// 入参已去除page offset的影响
impl From<usize> for VirtPageNum {
fn from(v: usize) -> Self {
Self(v & ((1 << VPN_WIDTH_SV39) - 1))
}
}
rCore使用上述函数实现usize转换至物理地址、物理地址页码、虚拟地址、虚拟地址页码的操作,需要注意的是,usize转换至物理地址页码以及虚拟地址页码时,已经去除了偏移量的影响。
3. 地址/地址页码转usize
3.1 虚拟地址转usize
需要注意,在 Sv39 中,定义物理地址(Physical Address)有 56位,而虚拟地址(Virtual Address) 有 39位。实际使用的时候,一个虚拟地址要占用 64位,只有低 39位有效,规定 63−39 位的值必须等于第 38 位的值(类似有符号整数),否则会认为该虚拟地址不合法,在访问时会产生异常。
因此,在尝试将虚拟地址转换为usize时,需要进行符号位扩展,保证uszie的 63-39位与第38位数值相等,具体代码如下所示:
impl From<VirtAddr> for usize {
fn from(v: VirtAddr) -> Self {
if v.0 >= (1 << (VA_WIDTH_SV39 - 1)) {
v.0 | (!((1 << VA_WIDTH_SV39) - 1))
} else {
v.0
}
}
}
代码解释
v.0 >= (1 << (VA_WIDTH_SV39 - 1))
:VA_WIDTH_SV39
是 Sv39 虚拟地址模式下的虚拟地址宽度,通常为39位。1 << (VA_WIDTH_SV39 - 1)
计算出虚拟地址的最高有效位(第38位)的值。如果v.0
的第38位为1,表示这是一个负数地址。
v.0 | (!((1 << VA_WIDTH_SV39) - 1))
:1 << VA_WIDTH_SV39
计算出一个只有第39位为1,其余位为0的数。((1 << VA_WIDTH_SV39) - 1)
计算出一个低39位全为1,其余位为0的数。!((1 << VA_WIDTH_SV39) - 1)
计算出一个高25位全为1,低39位全为0的数(因为64 - 39 = 25)。v.0 | (!((1 << VA_WIDTH_SV39) - 1))
将v.0
的高25位设置为1,完成符号扩展。
else { v.0 }
:如果
v.0
的第38位为0,表示这是一个非负数地址,直接返回v.0
。