实验地址:Lab2
Lab2的第一部分为物理地址的管理,其中包括物理内存的初始化,分配以及释放。
首先,在inc/mmu.h中定义了几个宏:
#define NPDENTRIES 1024 // page directory entries per page directory
#define NPTENTRIES 1024 // page table entries per page table
#define PGSIZE 4096 // bytes mapped by a page
#define PGSHIFT 12 // log2(PGSIZE)
其中PGSIZE表示一页的大小。
其次需要明确的是,当前运行在高地址阶段,即内核运行在KERNBASE之上的虚拟内存中。在kern/entry.S中已经将[KERNBASE,KERNBASE+4MB)的虚拟内存映射到了物理内存[0,4MB)里了。
现在来完成Exercise 1。
Exercise 1. In the file kern/pmap.c, you must implement code for the following functions (probably in the order given).
boot_alloc()
mem_init() (only up to the call to check_page_free_list(1))
page_init()
page_alloc()
page_free()
check_page_free_list() and check_page_alloc() test your physical page allocator. You should boot JOS and see whether check_page_alloc() reports success. Fix your code so that it passes. You may find it helpful to add your own assert()s to verify that your assumptions are correct.
题目的意思是补全那几个函数,使其能够正常地工作。
函数的要求:分配足够的虚拟内存页,使得这些页能够容纳n个字节的内容。
函数中的nextfree指针指向可以分配的内存地址。
实现代码如下:
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;
nextfree = ROUNDUP(result + n, PGSIZE);
if (PADDR(nextfree)>=0x400000 || nextfree < result)
{
cprintf("nextfree:%u\n",nextfree);
panic("nextfree's value is not correct\n");
}
return result;
首先使用ROUNDUP()这个宏向上取整,获取足够的虚拟页,然后判断分配的空间是否超出了4MB的物理空间范围,如果超出了,就报错(因为现在只映射了4MB的物理空间)。另外注意end这个变量,这是在kernel.ld这个链接脚本里面声明的变量,用来记录内核所有代码和数据的边界,在这个边界之外分配新的空间则不会破坏内核的代码和数据:
.bss : {
PROVIDE(edata = .);
*(.bss)
PROVIDE(end = .);
BYTE(0)
}
/DISCARD/ : {
*(.eh_frame .note.GNU-stack)
这个函数里面写的东西很少,而且简单:
// Your code goes here:
pages = (struct PageInfo *)boot_alloc(npages*sizeof(struct PageInfo));
memset(pages,0,npages*sizeof(struct PageInfo));
为pages分配npages个结构体的空间,并将这个数组初始化为’\0’。
这个函数是这几个函数中稍微麻烦一点的一个函数,完成了它,后面就简单了。
根据提示:
// The example code here marks all physical pages as free.
// However this is not truly the case. What memory is free?
// 1) Mark physical page 0 as in use.
// This way we preserve the real-mode IDT and BIOS structures
// in case we ever need them. (Currently we don’t, but…)
// 2) The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
// is free.
// 3) Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must
// never be allocated.
// 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?
//
// Change the code to reflect this.
// NB: DO NOT actually touch the physical memory corresponding to
// free pages!
Mark physical page 0 as in use.
将物理页第零页标记为已经使用,以作后用。
The rest of base memory, [PGSIZE, npages_basemem * PGSIZE)
这一个区间里面的页是没有使用的。
Then comes the IO hole [IOPHYSMEM, EXTPHYSMEM), which must never be allocated.
保留为IO使用,标记为已经使用。
Then extended memory [EXTPHYSMEM, …)
这一个区间分为两个子区间:(1)内核与上面使用boot_alloc()已经分配使用了的空间 (2)未使用的空间。所以我们的任务就是确定分割二者的边界。
先说实现的代码:
size_t i;
pages[0].pp_ref = 1;
pages[0].pp_link = NULL;
for (i = 1; i<npages_basemem; i++)
{
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
for (i=IOPHYSMEM/PGSIZE; i<EXTPHYSMEM/PGSIZE; i++)
{
pages[i].pp_ref = 1;
pages[i].pp_link = NULL;
}
for (; i<PADDR(pages+npages*sizeof(struct PageInfo))/PGSIZE; i++)
//for (; i
{
pages[i].pp_ref = 1;
pages[i].pp_link = NULL;
}
for (; i<npages; i++)
{
pages[i].pp_ref = 0;
pages[i].pp_link = page_free_list;
page_free_list = &pages[i];
}
可以看到,[EXTPHYSMEM, …)分割为两个子区间的边界为pages+npages*sizeof(struct PageInfo)
或者boot_alloc(0)
,后者比较简练。这都是虚拟地址,要将其转化为物理地址。
根据提示进行实现就行了。
// Fill this function in
struct PageInfo *result = page_free_list;
if (!result)
{
return NULL;
}
else
{
page_free_list = result->pp_link;
result->pp_link = NULL;
if (alloc_flags & ALLOC_ZERO)
memset(page2kva(result),'\0',PGSIZE);
return result;
}
需要注意的是page2kva(result)
的使用。page2kva()函数返回KERNBASE+(result-pages)<
if (pp->pp_ref || pp->pp_link)
panic("This page should not be free!(pp_ref or pp_link is not zero)\n");
pp->pp_link = page_free_list;
page_free_list = pp;
这个不必多说。
使用make;make qemu
得到如下结果:
qemu -drive file=obj/kern/kernel.img,index=0,media=disk,format=raw -serial mon:stdio -gdb tcp::26000 -D qemu.log
6828 decimal is 15254 octal!
Physical memory: 131072K available, base = 640K, extended = 130432K
check_page_free_list() succeeded!
check_page_alloc() succeeded!
kernel panic at kern/pmap.c:751: assertion failed: page_insert(kern_pgdir, pp1, 0x0, PTE_W) < 0
Welcome to the JOS kernel monitor!
Type ‘help’ for a list of commands.
出现
check_page_free_list() succeeded!
check_page_alloc() succeeded!
则表示当前的任务成功完成。
总结:
内核在虚拟内存中是从KERBASE+1MB开始的,所以虚拟内存的[KERBASE,KERBASE+1MB)空间保留给硬件等使用。从kern/kernel.ld中也可以看出。