SV39多级页表机制:内容介绍¶
本节导读¶
在上一小节中我们已经简单介绍了分页的内存管理策略,现在我们尝试在 RV64 架构提供的 SV39 分页机制的基础上完成内核中的软件对应实现。由于内容过多,我们将分成两个小节进行讲解。本节主要讲解在RV64架构下的虚拟地址与物理地址的访问属性(可读,可写,可执行等),组成结构(页号,帧号,偏移量等),访问的空间范围等;以及我们在OS中如何进行页表的处理。
虚拟地址和物理地址¶
内存控制相关的CSR寄存器¶
默认情况下 MMU 未被使能,此时无论 CPU 位于哪个特权级,访存的地址都会作为一个物理地址交给对应的内存控制单元来直接
访问物理内存。我们可以通过修改 S 特权级的一个名为 satp
的 CSR 来启用分页模式,在这之后 S 和 U 特权级的访存
地址会被视为一个虚拟地址,它需要经过 MMU 的地址转换变为一个物理地址,再通过它来访问物理内存;而 M 特权级的访存地址,我们可设定是内存的物理地址。
注解
M 特权级的访存地址被视为一个物理地址还是一个需要经历和 S/U 特权级相同的地址转换的虚拟地址取决于硬件配置,在这里我们不会进一步探讨。

上图是 RV64 架构下 satp
的字段分布。当 MODE
设置为 0 的时候,代表所有访存都被视为物理地址;而设置为 8
的时候,SV39 分页机制被启用,所有 S/U 特权级的访存被视为一个 39 位的虚拟地址,它们需要先经过 MMU 的地址转换流程,
如果顺利的话,则会变成一个 56 位的物理地址来访问物理内存;否则则会触发异常,这体现了该机制的内存保护能力。
虚拟地址和物理地址都是字节地址,39 位的虚拟地址可以用来访问理论上最大
地址格式与组成¶

我们采用分页管理,单个页面的大小设置为
地址转换是以页为单位进行的,在地址转换的前后地址的页内偏移部分不变。可以认为 MMU 只是从虚拟地址中取出 27 位虚拟页号, 在页表中查到其对应的物理页号(如果存在的话),最后将得到的44位的物理页号与虚拟地址的12位页内偏移依序拼接到一起就变成了56位的物理地址。
注解
RV64 架构中虚拟地址为何只有 39 位?
在 64 位架构上虚拟地址长度确实应该和位宽一致为 64 位,但是在启用 SV39 分页模式下,只有低 39 位是真正有意义的。
SV39 分页模式规定 64 位虚拟地址的
也就是说,所有
多级页表原理¶
页表的一种最简单的实现是线性表,也就是按照地址从低到高、输入的虚拟页号从

事实上,对于虚拟页号
线性表的问题在于:它保存了所有虚拟页号对应的页表项,但是高达
那么如何进行优化呢?核心思想就在于 按需分配 ,也就是说:有多少合法的虚拟页号,我们就维护一个多大的映射,并为此使用 多大的内存用来保存映射。这是因为,每个应用的地址空间最开始都是空的,或者说所有的虚拟页号均不合法,那么这样的页表 自然不需要占用任何内存, MMU 在地址转换的时候无需关心页表的内容而是将所有的虚拟页号均判为不合法即可。而在后面, 内核已经决定好了一个应用的各逻辑段存放位置之后,它就需要负责从零开始以虚拟页面为单位来让该应用的地址空间的某些部分 变得合法,反映在该应用的页表上也就是一对对映射顺次被插入进来,自然页表所占据的内存大小也就逐渐增加。
这种思想在计算机科学中得到了广泛应用:为了方便接下来的说明,我们可以举一道数据结构的题目作为例子。设想我们要维护
一个字符串的多重集,集合中所有的字符串的字符集均为
简单起见,假设

字典树由若干个节点(图中用椭圆形来表示)组成,从逻辑上而言每个节点代表一个可能的字符串前缀。每个节点的存储内容
都只有三个指针,对于蓝色的非叶节点来说,它的三个指针各自指向一个子节点;而对于绿色的叶子节点来说,它的三个指针不再指向
任何节点,而是具体保存一种可能的长度为
注意到如果某些字符串自始至终没有被插入,那么一些节点没有存在的必要。反过来说一些节点是由于我们插入了一个以它对应的字符串 为前缀的字符串才被分配出来的。如下图所示:

