0%

rCore_Tutorial_CP4

rCore_Tutorial_CP4

摸了几天继续看一下,第四章要实现究极虚拟内存,这非常关键,也是操作系统中的一个很核心的部分。之前的批处理和多道处理系统虽然实现了硬件特权的限制和系统调用,但是并没有对应用空间进行隔离,导致用户应用必须以规定的形式分布在内存中,并且还可以读取和修改其他用户程序,甚至是内核数据的内容,并不能认为是一个足够安全的系统。

本章的目标就是实现虚拟内存的分配,内存分页和页表映射,实现内核与各用户进程间的隔离。通过一层虚拟地址,使得操作系统获得了对实际物理内存访问的完全控制,用户程序只能访问由操作系统提供的虚拟地址,进而实现内存空间的完全隔离

实现还是经典的页表,段表难维护碎片多。进程对内存的访问一般来说就是通过load等汇编指令进行的,在硬件层面提供一个MMU,并通过内核维护应用程序的页表,MMU访问页表对虚拟地址进行解析,顺便还能对页进行rwx等权限控制,进一步加强保护

SV39多级页表的硬件机制

好怪的地址范围,虚拟地址39位,物理地址56位。页面大小12位(4k)
虚拟页即为27位,页表项8bytes,如果顺序放的话直接1G页表,所以采用分级页表来降低空间消耗

由于页表项8b,一个页仅能放512个页表项,即9位,故27位虚拟页要划到三级页表里面去,如果页表项拉满的话,应该是$$4k+4k512+4k512^2$$,最后一项就一个G了。。。
这也是必然的,毕竟最后分页的结果必然是一个物理页对应一个页表项,所以如果全部分配的话占用的空间是会大于顺序页表的,但实际情况就在于不可能有程序使用这么大的地址空间,也就使得按需分配的情况下页表项所占空间大规模减小了(但是操作系统还要负责多级页表里每一级的内存分配,好麻烦哦。。。感觉会在内核里反复触发缺页中断)

根页表用一个内核态寄存器指过去,每个进程的根页表位置不一样,需要在操作系统层面对每个进程的根页表地址进行保管和切换。

实现部分好麻烦哦。。。

管理SV39多级页表

这里提到了有意思的内容,在启动虚拟内存后,用户态和内核态的访存都会被认为是虚拟地址,经过MMU进行一系列的硬件实现访问到物理内存。

这段真的让人头大,主要问题在于对于用户态程序,无需考虑物理地址的实现,一切均交由内核维护页表。而对于内核态程序,其同样运行在虚拟内存环境下,却需要同时对虚拟内存和物理内存进行管理,对物理内存的管理主要就体现在对页表的管理

这里有一些方案的介绍accessing page tables
cr3寄存器(riscv里面是叫satp)指向页表所在的物理地址,之后以此为基址,将虚拟地址分为多段偏移,对多级页表进行查询。内核若需要对页表项进行修改,则需要访问cr3指向的物理地址的对应偏移,或是下一级页表物理地址基址的偏移。但内核运行在虚拟地址上,因此,如果试图对页表项进行修改,则需要得到该物理地址与虚拟地址的映射关系,一一映射能较为简单的解决该问题。即将整个物理内存映射到对应的虚拟内存位置上以解决该问题

但需要注意的是,内核地址空间也是虚拟地址空间,而内核是无法在未产生页表前正常使用虚拟空间的,所以实际的实现是,内核在初始化时先实现对页表的映射和规划,再通过修改指示虚拟地址空间的寄存器启动虚拟空间

页表自映射则是通过另一种方法对页表进行访问,通过将页表最后一个entry指向自己,进而使用虚拟地址进行解析时对页表项完成访问,对于27位页长的三级页表而言(每页512个entry,27位分三级),若想访问三级页表,则将虚拟地址设置为511 511 511(即0x7ffffff,得到页后还需要通过偏移访问具体页表项),二级则为511 511 offset,一级即为511 offset2 offset3,上述链接中有较详细的图片描述
(但全映射会对后续直接访存也带来便利,页表自映射的话只解决了访问页表的问题)

内核与应用的地址空间

已经快要猪脑过载了。。。太难了8
这里应该是定义了逻辑段,也就是代码段,数据段之类的段,并将段插入到应用的地址空间,组成MemorySet。
在sv39结构下,可用地址只有高256G和低256G,内核的低256G保存了内核的代码段数据段等内容,并且需要完成一个对物理内存的全映射,高256G放了应用内核栈和一个用户态内核态转换跳板
对于用户态应用,低256G放数据等各段,高256G同样和内核相同在最高处放置进入内核的状态转换跳板,并在次高处放置上下文。只需将其设置为用户态不可访问即可防止出现意外事故

基于地址空间的分时多任务

提出了两种内核空间隔离的方法,方案A为当前教程的实现思路,建立独立的内核空间与用户空间隔离,并在trap时产生地址空间的转换;方案B为之前操作系统课和目前大部分系统的实现方案,将内核地址直接映射到用户空间的高位,所有用户进程的高位均映射到同一份内核地址上。方案B相较于方案A,在trap时无需发生地址空间的切换,在一定程度上减少了开销。文中指出方案B在进程切换时无需进行地址空间转换,由内核完成,但实际上进程切换时首先需要trap进内核,再恢复时回到用户态,实际上还是进行了地址空间的转换

但是方案B将用户态和内核态放在一起,就易遭受到类似于熔断的攻击,然后通过加强buff上kpti之类的手段进行防护

由于方案A中trap前后位于两个不同的地址空间,地址空间变换后pc并不会改变,所以此处是内核态和用户态的最高页均为跳板,即使页表发生了切换,但两个地址空间最高位的代码相同,不会出现紊乱,系统得以继续执行

总结

由于时间等各种乱七八糟因素,以及这章难度爆表,并没有看的非常认真。。。。
大致理解了虚拟内存的工作原理和一些实现框架。。。。但是细看实现实在是有点看不进去了。。。并且这里的内存布局还是有一点点的简陋,在用户空间的构造上,没有对堆空间进行布局分配。但是已经大致的完成了虚拟空间的构建和用户各程序与内核间的隔离。
比较需要注意的点就是虚拟地址与物理地址间寻址的方法,页表中存储的均为物理地址,在中断时要考虑内核地址与用户地址的差异,确保中断切换前后中断控制流能正常运行等

练习中提到那个时间系统调用不能用了,需要改造。但是时间系统调用不是硬件实现计数器硬件中断吗?和现在的虚拟地址空间有什么关系呢?无法理解呜呜呜太垃圾了