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

以下是代码实现 image

实现 mem_init 每一个物理也都需要一个结构体来管理,这个结构体就是 PagInfo, 在 mem_init() 中我们需要分配内存来存储这些用来管理物理页的结构体。 代码实现: image

实现 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() 分配出去的。 代码实现: image 实现 page_alloc()

  • 分配一个物理页,如果 alloc_flags & ALLOC_ZERO 为真,将所有分配的物理内存用’\0’填充,不需要修改 pp_ref, 让调用者来做这件事情。
  • 确保已经分配的内存的 pp_link 为 NULL
  • 如果内存不足返回 NULL
  • 提示:使用 page2kva 和 memset

代码实现: image

实现 page_free() 将一个页放回 page_free_list(当 pp_ref 为 0 时,这个函数才能被调用) 当 pp_ref 不为 0 或者 pp_link 不为 NULL 是函数需要 panic 代码实现 image

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 查看内存映射 image

image

Question 1

假设下面的代码是正确的,那么 x 的类型是什么?uintptr_t 还是 physaddr_t?

1
2
3
4
mystery_t x;
	char* value = return_a_pointer();
	*value = 10;
	x = (mystery_t) value;

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 中有一些操作页表和页目录项又要的宏 代码实现: image 实现 boot_map_region() 这个函数将虚拟地址 [va, va+size) 映射到物理地址 [pa, pa+size) 代码实现: image 实现 page_lookup() 这个函数返回映射在虚拟地址 va 的一页,如果 pte_store 不是 0,我们把页目录项存到 pte_store,这是为了之后 page_remove() 能够检查权限。如果 va 没有映射到某一页返回空。 代码实现 image 实现 page_remove() 移除 va 的映射关系,如果 va 没有被映射,那么就什么都不做 注意的细节:

  • pp_ref 需要减一
  • 如果 pp_ref 减到 0 了那么物理页需要释放
  • va 对应的页目录项要置 0
  • 移除一个页目录项 TLB 会过期

代码实现 image

实现 page_insert() 将某一物理页 pp 映射到虚拟地址 va, 权限为设为 perm|PTE_P 要求:

  • 如果已经有一页映射到 va 了,那么要先用 page_remove() 移除
  • 如果必要的话,需要分配一个页表并插入页目录表
  • 如果插入成功,pp_ref 加一
  • TLB 会过期

考虑特殊情况,同一页被重复插入的情况 返回

  • 0 成功
  • -E_NO_MEM 如果分配页失败

代码实现: image

Part 3: Kernel Address Space

Exercise 5

补充 mem_init() 缺少的代码 image

至此,lab2 的代码部分全部完成 image

Question 2

页目录表中填充了什么? 根据 qemu 的输出: image

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

操作系统支持的最大内存是多少?为什么? imagepages 占用了 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 之上。