6.828 lab2 Memory Management
Contents
Part 1: Physical Page Management
Exercise 1
实现 kern/pmap.c 中的下列四个函数 boot_alloc() mem_init() page_init() page_alloc() page_free()
首先实现 boot_alloc(), 根据注释,实现三点
- 如果 n > 0 分配 n 个字节的物理内存,不需要初始化
- 如果 n = 0 直接将下个可用物理内存的地址返回,不需分配
- 如果内存不足,boot_alloc() 会 panic
以下是代码实现
实现 mem_init 每一个物理也都需要一个结构体来管理,这个结构体就是 PagInfo, 在 mem_init() 中我们需要分配内存来存储这些用来管理物理页的结构体。 代码实现:
实现 page_init 现在 page_init 中代码是将所有的内存标记未被使用的,事实是有些内存已经被使用了,那些内存是未被使用的呢?
- 首先第 0 页需要标记未已使用,我们保留这一页,因为这里是之前的加载实模式中断向量表以及 bios 结构体的地方(虽然我们现在不用)
- 其他的 base memory,[PGSIZE, npages_basemem * PGSIZE) 是未被使用的
- 接下来的 io 空洞 [IOPHYSMEM, EXTPHYSMEM), 这里不能分配出去
- extended memory 部分 [EXTPHYSMEM, …),这里有些已经使用了,有些没被使用,kernel 在物理内存的哪个位置?那些页已经用来存在页表和其他的数据结构了呢?
这里主要是第四点,内核代码在物理内存的位置在之前 lab1 中就已经知道了,我们不必关系哪些页已经被使用了,我们只需要知道哪些也是未被使用的就可以了,通过 boot_alloc(0) 我们可以拿到可以使用内存的起始地址,因为之前页表和其他结构占用的物理内存都是通过 boot_alloc() 分配出去的。 代码实现: 实现 page_alloc()
- 分配一个物理页,如果 alloc_flags & ALLOC_ZERO 为真,将所有分配的物理内存用’\0’填充,不需要修改 pp_ref, 让调用者来做这件事情。
- 确保已经分配的内存的 pp_link 为 NULL
- 如果内存不足返回 NULL
- 提示:使用 page2kva 和 memset
代码实现:
实现 page_free() 将一个页放回 page_free_list(当 pp_ref 为 0 时,这个函数才能被调用) 当 pp_ref 不为 0 或者 pp_link 不为 NULL 是函数需要 panic 代码实现
Part 2: Virtual Memory
Exercise 2
看一下 Intel 80386 Reference Manual 了解段页式内存管理
Exercise 3
GDB 打印的是虚拟地址,要想看到真相的物理地址要使用 qemu monitor, 在 qemu 命令行中按住 Ctrl-a c,使用命令 xp, 然后在 gdb 中用命令 x, 观察 qemu 中的物理地址和 gdb 中的虚拟地址,打有课程补丁的 qemu 还可以使用命令 info pg 查看页表,info mem 查看内存映射
Question 1
假设下面的代码是正确的,那么 x 的类型是什么?uintptr_t 还是 physaddr_t?
|
|
x 应该是虚地址,类型是 uintptr_t
Exercise 4
实现 kern/pmap.c 中的下面几个函数 pgdir_walk() boot_map_region() page_lookup() page_remove() page_insert()
首先实现 pgdir_walk() 给定一个页目录地址 pgdir,和一个线性地址,返回一个指向这个页表项的指针,页表项映射到线性地址,这需要经过二级页表结构 相关的页表页可能还不存在,如果页表页不存在并且 create==false, 则返回 NULL, 否则的话分配一页,如果分配失败返回 NULL, 否则的话 pp_ref 加 1,pgdir_walk() 返回新分配页的地址。 提示 1:使用 page2pa() 将 PageInfo 装换为物理地址 提示 2:页目录和页表的权限 mmu 都会检查,所以页目录的权限不需要那么严格 提示 3:inc/mmu.h 中有一些操作页表和页目录项又要的宏 代码实现: 实现 boot_map_region() 这个函数将虚拟地址 [va, va+size) 映射到物理地址 [pa, pa+size) 代码实现: 实现 page_lookup() 这个函数返回映射在虚拟地址 va 的一页,如果 pte_store 不是 0,我们把页目录项存到 pte_store,这是为了之后 page_remove() 能够检查权限。如果 va 没有映射到某一页返回空。 代码实现 实现 page_remove() 移除 va 的映射关系,如果 va 没有被映射,那么就什么都不做 注意的细节:
- pp_ref 需要减一
- 如果 pp_ref 减到 0 了那么物理页需要释放
- va 对应的页目录项要置 0
- 移除一个页目录项 TLB 会过期
代码实现
实现 page_insert() 将某一物理页 pp 映射到虚拟地址 va, 权限为设为 perm|PTE_P 要求:
- 如果已经有一页映射到 va 了,那么要先用 page_remove() 移除
- 如果必要的话,需要分配一个页表并插入页目录表
- 如果插入成功,pp_ref 加一
- TLB 会过期
考虑特殊情况,同一页被重复插入的情况 返回
- 0 成功
- -E_NO_MEM 如果分配页失败
代码实现:
Part 3: Kernel Address Space
Exercise 5
补充 mem_init() 缺少的代码
至此,lab2 的代码部分全部完成
Question 2
页目录表中填充了什么? 根据 qemu 的输出:
Entry | Base Virtual Address | Points to (logically) |
---|---|---|
1023 | 0xffc00000 | Page table for top 4MB of physical memory |
1022 | 0xff800000 | Page table for second top 4MB of physical memory |
… | … | … |
960 | 0xf0000000 | Page table for first 4MB of physical memory |
959 | 0xefc00000 | First 8 PDEs are page table for bootstack |
957 | 0xef400000 | Page directory itself |
956 | 0xef000000 | pages data structure |
955 | 0xeec00000 | Unmapped |
… | … | Unmapped |
2 | 0x00800000 | Unmapped |
1 | 0x00400000 | Unmapped |
0 | 0x00000000 | Unmapped |
Question 3
我们把用户环境和内核放在同一地址空间,为什么用户不会读写内核的内存,是什么特殊的机制保护了内核内存 因为用户,和内核的权限是不一样的,mmu 映射内存钱会检查 PDE 和 PTE 的权限位,如果权限不足会产生页错误
Question 4
操作系统支持的最大内存是多少?为什么? pages 占用了 4M 内存 sizeof(struct PageInfo)=8, 所以有 4M/8 页,每页大小为 4k, 所以最大的内存是 4M/8*4K=2G
Question 5
超出的内存空间是多少?真正可用的空间能否达到最大?内存超出是怎样发生的? 当没有空的页目录项时,内存超出,超出的大小为,页目录表的大小加页表的大小 页目录表大小:1024 entries * 4 bytes = 4 KB 页表大小:1024 PTs * 1024 entries * 4 bytes = 4 MB 总共是 4MB + 4KB = 4100 KB
Question 6
什么是 eip 指向 kernbase 之上这个转换发生了?在开启分页和 eip 指向 kernbase 之上这之间为什么 eip 能够运行在地址?为什么这个转换是必要的? 转换发生在跳转到$relocated 时,在加载页目录表之前,其实内核开启了分页,虚地址 [0,4M] 和 [KERNBASE,KERNBASE+4M] 都被映射到了物理内存 [0,4M], 当加载了页目录表之后虚地址 [0,4M] 不再是映射到物理内存 [0,4M] 了,所以需要将 eip 指向 KERNBASE 之上。