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 之上。