引言
在本实验中,将为操作系统编写内存管理代码。
内存管理有两个组件。 第一个组件是内核的物理内存分配器,这样内核就可以分配内存并在以后释放它。分配器将以4096字节为单位运行,称为页面。任务是维护数据结构,记录哪些物理页面是空闲的,哪些是已分配的,以及有多少进程共享每个已分配的页面。还将编写分配和释放内存页面的例程。
内存管理的第二个组成部分是虚拟内存,它将内核和用户软件使用的虚拟地址映射到物理内存中的地址。当指令使用内存时,x86硬件的内存管理单元(MMU)会参考一组页表来执行映射。在这一part种将根据提供的规范修改JOS来设置MMU的页表。
Exercise 1
在文件kern/pmap.c中,需要实现以下几个函数:
boot_alloc()
mem_init()
page_init()
page_alloc()
page_free()
check_page_free_list()和check_page_alloc()两个函数将会检测你写的页分配器代码是否正确。
首先打开kern/init.c文件,观察启动内核的函数i386_init函数,可以看到mem_init函数是在内核启动时进行初始化内存的函数。因此,在这个练习中我们从men_init函数入手。
阅读mem_init()函数,首先执行i386_detect_memory()函数,这个函数的作用是检查机器有多少内存(注释有)(有一个疑惑:其中的不可用内存空间是怎么处理的?)执行完这个函数之后,定义了一个变量kern_pgdir,这是一个指向OS页目录表的指针。对这个指针指向的地方分配内存并清零后,通过boot_alloc()函数赋值。
因此我们要开始实现boot_alloc函数。
根据注释可知,boot_alloc(n)只是一个暂时的分配器,需要实现的功能是:分配n个字节连续的内存空间。如果超出内存了就panic. 这个函数主要是维护一个nextfree的指针,指向分配内存的起始地址。
在这段代码中,end指的是.bss段的尾部,定义可见kern/kernel.ld中
if (!nextfree) {
extern char end[];
nextfree = ROUNDUP((char *) end, PGSIZE);
}
操作系统的物理内存分配如图
因此,编写代码如下:
// LAB 2: Your code here.
result = nextfree;
nextfree = ROUNDUP(nextfree+n,PGSIZE);
if((uint32_t)nextfree-KERNBASE > (npages*PGSIZE))
panic("Out of memory!\n");
return result;
回到mem-init函数,继续往下阅读代码。下一行代码是:
kern_pgdir[PDX(UVPT)] = PADDR(kern_pgdir) | PTE_U | PTE_P;
这一行代码是为页目录添加第一个页目录表项。通过查看inc/memlayout.h文件,可以看到UVPT是一段虚拟地址的起始地址。也就是说从0xef400000这个虚拟地址开始,存放操作系统的页表kern_pgdir. 我们必须把它和页表kern_pgdir的物理地址映射起来,PADDR(kern_pgdir)就是在计算kern_pgdir的物理地址。
然后下面的代码需要自己编写,根据注释了解到需要完成的功能是:分配一块内存,用来存放一个struct PageInfo的数组,数组中的每一个PageInfo代表内存当中的一页。操作系统内核就是通过这个数组来追踪所有内存页的使用情况的。
代码如下:
// Your code goes here:
pages =(struct PageInfo *) boot_alloc(npages * sizeof(struct PageInfo));
memset(pages,0, npages * sizeof(struct PageInfo);
接下来就开始执行page_init()函数。阅读example code发现两个新变量pp_link, pp_ref,于是打开头文件inc/memlayout.h,找到了PageInfo的定义:pp_ref表示指向这个页面的指针计数(即物理地址映射向虚拟地址的映射次数)。如果是空闲的就加入空闲链表中。,*pp_link是指向空闲页面链表上下一空闲页面的指针。
根据page_init()函数中的注释可知,第0页,IO Hole和部分扩展内存都已经被占用,不能再分配。
结合上面的物理内存图和注释编写代码如下,就是把已分配的地方标记,空闲的地方用链表串起来。
size_t i;
page_free_list = NULL;
size_t first_free_extend_addr = PADDR (boot_alloc(0));
for (i = 0; i < npages; i++) {
//0 page
if(i==0){
pages[i].pp_ref = 1;
//IO hole && Extend Memory being used
}else if(i*PGSIZE>=IOPHYSMEM && i*PGSIZE<=first_free_extend_addr){
pages[i].pp_ref = 1;
}else{
//free memory
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)
{
// Fill this function in
struct PageInfo *alloc_space;
if(page_free_list ==NULL)
return NULL;
alloc_space = page_free_list;
page_free_list = alloc_space->pp_link;
alloc_space->pp_link = NULL;
if(alloc_flags && ALLOC_ZERO){
memset(page2kva(alloc_space),0,PGSIZE);
}
return alloc_space;
}
page_free,同样也是链表操作,在这里值得注意的是,并不需要把pp_ref设为1。
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){
pp->pp_link = page_free_list;
page_free_list = pp;
}else{
panic("This page is being used!\n");
}
}
到这里,除mem_init()函数外其他函数都完成啦,mem_init()函数到后面的练习继续补充。
make qemu一下,实验成功。
疑问
KERNBASE和end的地址之间的关系?
我的理解是KERNBASE是kernel的堆栈顶指针(事实上代码注释里也是这样解释的?)那end-KERNBASE是啥?搞了好久没搞懂踩个坑,看看后面的练习会不会碰到。