操作系统启动过程中,如何完成物理地址到虚拟地址的转换?虚拟地址又是如何翻译成物理地址的?

以qemu-system-riscv64为例,opensbi会将内核搬到内存0x80200000的位置,如果内核的.ld链接文件也是以0x80200000开始,那么人畜无害,操作系统一直在物理地址下运行,可以作为RTOS简单控制。但如果要开启虚拟地址,要做什么操作呢?

1. 物理地址到虚拟地址的转换?

首先我们在链接的时候,需要修改.ld的基址,0x80200000修改为想要内核运行的虚拟地址下,比如0xFFFFFFFFC0200000。这样一来opensbi还是会将内核搬到0x80200000的位置,如果继续执行,pc会跳转到一个0xFFFFFFFFC0200000以上的位置,远远超过了物理内存地址,必然报错。那么还需要做什么设置呢?

开启虚拟内存。CSR中有个satp(Supervisor Address Translation and Protection Register)寄存器,它的结构如下:

satp

最高4位表示寻址模式:

  • 0000表示不适用页表,直接用物理地址,用于简单的嵌入式系统。
  • 1000表示Sv39页表,也是接下来要说明的,虚拟内存空间为512GB。
  • 1001表示Sv48,比Sv39多一级,虚拟内存空间为256TB。

将satp的模式切换到Sv39,并且设置指向首级页表的地址PPN,最后执行sfence.vma刷新TLB,即可使用虚拟地址。

当然在此之前,还需要设置页表。页表可以在入口代码处提前设置,然后将其起始地址赋值给satp.PPN。

2. 虚拟地址如何转换为物理地址?

开启Sv39后,假设初始化函数的入口地址VA是0xffffffffc0200032,该地址如何转换成物理地址呢?先看虚拟地址的结构图: image.png

首先会访问satp获得页表的基地址,比如0x80205,将该地址左移12位后,和虚拟地址的VPN[2]左移3位相加(一个页表项占8字节),以0xffffffffc0200032为例,VPN[2]=111111111,VPN[1]=000000001,VPN[0]=000000000,page offset=000110010。

第一级页表项位置:0x80205<<12 + 111111111<<3 = 0x80205ff8,页表项的结构图如下: image.png

  • V为Valid位,D位Dirty位,A为Accessed位
  • RWX表示可读,可写,可执行位
  • RWX如果全0,那么该页表项需要指向下一级页表

假设0x80205ff8的页表项PTE已设置,PTE.PPN=0x80000,PTE.XWRV=1111,那么该页表项不是指向下一级页表,可以直接转换物理地址,这时需要不但需要虚拟地址的page offset,还需要VPN[1]和VPN[0]:

PA = 0x80000<<12 + VA[29:0] = 0x80200032

即完成了虚拟地址和物理地址的转换。

3. 小结

物理地址切换虚拟地址模式:

  • 分配页表内存空间,初始化页表;
  • 设置页基址寄存器satp;
  • 刷新TLB

虚拟地址转换物理地址:

  • 第一级页表项PTE1 = satp.PPN<<12 + VA.VPN2<<3 ;如果可RWX,物理地址PA=PTE1.PPN<<12+VA[29:0],如果RWX全0,进入下一级。
  • 第二级页表项PTE2 = PTE1.PPN<<12 + VA.VPN1<<3;如果可RWX,物理地址PA=PTE2.PPN<<12+VA[20:0],如果RWX全0,进入下一级。
  • 第三级页表项PTE3 = PTE2.PPN<<12 + VA.VPN0<<3;如果可RWX,物理地址PA=PTE3.PPN<<12+VA[11:0]

标签: riscv, mmu

添加新评论