lab2的实验手册带着我们学习操作系统是如何处理内存管理的。lab2将内存管理划分为 物理内存管理、页表管理、内核地址空间划分三个部分。
lab2的学习目标:重点学习内存管理的相关知识,包括内存布局、页表结构、页映射
lab2的学习任务:完成内存管理的相关代码
在lab2中,完全可以跟着实验手册的节奏走,逐步完善内存管理的代码。不过在根据注释补充各个函数的时候,大概率是懵逼的,需要自己多看几遍,总结
环境准备:
实验 2 包含以下新的源文件:
memlayout.h
和 pmap.h
定义了 PageInfo
结构,用于跟踪哪些物理内存页是空闲的。
kclock.c
和 kclock.h
操作 PC 的电池时钟和 CMOS RAM 硬件,其中 BIOS 记录了 PC 所含的物理内存量等信息。
pmap.c
中的代码需要读取这些设备硬件,以计算出物理内存的容量,但这部分代码已经为你完成:你不需要了解 CMOS 硬件工作的细节。
请特别注意 memlayout.h
和 pmap.h
,因为本实验要求您使用并理解其中包含的许多定义。您可能还需要查看 inc/mmu.h
,因为其中包含的许多定义对本实验也很有用。
操作系统必须跟踪物理内存中哪些是空闲内存,哪些是当前正在使用的内存。JOS 以页面粒度管理 PC 的物理内存,这样它就可以使用 MMU 来映射和保护每一块已分配的内存。
现在你将编写物理页面分配器
来实现物理内存管理。它通过 struct PageInfo 对象的链表来跟踪哪些页面是空闲的,每个对象对应一个物理页面。我们要做的就是通过 PageInfo链表,实现对物理内存的申请、释放。
练习 1. 在 kern/pmap.c 文件中,您必须实现以下函数的代码(可能按照给出的顺序)。
boot_alloc()
mem_init()(只调用到 check_page_free_list(1))。
page_init()
page_alloc()
page_free()
check_page_free_list() 和 check_page_alloc() 对物理页面分配器进行测试。你应该启动 JOS 并查看 check_page_alloc() 是否报告成功。修改代码,使其通过测试。你可能会发现添加自己的 assert()s 来验证你的假设是否正确很有帮助。
为了逐步理解物理页面分配器的工作原理,我们就按照练习1的要求,逐个实现代码就好了,不过在那之前,我们来看一下lab1 最后的物理内存的情况和虚拟地址空间的映射情况:
只在初始化时使用,用来确定申请n子节内存后后,空闲内存的首地址(虚拟内存空间)是多少。
为了让JOS能够追踪空闲内存的首地址究竟是多少,这里使用一个全局变量 nextfree
来记录.
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result;
// nextfree 一开始的值应该是多少?当然是kernel.ld中的标号end所指的位置,即内核加载进内存后的尾部
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end, PGSIZE);
}
// 分配一块足够放下n个字节的地址块,然后将这个地址块的地址返回。
// 注意地址块必须按照 PGSIZE 对齐
result = nextfree;
// 更新nextfree
nextfree = ROUNDUP((char *)result + n, PGSIZE);
return result;
}
完事了之后,按照 练习1 的指引,我们看一眼 mem_init
mem_init 是用来初始化内存管理的函数。物存管理的部分在前面被处理,大致工作流程为:
// Set up a two-level page table:
// kern_pgdir is its linear (virtual) address of the root
//
// This function only sets up the kernel part of the address space
// (ie. addresses >= UTOP). The user part of the address space
// will be set up later.
//
// From UTOP to ULIM, the user is allowed to read but not write.
// Above ULIM the user cannot read or write.
void
mem_init(void)
{
uint32_t cr0;
size_t n;
// Find out how much memory the machine has (npages & npages_basemem).
i386_detect_memory();
// Remove this line when you're ready to test this function.
// panic("mem_init: This function is not finishedn");
//////////////////////////////////////////////////////////////////////
// create initial page directory.
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
memset(kern_pgdir, 0, PGSIZE);
//////////////////////////////////////////////////////////////////////
// Recursively insert PD in itself as a page table, to form
// a virtual page table at virtual address UVPT.
// (For now, you don't have understand the greater purpose of the
// following line.)
// Permissions: kernel R, user R
kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
//////////////////////////////////////////////////////////////////////
// Allocate an array of npages 'struct PageInfo's and store it in 'pages'.
// The kernel uses this array to keep track of physical pages: for
// each physical page, there is a corresponding struct PageInfo in this
// array. 'npages' is the number of physical pages in memory. Use memset
// to initialize all fields of each struct PageInfo to 0.
// Your code goes here:
pages =(struct PageInfo *) boot_alloc(sizeof(struct PageInfo)*npages);
memset(pages, 0, sizeof(struct PageInfo) * npages);
//////////////////////////////////////////////////////////////////////
// Now that we've allocated the initial kernel data structures, we set
// up the list of free physical pages. Once we've done so, all further
// memory management will go through the page_* functions. In
// particular, we can now map memory using boot_map_region
// or page_insert
page_init();
check_page_free_list(1);
check_page_alloc();
check_page();
//....
}
在完成了 mem_init刀 page_init 之前的代码后,整理一下目前的物理内存和 虚拟地址空间的映射情况:
目前我们对 PageInfo 的了解还不足够,在研究page_init之前,有必要学习下 struct PageInfo 的具体细节。
先来看看 PageInfo 这个结构体,这个注释真棒。
/*
* 页面描述符结构,映射到 UPAGES。
* 内核可读写,用户程序只读。
*
* 每个结构 PageInfo 保存一个物理页面的元数据。
* 它不是物理页面本身,但物理页面和结构 PageInfo 之间有一一对应的关系。
* 您可以使用 kern/pmap.h 中的 page2pa() 将结构 PageInfo * 映射到相应的物理地址。
*/
struct PageInfo {
//空闲列表中的下一页。
struct PageInfo *pp_link;
// pp_ref 是指向此页的指针(通常是页表条目)的计数。
// 对于使用 page_alloc 分配的页面,pp_ref 是指向该页面的指针计数(通常在页表项中)。
// 在启动时使用 pmap.c 的boot_alloc 分配的页面没有有效的引用计数字段。
uint16_t pp_ref;
}
如注释所述, PageInfo 和物理内存是一一对应的,一个PageInfo 对应一页物理内存(4KB),可以从 page2pa 这个映射函数中看出来
对于一个物理地址 pa ,将其右移 12 位,然后就可以作为 pages 数组的下标了。
也就是说 :
pages[0] 对应 pa 0x0000_0000 到 0x0000_1000
pages[1] 对应 pa 0x0000_1000 到 0x0000_2000
pmap.h 中还有很多好用的函数和宏,除了这个 pa2page 还有 PADDR、KADDR等,可以先看一看,理解下。
理解了 struct PageInfo 的结构和映射方法,可以来看 page_init 了。
page_init 初始化了 pages 数组,注释给的相当详尽了。按照上面mem_init总结的图写,可以参照 lab1笔记 中的内存布局和 memlayout.h 中关于 IOPHYSMEM、EXTPHYSMEM 的定义写。
// 初始化页面结构和内存空闲列表。
// 完成后,永远不要再使用 boot_alloc。 只使用下面的页面分配器函数来分配和取消分配物理内存。
// 通过 page_free_list 分配和删除物理内存。
//
void
page_init(void)
{
// 这里的示例代码将所有物理页面标记为空闲。
// 但实际情况并非如此。 哪些内存是空闲的?
// 1) 将物理页 0 标记为使用中。
// 这样,我们就可以保留实际模式 IDT 和 BIOS 结构,以备不时之需。 (目前还不需要,但是......)。
//
// 2) 其余的基本内存 [PGSIZE, npages_basemem * PGSIZE)是空闲的。
//
// 3) 然后是 IO 孔 [IOPHYSMEM, EXTPHYSMEM),它必须永远不会被分配。
//
// 4) 然后是扩展内存 [EXTPHYSMEM, ...) 其中一些在使用中,一些是空闲的。
// 内核在物理内存的哪里?哪些物理页已经用于页表和其他数据结构?
//
// 修改代码以反映这一点。
// 注意:切勿实际触及与空闲页面对应的物理内存!
//
size_t i;
//物理页 0 标记为使用中
pages[0].pp_ref = 1;
for(int i = 1; i
从pageinfo 空闲链表中摘下一个,并返回,细节见注释:
//
// 分配一个物理页面。 如果(alloc_flags & ALLOC_ZERO),则用“
参与评论
手机查看
返回顶部