关于虚拟内存实现--你需要了解的事情(基于 xv6)
预备知识
- 了解操作系统分页的基本实现
使用的英文缩写
- 页目录索引: pte(page table entry)
- 虚拟地址: va
- 物理地址: pa
- 物理页号: ppn(Physical Page Number)
introduction
- 通过例题讲解分页的基本原理, 便于平缓过渡到 xv6 代码实现
- 借用 xv6 讲解分页的实现(仅讲解核心代码, 不了解 xv6 对阅读无影响) 做到知行合一
二级分页例题
- 页表大小: 32 bytes
- 物理内存: 128 page
page 0:1b1d05051d0b19001e00121c1909190c0f0b0a1218151700100a061c06050514
...
page 33:7f7f7f7f7f7f7f7fb57f9d7f7f7f7f7f7f7f7f7f7f7f7f7f7f7ff6b17f7f7f7f
page 53:0f0c18090e121c0f081713071c1e191b09161b150e030d121c1d0e1a08181100
page 108:83fee0da7fd47febbe9ed5ade4ac90d692d8c1f89fe1ede9a1e8c7c2a9d1dbff
...
page 127:7f7f7f7f7f7f7f7f7f7f7f7f7f7f7f7fdf7f7f7f7f7f7f7f7f7f7f7f7f957f7f
PDBR: 108 (decimal) [This means the page directory is held in this page]
求虚拟地址(va) 0x611c 对应的物理地址并求出该物理地址中存放的值是多少?
提示: va 以及 pte 结构如下图(看一下自己是否可以推导出来)
解析
- 首先定位到二级目录的位置: PDBR: 108, 结合 va 前五位(pte 偏移量), 得下一级目录在 page33
pde index:0x18 [decimal 24] pde contents:0xa1 (valid 1, pfn 0x21 [decimal 33])
- 按照 1. 的逻辑, 定位出物理地址的 page(page 33 结合 va 中间 5 位)
pte index:0x8 [decimal 8] pte contents:0xb5 (valid 1, pfn 0x35 [decimal 53])
- 物理 page 结合物理地址偏移(va 后 5 位), 即可得出物理地址中存放的值
Translates to Physical Address 0x6bc --> Value: 0x08
上述过程图解如下
核心的逻辑在于: pte 中只存储相应物理内存的页号, va 中只存储相对应的偏移量
xv6 分页代码讲解
riscv 中 va, pa, pte page 的结构
riscv 页表为三极结构
- 页大小: 4kb
- va: 64 位, 低 offset 12 位; 三级目录索引, 每级各 9 位; 剩下高位与分页无关
- pa: 64 位, 低 offset 12 位; FFN 44 位; 剩下高位与分页无关
- pte: 64 位, 低 权限位 10 位; FFN 44 位; 剩下高位与分页无关
具体结构如下图
千呼万唤始出来-xv6 中分页的建立
创建分页流程
graph TD
创建二级pagetable --> 循环创建一级以及0级pagetable --> 将其pte填入上一级的pagetable中 --> 最后将物理地址填入上一级的pagetable中
创建二级pagetable
调用kalloc创建二级目录作为根目录(8 * 512 = 4096 bytes)并初始化其内存单元(全为0)
kpgtbl = (pagetable_t) kalloc();
memset(pagetable, 0, PGSIZE);
调用mappages进行va与pa的map(kernel/vm.c)
mappages(pagetable_t pagetable, uint64 va, uint64 size, uint64 pa, int perm)
-
获取 va, va + size - 1(va 最后一个内存单元) 的页表号(去掉 offset),
c a = PGROUNDDOWN(va); last = PGROUNDDOWN(va + size - 1); -
调用
walk创建 va->pa 的映射(注意: 这里只创建映射, 不负责分配 pa 的内存空间), 返回 0 级页表的 pte -
对于 pte 进行相关校验处理
-
将 pa->pte 并添加状态位, 存储在 pte 中
c *pte = PA2PTE(pa) | perm | PTE_V; -
该页表映射处理完毕, 判断是否需要继续映射(size > PAGSIZE)
C if(a == last) break; a += PGSIZE; pa += PGSIZE;
调用walk 函数创建多级目录
循环创建多级目录(结合 va 偏移), 这里以 level = 2 为例
- 宏 px: 获得二级 pte 偏移量
- 求出 1 级 pte 在二级页表中的物理地址
- 查看该 pte 是否在二级页表中, 由于这是开始创建页表映射, 故一定不在, 那么创建一级页表
- 将新创建出的 pagetable(注意这里创建出来的是一整张页表, 故其返回的物理地址偏移量一定为 0) 物理地址进行映射为 pte, 放入上一级页表中(8 字节)
- 最后将实际的物理地址页表号放入 0 级页表中
至此完成了映射