虚拟地址转物理地址
操作系统启动过程中,如何完成物理地址到虚拟地址的转换?虚拟地址又是如何翻译成物理地址的?
以qemu-system-riscv64为例,opensbi会将内核搬到内存0x80200000
的位置,如果内核的.ld链接文件也是以0x80200000
开始,那么人畜无害,操作系统一直在物理地址下运行,可以作为RTOS简单控制。但如果要开启虚拟地址,要做什么操作呢?
1. 物理地址到虚拟地址的转换?
首先我们在链接的时候,需要修改.ld的基址,0x80200000
修改为想要内核运行的虚拟地址下,比如0xFFFFFFFFC0200000
。这样一来opensbi还是会将内核搬到0x80200000
的位置,如果继续执行,pc会跳转到一个0xFFFFFFFFC0200000
以上的位置,远远超过了物理内存地址,必然报错。那么还需要做什么设置呢?
开启虚拟内存。CSR中有个satp(Supervisor Address Translation and Protection Register)寄存器,它的结构如下:
最高4位表示寻址模式:
- 0000表示不适用页表,直接用物理地址,用于简单的嵌入式系统。
- 1000表示Sv39页表,也是接下来要说明的,虚拟内存空间为512GB。
- 1001表示Sv48,比Sv39多一级,虚拟内存空间为256TB。
将satp的模式切换到Sv39,并且设置指向首级页表的地址PPN,最后执行sfence.vma
刷新TLB,即可使用虚拟地址。
当然在此之前,还需要设置页表。页表可以在入口代码处提前设置,然后将其起始地址赋值给satp.PPN。
2. 虚拟地址如何转换为物理地址?
开启Sv39后,假设初始化函数的入口地址VA是0xffffffffc0200032
,该地址如何转换成物理地址呢?先看虚拟地址的结构图:
首先会访问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
,页表项的结构图如下:
- 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]