6.828Lab2
Lab2: Memory Management
Intro:
在本实验中,您将为您的操作系统编写内存管理代码。内存管理有两个组件。
第一个组件是内核的物理内存分配器,以便内核可以分配内存并在以后释放它。您的分配器将以 4096 字节为单位运行,称为 pages。您的任务是维护数据结构,记录哪些物理页面是空闲的,哪些是已分配的,以及有多少进程共享每个分配的页面。您还将编写例程来分配和释放内存页面。
内存管理的第二个组成部分是虚拟内存,它将内核和用户软件使用的虚拟地址映射到物理内存中的地址。x86 硬件的内存管理单元 (MMU) 在指令使用内存时执行映射,并参考一组页表。您将根据我们提供的规范修改 JOS 以设置 MMU 的页表。
boot_map_region映射的物理页不改变对应的pp_ref,一个物理页被这个函数映射与它是否被使用没有任何关系;而通过page_insert映射的物理页,同时就表示了该物理页被使用了一次,要给pp_ref加1。
切换到lab2
memlayout.h
描述了您必须通过修改pmap.c
来实现的虚拟地址空间的布局。
memlayout.h
和pmap.h
定义了PageInfo
用于跟踪哪些物理内存页面空闲的结构。
kclock.c
and kclock.h
manipulate the PC’s battery-backed clock and CMOS RAM hardware, in which the BIOS records the amount of physical memory the PC contains, among other things.
pmap.c
中的代码需要读取这个设备硬件,以便计算出有多少物理内存,但是这部分代码是为您完成的:您不需要了解 CMOS 硬件如何工作的细节。
请特别注意memlayout.h
和pmap.h
,因为本实验要求您使用并理解它们包含的许多定义。您可能还想查看inc/mmu.h
,因为它还包含许多对本实验有用的定义。
在开始实验之前,不要忘记add -f 6.828获取 6.828 版本的 QEMU。
Part 1: Physical Page Management
您现在将编写物理页分配器。它通过对象的链接列表跟踪哪些页面是空闲的struct PageInfo
(与 xv6 不同,这些对象不嵌入空闲页面本身),每个对象对应一个物理页面。您需要先编写物理页面分配器,然后才能编写其余的虚拟内存实现,因为您的页表管理代码将需要分配物理内存来存储页表。
Exercise 1
在文件kern/pmap.c
中,您必须实现以下函数的代码(可能按照给定的顺序)。
1 | boot_alloc() |
check_page_free_list() 和 check_page_alloc()
测试您的物理页面分配器。
您应该启动 JOS 并查看是否check_page_alloc()
报告成功。
Fix您的代码以使其通过。You may find it helpful to add your own assert()
s to verify that your assumptions are correct.
boot_alloc() 是一个内存分配器
函数的核心是维护一个静态变量nextfree,代表下一个可以使用的空闲内存空间的虚拟地址
1 | static void * |
mem_init() 需要我们设置一个两层的页表
1 | void |
page_init() 初始化页面结构和内存空闲列表。
1 | void |
page_alloc() 完成页面的分配。
分配是基于PageInfo的,只是把页面标记为使用,并未真正的分配页面。
1 | // page2kva: page to kernel virtual address |
page_free()
释放一个页面,到page_free_list中
(This function should only be called when pp->pp_ref reaches 0.)
1 | // Return a page to the free list. |
使用 make qemu-nox
运行,发现报了个panic, 需要把panic注释掉。看漏了
Part 2: Virtual Memory
虚拟内存
当 cpu 拿到一个地址并根据地址访问内存时,在 x86架构下药经过至少两级的地址变换:段式变换和页式变换。分段机制的主要目的是将代码段、数据段以及堆栈段分开,保证互不干扰。分页机制则是为了实现虚拟内存。
虚拟内存主要的好处是:
让每个程序都以为自己独占计算机内存空间,概念清晰,方便程序的编译和装载。
通过将部分内存暂存在磁盘上,可以让程序使用比物理内存大得多的虚拟内存,突破物理内存的限制。
通过对不同进程设置不同页表,可以防止进程访问其他进程的地址空间。通过在不同进程之间映射相同的物理页,又可以提供进程间的共享。
虚拟、线性和物理地址
虚拟地址
最原始的地址,也是 C/C++ 指针使用的地址。由前 16bit 段 (segment) 选择器和后 32bit 段内的偏移 (offset) 组成,显然一个段大小为 4GB。通过虚拟地址可以获得线性地址。
线性地址
前 10bit 为页目录项(page directory entry, PDE),即该地址在页目录中的索引。中间 10bit 为页表项(page table entry, PTE),代表在页表中的索引,最后 12bit 为偏移,也就是每页 4kB。通过线性地址可以获得物理地址。
页目录偏移DIR |页表偏移Table|页内偏移Offset
物理地址
经过段转换以及页面转换,最终在 RAM 的硬件总线上的地址。
JOS只有一个段,因此虚拟地址在数值上等于线性地址。
Exercise 4
pgdir 是指向页目录的指针。
pgdir_walk() returns a pointer to page table entry(PTE) for linear address(va)。查找一个虚拟地址对应的页表项地址。
在页目录项、页表项中存储的是页表项的物理地址前 20bit 外加 12bit 的 flag。
1 | // Given 'pgdir', a pointer to a page directory, pgdir_walk returns |
page_lookup()
// 返回映射到虚拟地址 va 的页面
// pgdir_walk 只查询,不创建,create为0
// pa2page 由物理地址 返回对应的页面描述
1 | Map [va, va+size) of virtual address space to physical [pa, pa+size) in the page table rooted at pgdir. Size is a multiple of PGSIZE, and va and pa are both page-aligned. |
1 | // Return the page mapped at virtual address 'va'. |
page_remove()
移除一个虚拟地址与对应物理地址的映射关系
1 | // |
page_insert()
建立一个虚拟地址与物理页的映射,与page_remove() 对应。
The permissions (the low 12 bits) of the page table entry should be set to ‘perm|PTE_P’.
requirement:
// 如果已经有一个页面映射到’va’,it should be page_remove()d.
// 如果有必要,应按需分配一个页表,并插入到’pgdir’。
// 如果插入成功,pp->pp_ref应该加1。
// TLB必须无效,如果 ‘va’ 对应的一个页面已经存在。
1 | // 建立一个虚拟地址与物理页的映射,与page_remove() 对应 |
boot_map_region()
映射一片虚拟页到制定物理页,大小为size, size是PGSIZE的倍数
1 | // |
Part 3: Kernel Address Space
JOS将处理器的32位线性地址划分为 用户地址() 和 内核地址(),二者以ULIM划分。
计算可得出一个物理页大小是4MB
ULIM = (MMIOLIM - PTSIZE) = (KSTACKTOP - PTSIZE - PTSIZE) = 0xF0000000 - 0x00400000 - 0x00400000 = 0xef800000
查看memlayout.h 可以看到,的确为0xef800000
1 | ////////////////////////////////////////////////////////////////////// |
问题
此时,页面目录中填写了哪些条目(行)?他们映射什么地址,指向哪里?换句话说,尽可能多地填写这张表格:
入口 基本虚拟地址 指向(逻辑上): 1023 0xffc00000 page table for [252,256)MB of physical address 1022 0xff900000 page table for [248,252)MB of physical address … … … 960 0xf0000000 page table for [0,4)MB of physical address 959 0xefc00000 958 0xef800000 ULIM 957 0xef400000 State register (UVPT) 956 0xef000000 UPAGES, array of PageInfo 955 0xeec00000 UPAGES, array of PageInfo … … NULL 1 0x00400000 NULL 0 0x00000000 same as 960 1
2
3
4// User read-only virtual page table (see 'uvpt' below)
// Read-only copies of the Page structures我们已将内核和用户环境放置在同一地址空间中。为什么用户程序无法读取或写入内核的内存?哪些特定机制可以保护内核内存?
页表内的标记位可以设置权限,PTE_U设置为1,用户才有权利读写。
这个操作系统可以支持的最大物理内存量是多少?为什么?
UPAGES 大小是4096bytes,即4MB,每个结构体 PageInfo 占8bytes。指针占4字节,uint16_t占两字节,对齐后8字节。
那么共有 4MB / 8B = 2^19 页,
每页的大小PGSIZE = 4096 bytes
那么最多使用 2^19 * 4096 = 2^31 = 2GB 的物理内存
如果我们真的有最大数量的物理内存,有多少空间来管理内存?这个开销是怎么分解的?
为2GB的最大内存时,UPAGES的大小为4MB,page table directory 的大小为4MB, 一共8MB。
重新访问
kern/entry.S
和kern/entrypgdir.c
中的页面表设置。在我们打开分页后,EIP仍然是一个低数字(略高于1MB)。我们什么时候过渡到KERNBASE上方的EIP运行?when we enable paging and when we begin running at an EIP above KERNBASE,是什么使我们能够继续以低EIP执行?为什么需要这种过渡?EIP寄存器存储着CPU读取的下一条指令的地址,相当于PC计数器。在8086中,EIP=PC。
在jmp处打上断点,向后执行一步,产生了映射,分页机制启动
把虚拟地址的[0,4M) [KERNBASE, KERNBASE+4M)两个区间都映射到同一个物理地址区间[0,4M)的原因在于不要让指令的寻址受到地址空间变化的影响。
- Display in a useful and easy-to-read format all of the physical page mappings (or lack thereof) that apply to a particular range of virtual/linear addresses in the currently active address space. For example, you might enter
'showmappings 0x3000 0x5000'
to display the physical page mappings and corresponding permission bits that apply to the pages at virtual addresses 0x3000, 0x4000, and 0x5000.
1 | // 添加lab2 中的映射函数,以显示pa和va的对应关系 |