2团
Published on 2024-08-23 / 27 Visits
0
0

rCore学习—RISC-V的SV39多级页表机制下的虚拟地址和物理地址转换

1.前言

1.1 虚拟地址页码和物理地址页码定义

SV39 分页机制被启用后,所有 S/U 特权级的访存被视为一个 39 位的虚拟地址,它们需要先经过 MMU 的地址转换流程,如果顺利的话,则会变成一个 56 位的物理地址来访问物理内存。

sv39-va-pa.png

上图为RISC-V下SV39多级页表机制下的虚拟地址页码和物理地址页码格式示意图(Page Offset::页内偏移),可以得知虚拟地址页码的有效宽度为:38-12,物理地址页码的有效位数为:55-12。

1.2 页表项-PageTableEntry(PTE)

sv39-pte.png

上图为 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
        }
    }
}

代码解释

  1. v.0 >= (1 << (VA_WIDTH_SV39 - 1)):

    • VA_WIDTH_SV39 是 Sv39 虚拟地址模式下的虚拟地址宽度,通常为39位。

    • 1 << (VA_WIDTH_SV39 - 1) 计算出虚拟地址的最高有效位(第38位)的值。如果 v.0 的第38位为1,表示这是一个负数地址。

  2. 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,完成符号扩展。

  3. else { v.0 }:

    • 如果 v.0 的第38位为0,表示这是一个非负数地址,直接返回 v.0

4. 参考

SV39多级页表机制:OS实现


Comment