虚拟内存
Virtual, Linear and Physical Addresses
在x86体系中
- 一个虚拟地址由段选择子(segment selector)和段内偏移(segment offset)组成
- 一个线性地址由段地址转换机构(segment translation)将虚拟地址进行转换后得到
- 一个物理地址由分页地址转换机构(page translation)将线性地址转换后得到,这个地址最终会送到地址总线上
Selector +--------------+ +-----------+
---------->| | | |
| Segmentation | | Paging |
Software | |-------->| |----------> RAM
Offset | Mechanism | | Mechanism |
---------->| | | |
+--------------+ +-----------+
Virtual Linear Physical
Exercise2:
- 阅读Intel 80386 Reference Manual的5.6两章
C 指针是虚拟地址的“偏移”部分。在 boot/boot.S
中,通过全局描述符表 (GDT)将所有段基地址设置为 0 并设定界限为 0xffffffff 来有效地禁用段转换。因此 segment selector 没有作用,线性地址总是等于虚拟地址的偏移量。在lab2只需要关注 Page translation
。
在lab1的part3中,将从 0x0010|0000
的 4MB 物理内存映射到了 0xf010|0000
开始的虚拟地址,在这次实验会将物理空间的头256MB内存映射到(从 0xf000|0000
开始的虚拟地址上 和 一些其他的虚拟内存空间)。
Exercise3:
- 在 QEMU monitor commands 页面熟悉 qemu 的操作命令
- xp/Nx paddr: 查看物理地址 paddr 处保存连续的 N个 words
- info registers: 查看寄存器
- info mem: 查看映射的虚拟内存地址和它们的访问权限
- info pg: 查看当前的页表结构:虚拟地址范围;区分是页目录条目(PDE)还是页表条目(PTE);访问权限;物理地址范围
一旦进入了保护模式(which we entered first thing in boot/boot.S
),所以对内存的引用都会被视作虚拟地址,然后由MMU进行翻译。所以所有C指针都是虚拟地址。
JOS 内核经常需要将地址按照一种模糊的值或整数值来进行操作,而不是进行解引用(dereference)。比如MMU,肯定会涉及对VA(virtual address)和PA(physical address)的操作,那么应该如何区分呢?JOS源码使用 uintptr_t, phyaddr_t
分别代表VA,PA。但它们实际上都是 uint32_t
,所以是可以互相赋值的。
可以通过强制类型转换(casting)来将 uintptr_t
转换为指针然后解引用,虽然 `phyaddr_t` 也可以这么操作,但是解引用的地址并不是真实的物理地址指向的内存,此时该物理地址被当作虚拟地址,因此是不合理的操作。
所以可以直接回答lab2的question1: x 的类型是 uintptr_t
JOS的内核有时候需要读写内存,但是却只知道物理地址。但是内核并不能直接操作物理地址,因此需要将此物理地址转换为虚拟地址才行。比如JOS将从0x0开始的物理地址映射到了从0xf000 | 0000开始的虚拟地址,那么内核只需要将目前已知的物理地址+0xf000 | 0000就可以找到对应的虚拟地址,可以使用 KADDR(pa) 来完成这个加法操作。 |
反之由VA得到PA的操作可以由 PADDR(va)
来完成。
Reference counting
之后的lab经常会碰到多个VA映射到同一个物理页(physical page)的情况,需要跟踪此物理页对应的 struct PageInfo
的 pp_ref
字段,统计它被引用的次数。当 pp_ref = 0 时,就说明它可以被释放。通常来说,任意一个物理页p的pp_ref值等于它在所有的页表项中,被位于虚拟地址UTOP
之下的虚拟页所映射的次数(UTOP
之上的地址范围在启动的时候已经被映射完成了,之后不会被改动)。
当使用 page_alloc()
时需要注意它返回的 struct PageInfo
的 pp_ref
等于 0,因此如果对这个返回的页面做了任何事情,需要立马执行: pp_ref + 1
。
Page Table Management
着手开始编写管理页表的程序:包括插入和删除线性地址到物理地址的映射关系,以及创建页表等操作。
Exercise4:
- 在
kern/pmap.c
中,实现如下函数的代码:- pgdir_walk()
- boot_map_region()
- page_lookup()
- page_remove()
- page_insert()
- 之后 mem_init() 中的 check_page() 会测试这些页表管理程序