物理页面管理相关的函数
物理内存页管理
- 完善
kern/pmap.c
中的函数:boot_alloc(), mem_init(), page_init(), page_alloc(), page_free()
。check_page_free_list()
和check_page_alloc()
两个函数将会检测你写的页分配器代码是否正确。
在 entry.S
中可以看到,当进入 kernel 分配好堆栈位置(movl $0x0,%ebp; movl $(bootstacktop),%esp)后,紧接着就调用了 i386_init()
,它定义在 kern/init.c
中,可以看到经过几步之后就开始调用 mem_init()
在lab中出现了 page table 和 page directory 的概念,但是单看lab笔者也没有弄明白它们之间的区别。但后来发现其实就是多级页表,更详细可以看一下补充材料–多级页表
mem_init()
首先调用 i386_detect_memory()
,分析可知其功能为检测内存空间,得到了两个重要的全局变量 npages, npages_basemem
分别代表总内存有多少 页,base(0x0~0xA0000)内存有多少 页。
接下来通过 boot_alloc()
分配一 页 大小的空间用于存放页表目录(page directory),并返回一个 pde_t *
指向它。
boot_alloc()
// mem_init 中:
kern_pgdir = (pde_t *) boot_alloc(PGSIZE);
memset(kern_pgdir, 0, PGSIZE); // 可以看到只分配了一页的大小用来存储页目录指针,先将这部分清零。
// 完善 boot_alloc()
static void *
boot_alloc(uint32_t n)
{
static char *nextfree; // virtual address of next byte of free memory
char *result;
// 只有第一次需要初始化nextfree
// 并且将其对其至 PGSIZE 整数倍的位置
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end, PGSIZE);
}
// Allocate a chunk large enough to hold 'n' bytes, then update
// nextfree. Make sure nextfree is kept aligned
// to a multiple of PGSIZE.
//
// LAB 2: Your code here.
result = nextfree; // 分配的空间的首地址交与 result
nextfree = ROUNDUP((char*)((uint32_t)nextfree + n), PGSIZE); // 分配n个字节的空间,依旧需要对齐!
if ((uint32_t)nextfree - KERNBASE > (npages * PGSIZE)) {
// 判断nextfree是否合法,利用inc/assert.h 里定义的panic函数,方便log信息。
panic("Out of memory!\n");
}
return result; // 输出分配的空间的首地址
// return NULL;
}
接下来 kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
是为这个页目录(page directory)增加第一个页目录表项。且此表项代表的是此页目录本身。PDX
用于将虚拟地址 UVPT
转成页目录索引,PADDR
用于将虚拟地址转成物理地址。PTE_U | PTE_P
是标志位,代表页目录项存在,运行权限为用户态。
根据注释,需要为npages个记录页面信息的 struct PageInfo
分配空间并清零:
// mem_init()
//////////////////////////////////////////////////////////////////////
// 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(npages * sizeof(struct PageInfo));
memset(pages, 0, npages * sizeof(struct PageInfo));
接下来 page_init()
用于初始化pages数组,并初始化pages_free_list(它代表由空闲页组成的链表的head指针),
page_init()
为了完成这个函数,无非就是分析到底pages数组的每个索引值代表的页面,到底是空闲还是忙碌。注释中也给了我们提示哪些是空闲/忙碌。
// page_init()
// 0 页面是忙碌
// [1, npages_basemem) 是空闲
// [IOPHYSMEM / PGSIZE, EXTPHYSMEM / PGSZIE) 是忙碌,是一个从640KB~1M范围内,大小为384KB的IO hole
// [EXTPHYSMEM / PGSIZE, nextfree) 是忙碌,这些页面目前通过boot_alloc()分配掉了。
// Pageinfo结构有两个成员
// 一个 struct Pageinfo * 指涉下一个页面
// 一个 uint16_t pp_ref 记录指向此页面的指针的个数,用于判断空闲/忙碌
size_t i;
page_free_list = NULL;
int num_allocated = ((uint32_t) boot_alloc(0) - EXTPHYSMEM) / PGSIZE;
int num_iohole = (EXTPHYSMEM - IOPHYSMEM) / PGSIZE;
for (i = 0; i < npages; i++) {
if (i == 0 || (i >= npages_basemem && i < (npages_basemem + num_allocated + num_allocated))) {
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
} else {
pages[i].pp_ref = 1;
pages[i].pp_link = NULL;
}
}
接下来运行 check_page_free_list()
检查页面空闲链表的合理性之后,就要运行 check_page_alloc()
来检查 page_alloc(), page_free()
两个子函数能否正确运行:
page_alloc(), page_free()
// page_alloc(int alloc_flags) 实现分配页面的功能,alloc_flags 表示是否清零此页面
// 根据如下的提示完善函数
// Allocates a physical page. If (alloc_flags & ALLOC_ZERO), fills the entire
// returned physical page with '\0' bytes. Does NOT increment the reference
// count of the page - the caller must do these if necessary (either explicitly
// or via page_insert).
//
// Be sure to set the pp_link field of the allocated page to NULL so
// page_free can check for double-free bugs.
//
// Returns NULL if out of free memory.
//
// Hint: use page2kva and memset
struct PageInfo *
page_alloc(int alloc_flags)
{
struct PageInfo *result;
if (page_free_list == NULL) {
return NULL; // 没有空闲页面,返回NULL
}
result = page_free_list;
page_free_list = result->pp_link;
result->pp_link = NULL;
if (alloc_flags & ALLOC_ZERO) {
memset(page2kva(result), 0, PGSIZE); // 如果清零,就memset一下
}
return result;
// return 0;
}
// page_free(struct PageInfo *pp) 实现释放pp的资源,将其加入空闲页面
// 需要通过assert断言pp页面的状态
// Return a page to the free list.
// (This function should only be called when pp->pp_ref reaches 0.)
//
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.
assert(pp->pp_link == NULL);
assert(pp->pp_ref == 0);
pp->pp_link = page_free_list;
page_free_list = &pp;
}