rust写个操作系统——课程实验blogos移至armV8深度解析:实验八下 内存管理
你将在每个实验对应分支上都看到这句话,确保作者实验代码在被下载后,能在正确的环境中运行。
运行环境请参考: lab1 环境搭建
cargo build |
实验八 内存管理(下)
第二部分:非identity mapping映射(内核置于下半部分-原始地址,外设置于虚拟页-0xffffffff00000000开始的二级页表处)
实验目的
实验指导书这么写的,全面理解分页式内存管理的基本方法以及访问页表,完成地址转换等的方法。这部分我也不太能搞懂,所以还是按它说的来吧。
非identity mapping映射:块映射
dentity mapping
毕竟过于简单,在实际的系统上并不实用,但也不是完全没有用途。如arm规定在启用地址映射时最好采用identity mapping
。
我们首先重写链接脚本,重新调整内核内存的分布结构:
__KERN_VMA_BASE = 0xfffffff000000000; |
相比于原先的链接脚本,我们重新定义了内核的入口为0x40010000
处,并将内核的虚拟地址空间结构定义在了0xfffffff000000000 + 0x40010000
。在mmu还未启用前,程序仍然会先进入src/start.s
的_start
函数部分(老师似乎使用了一些技巧使得入口不变,这边我不太理解,可能是恰当的增长内核结构空间使得0x40080000
移到了0x40010000
)。然后在链接脚本中对.rodata
, .data
, .bss
, .pt
段都进行了一次对齐操作。然后在接下来的4k内存空间中分别用于存储一级和二级页表。
但现在实现一二级页表还太过困难。我们先尝试将整个外设和内核的物理内存块映射到0xfffffff000000000
开始的上半空间中,这步和直接映射的偏移很像,这里不再说明。
/* 虚拟地址空间的上半部分处理 */ |
PERIPHERALS_ATTR
属性设置同直接映射,而KERNEL_ATTR
其实只是把直接映射的IDENTITY_MAP_ATTR
换了个名,这里不再赘述。
这里我们需要考虑的是,我们已经把外设和内核映射到了内存的上半空间了,我们的下半空间是否还需要映射外设和内核?自行尝试后发现是需要的。原因在于,启用了mmu后,按照virt的机器启动历程,仍然会访问虚拟地址0x40010000
而非0xfffffff40010000
,因此虚拟地址 1G -2G 部分仍然要映射到物理内存相同的位置中。
我们使用gdb调试来进行验证,可以看到0x0000000040010000 in ?? ()
,程序一开始访问的还是0x40010000
。这是由机器所决定的,无法从内核层面进行更改。
我们在gdb中输入b _start
,得到Breakpoint 1 at 0xfffffff040010000
,发现程序其实已经放在了虚拟地址空间的上半页。那么程序是在什么阶段从0x40010000
跳到了0xfffffff40010000
?
回到我们实际一点的物理地址,无论是0x40010000
还是0xfffffff40010000
,物理地址上都是一样的,但是程序其实已经根据链接脚本把_start
判断成虚拟地址的0xfffffff40010000
(实际它也同时存在于0x40010000
)。当我们不使用gdb时,他会自然而然的认为程序一开始就已经运行在0xfffffff40010000
(实际上并不是,毕竟入口是0x40010000
)。顺其自然的运行下去似乎没什么问题,程序就跑在上半空间了,下半空间的 1G - 2G 空间可以顺其自然的被丢弃。所以下半空间的映射内存是必要的,但它只被使用了一瞬间。
不过如果想尝试gdb单步运行会发现是不能的,gdb理解不能为什么0x40010000能继续往下跑(我不知道是不是它觉得下个地址非法)
所以最终我们的src/start.s
如下:
.extern LD_STACK_PTR |
由于我们更改了外设的地址,所以几个驱动文件中也要相应的更改外设的基址:
// interrupts.rs |
编译内核并运行
cargo build |
屏幕上能够正常输出[0] Hello from Rust!
并正常打点即说明成功实现了块映射。
非identity mapping映射:分页映射
实现了块映射后,我们可以进一步尝试用分页来进行内存管理。链接脚本我们在上一步就已经做好了一二级页表基址的定义。所以我们主要考虑如何更改块映射部分。
这里我们希望的是,0xfffffff40010000
能通过分页完美的映射到物理内存的0x40010000
部分。
首先下半肯定是保持不变的,上节已经说明过理由,主要考虑的是上半空间的1g开始的部分。
我们令1g开始的部分指向二级页表所在的地址段,二级页表每页指向 1G 的物理地址空间。
以0xffffffff40010000
为例,一级页表大小为4k(由TCR_EL1_VALUE
定义)
按其定义,则我们采用的为36位地址空间。
VA[63:36]
的每一位都为1,故采用TTBR1
的页表基址页表包含8个页表条目,使用
VA[24:22]
编制索引,故拆分出索引为b000
VA[21:0]
为物理地址位,范围为0 - 2M(0x200000)
,即0x10000
不考虑物理地址位,我们需要把b000
再经过一次二级页表转换。
而一级页表的每一项指向16M的空间(也就是8页二级表),故将块映射代码替换如下:
/* |
二级页表只取高36位后放入一级页表中,然后我们将物理内存 1G - 2G 部分放入二级页表中。汇编代码通过循环构建好8个页表项。
最后src/start.s
为:
.extern LD_STACK_PTR |
编译内核并运行
cargo build |
屏幕上能够正常输出[0] Hello from Rust!
并正常打点说明我们分页部分的代码不会导致内存错误,但是由于没有涉及后面的二级条目(我们只用了第0页),所以不能说明内存分页操作无误。