【MIT CS6.828】Lab 2: Memory Management - Part 1: Physical Page Management
Part 1: Physical Page Management
练习 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 有助于验证您的假设是否正确。
和 Lab 1 的详尽引导不同,从 Lab 2 开始的实验多少有点“fly bitch"的感觉……
1. 物理内存布局及相关数据结构
练习 1 阶段的物理内存布局如下图所示:(图是我根据 Lab 1 和在练习 1 函数中打印的相关信息画的,欢迎纠错)
物理内存管理是以页而非字节为单位的,Lab 2 已规定了一页的大小:PGSIZE 4096
,即一页为 4 KB。所谓内存管理,实际上就是要保存并时刻更新内存中各页的状态,哪些可用,哪些已用,有进程来申请使用应该如何分配……
PageInfo
:Lab 2 定义好的,用于保存一页信息的结构体:
struct PageInfo {
// Next page on the free list.
struct PageInfo *pp_link;
// pp_ref is the count of pointers (usually in page table entries)
// to this page, for pages allocated using page_alloc.
// Pages allocated at boot time using pmap.c's
// boot_alloc do not have valid reference count fields.
uint16_t pp_ref;
};
-
pp_link
是一个内存地址,该地址存储一个PageInfo
类型的变量;从功能上,它指向下一个空闲页所对应的PageInfo
。可以通过以下方式遍历这个空闲页链表:
PageInfo* first_free_page, *free_page; free_page = first_free_page; // first_free_page是已知的第一个空闲页的PageInfo的地址 while(free_page){ //do something; free_page = free_page -> pp_link; }
-
pp_ref
是该页的指针计数器。仅在该页通过page_alloc
分配时pp_ref
有效,pp_ref=0
时表示该页空闲;若该页是通过boot_alloc
分配的,不需要管理pp_ref
。
pages
:Lab 2 定义好的,用于保存物理内存所有页对应PageInfo
的数组:
struct PageInfo *pages;
pages
的每一个元素都能与相应的物理内存页面一一对应,如pages[0]
对应物理内存的第一个 4KB,pages[1]
对应物理内存的第二个 4KB……以此类推,所以可以通过下标直接计算得到它所对应的物理内存页面的起始地址。
pages
中每一个元素的pp_link
构成了一个空闲页链表,链表中的每一个结点都是空闲页对应的PageInfo
,这些空闲页在物理上不一定相邻,如下图所示(仅为示意图,不代表实际内存分配情况,箭头方向是根据下文page_init
函数的实现画的):
显然,对于已分配的PageInfo
,其pp_link
应为NULL
。
有了对整体内存布局及相关数据结构的把握,接下来可以着手完成练习 1了。
2. 物理内存管理代码
JOS 内存管理初始化是从mem_init
完成的,它做了以下工作:
- 调用
i386_detect_memory
读硬件状态,获取物理内存总大小及物理内存中基本内存部分的大小(单位 KB),同时换算为页数; - 调用
boot_alloc
,分配大小为PGSIZE
的内存用于存储kern_pgdir
(暂时不知道做啥用的) - 调用
boot_alloc
,划分出一块内存用于存储pages
,即用于记录物理页状态的数组 - 调用
page_init()
,初始化真正的内存管理机制。
boot_alloc()
负责建立物理内存管理期间的内存分配工作:
if(n == 0){
return nextfree;
}
else if(n > 0){
char* result = nextfree;
nextfree = ROUNDUP((char*)(nextfree + n), PGSIZE);
// 需要以页为单位分配,如申请了6KB也要分配8KB,因此使用ROUNDUP,向上取PGSIZE的倍数
if(PADDR(nextfree) >= npages * PGSIZE)
panic("boot_alloc: out of memory!\n");
else return result; // 返回一个虚拟地址
}
else
panic("boot_alloc: the parameter n is invalid!\n");
// 这个panic写不写无所谓,只是讲究一个鲁棒性
然后在mem_init()
中调用boot_alloc()
为kern_pgdir
和pages
分配内存:
// create initial page directory.
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
memset(kern_pgdir, 0, PGSIZE);
// 为物理页状态数组分配内存并初始化为0
pages = boot_alloc(npages * sizeof(struct PageInfo));
memset(pages, 0, npages * sizeof(struct PageInfo));
page_init()
需要标记哪些内存是可用的,哪些内存是不可用的/已占用的。代码注释已直接指明,照着实现即可:
// 1) Mark physical page 0 as in use.
pages[0].pp_ref = 1;
// 2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE) is free.
size_t i;
for(i = 1; i < npages_basemem; i++){
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
// pages[1].pp_link = NULL;
// pages[2].pp_link = &pages[1];
// ...
// pages[npages_basemem-1].pp_link = &pagepages[npages_basemem-2]
}
// 3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must never be allocated.
// 在inc/memlayout.h中:IOPHYSMEM 0x0A0000, EXTPHYSMEM 0x100000
for(i = (IOPHYSMEM >> PGSHIFT); i < (EXTPHYSMEM >> PGSHIFT); i++){
// PGSHIFT = 12,IOPHYSMEM>>PGSHIFT 等价于 IOPHYSMEM/4096,计算机做除法很慢,所以这里尽量用右移代替
pages[i].pp_ref = 0;
// 不允许被分配,通过pages[i].pp_link = NULL实现
}
// 4) Then extended memory [EXTPHYSMEM, ...).
/* Some of it is in use, some is free. Where is the kernel in physical memory? Which pages are already in use for page tables and other data structures? */
// 第0页已占用,0x0A0000 ~ 0x0FFFFF 为IO Hole已占用,0x100000 ~ 0x117000-0x1(0x116FFF) 为内核ELF文件区域已占用,上述部分已经标记完毕。
// 0x117000 ~ 0x158000-0x1已由boot_alloc分配,从0x158000开始后面全部空闲
size_t EXT_first_free = PADDR(boot_alloc(0)) >> PGSHIFT;
// boot_alloc(0)返回的是一个虚拟地址,通过PADDR转换为物理地址
// EXT_first_free = 0x158000 >> 12
for(i = (EXTPHYSMEM >> PGSHIFT); i < EXT_first_free; i++){
pages[i].pp_ref = 1; // 已由boot_alloc分配的部分
}
// 余下部分均标记为空闲
for(i = EXT_first_free; i < npages; i++){
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
page_alloc()
负责实现一页的内存分配:
struct PageInfo *
page_alloc(int alloc_flags)
{
// 只需要返回所分配的页的对应PageInfo虚拟地址,而不是实际分配内存的起始地址
if(page_free_list == NULL) return NULL;
// page_free_list此时已指向空闲页链表的表头结点,是一个虚拟地址
struct PageInfo *ret = page_free_list;
page_free_list = page_free_list->pp_link;
if (alloc_flags & ALLOC_ZERO){
memset(page2kva(ret),'\0', PGSIZE);
// page2kva:参数为PageInfo虚拟地址,返回该PageInfo对应的页的虚拟地址
// 将该页中数值全部初始化为'\0'
}
// 记得将分配出去的页从空闲页链表中摘出去
ret -> pp_link = NULL;
// 不增加 pp_ref,该工作由调用者完成
return ret; // 返回虚拟地址
}
page_free()
负责实现一页的内存回收:
(注:如果此处加上panic
会导致check_page_alloc()
无法通过,说明page_alloc
里漏了写ret -> pp_link = NULL;
)
void
page_free(struct PageInfo *pp)
{
// Fill this function in
// Hint: You may want to panic if pp->pp_ref is nonzero or
// pp->pp_link is not NULL.
if(pp->pp_ref != 0 || pp->pp_link != NULL)
panic("page_free error!");
pp->pp_link = page_free_list;
page_free_list = pp;
//从空闲页链表头部插入结点
}
至此练习 1 已完成,make grade
应输出以下信息:
running JOS: (1.0s)
Physical page allocator: OK
Page management: FAIL
AssertionError: ...
check_page_alloc() succeeded!