一开始仅存在一个根节点。在我们插入字符串 acb
的过程中,我们只需要分配 a
和 ac
两个节点。
注意 ac
是一个叶节点,它的 b
指针不再指向另外一个节点而是保存字符串 acb
的计数。
此时我们无法访问到其他未分配的节点,如根节点的 b/c
或是 a
节点的 a/b
均为空指针。
如果后续再插入一个字符串,那么 至多分配两个新节点 ,因为如果走的路径上有节点已经存在,就无需重复分配了。
这可以说明,字典树中节点的数目(或者说字典树消耗的内存)是随着插入字符串的数目逐渐线性增加的。
读者可能很好奇,为何在这里要用相当一部分篇幅来介绍字典树呢?事实上 SV39 分页机制等价于一颗字典树。
这种页表实现被称为 多级页表 (Multi-Level Page-Table) 。由于 SV39 中虚拟页号被分为三级 页索引 (Page Index) ,因此这是一种三级页表。
非叶节点的页表项标志位含义和叶节点相比有一些不同:
当 V 为 0 的时候,代表当前指针是一个空指针,无法走向下一级节点,即该页表项对应的虚拟地址范围是无效的;
只有当V 为1 且 R/W/X 均为 0 时,表示是一个合法的页目录表项,其包含的指针会指向下一级的页表。
注意: 当V 为1 且 R/W/X 不全为 0 时,表示是一个合法的页表项,其包含了虚地址对应的物理页号。
在这里我们给出 SV39 中的 R/W/X 组合的含义:
注解
大页 (Huge Page)
本教程中并没有用到大页的知识,这里只是作为拓展,不感兴趣的读者可以跳过。
事实上正确的说法应该是:只要 R/W/X 不全为 0 就会停下来,直接从当前的页表项中取出物理页号进行最终的地址转换。 如果这一过程并没有发生在多级页表的最深层,那么在地址转换的时候并不是直接将物理页号和虚拟地址中的页内偏移接 在一起得到物理地址,这样做会有问题:由于有若干级页索引并没有被使用到,即使两个虚拟地址的这些级页索引不同, 还是会最终得到一个相同的物理地址,导致冲突。
我们需要重新理解将物理页号和页内偏移“接起来”这一行为,它的本质是将物理页号对应的物理页帧的起始物理地址和
页内偏移进行求和,前者是将物理页号左移上页内偏移的位数得到,因此看上去恰好就是将物理页号和页内偏移接在一起。
但是如果在从多级页表往下走的中途停止,未用到的页索引会和虚拟地址的
在 SV39 中,如果使用了一级页索引就停下来,则它可以涵盖虚拟页号的前
使用大页的优点在于,当地址空间的大块连续区域的访问权限均相同的时候,可以直接映射一个大页,从时间上避免了大量
页表项的索引和修改,从空间上降低了所需节点的数目。但是,从内存分配算法的角度,这需要内核支持从物理内存上分配
三种不同大小的连续区域(
那么 SV39 多级页表相比线性表到底能节省多少内存呢?这里直接给出结论:设某个应用地址空间实际用到的区域总大小为
注解
分析 SV39 多级页表的内存占用
我们知道,多级页表的总内存消耗取决于节点的数目,每个节点
则需要一个大小为
每映射一个
的虚拟页面,最多需要新分配两个物理页帧来保存新的节点,加上初始就有一个根节点, 因此消耗内存不超过 ;考虑已经映射了很多虚拟页面,使得根节点的
个孩子节点都已经被分配的情况,此时最坏的情况是每次映射 都需要分配一个不同的最深层节点,加上根节点的所有孩子节点并不一定都被分配,从这个角度来讲消耗内存不超过 。
虽然这两个上限都可以通过刻意构造一种地址空间的使用来达到,但是它们看起来很不合理,因为它们均大于
上面主要是对一个固定应用的多级页表进行了介绍。在一个多任务系统中,可能同时存在多个任务处于运行/就绪状态,它们的多级页表
在内存中共存,那么 MMU 应该如何知道当前做地址转换的时候要查哪一个页表呢?回到 satp CSR 的布局 ,
其中的 PPN 字段指的就是多级页表根节点所在的物理页号。因此,每个应用的地址空间就可以用包含了它多级页表根节点所在物理页号
的 satp
CSR 代表。在我们切换任务的时候, satp
也必须被同时切换。
最后的最后,我们给出 SV39 地址转换的全过程图示来结束多级页表原理的介绍:
