本文是基本翻译 https://pdos.csail.mit.edu/6.828/2014/labs/lab2/ MITJOS的实验步骤
然后代码是转载 http://www.cnblogs.com/fatsheep9146/p/5124921.html这篇博客的代码。
一、实验目的:
Lab2实验主要是对内存管理来设计的实验。
二、实验过程:
Part0:预备工作Getting started
首先进入https://pdos.csail.mit.edu/6.828/2014/ 这个网站,然后选择Lab2,在Terminal中输入命令git checkout -b lab2 origin/lab2 即可将lab1转为lab2。
Lab2中多出来的文件有:inc/memlayout.h,kern/pmap.c,kclock.h,kclock.c。
Part1 : 物理页管理Physical PageManagement
Exercise 1: 为实现物理页的分配,需要在pmap.c中编写以下几个函数的代码:boot_alloc(); mem_init(); page_init(); page_alloc(); page_free(); 写完后,check_page_free_list()和check_page_alloc()两个函数将会检测你写的页分配器代码是否正确。
在做这个练习之前,我们需要先看一下pmap.c中的代码。其中的mem_init()函数就是jos操作系统对内存初始化的函数。
mem_init()函数第一句:
//Find out how much memory the machine has (npages & npages_basemem).
i386_detect_memory();
调用i386_detect_memory()这个函数,这个函数如下:
static void
i386_detect_memory(void)
{
size_tnpages_extmem;
//Use CMOS calls to measure available base & extended memory.
//(CMOS calls return results in kilobytes.)
npages_basemem= (nvram_read(NVRAM_BASELO) * 1024) / PGSIZE;
npages_extmem= (nvram_read(NVRAM_EXTLO) * 1024) / PGSIZE;
//Calculate the number of physical pages available in both base
//and extended memory.
if(npages_extmem)
npages= (EXTPHYSMEM / PGSIZE) + npages_extmem;
else
npages= npages_basemem;
cprintf("Physicalmemory: %uK available, base = %uK, extended = %uK\n",
npages* PGSIZE / 1024,
npages_basemem* PGSIZE / 1024,
npages_extmem* PGSIZE / 1024);
}
阅读代码我们能够发现,在这个函数中主要是为npages(用来记录整个内存的页数)来赋值。
然后我们来了解一下basemem和extmem在jos中是什么。
从 0x00000~0xA0000,这部分也叫basemem,是可用的。npages_basemem就是记录basemem的页数。
紧接0xA0000~0x100000,这部分叫做IO hole,是不可用的,主要被用来分配给外部设备。
然后0x100000~0x,这部分叫做extmem,是可用的。npages_extmem就是记录extmem的页数。
mem_init()函数下一句
// Remove this line when you're ready to test thisfunction.
panic("mem_init: This function is notfinished\n");
这句话就是在说exercise1如果完成,就删除这句话测试一下是不是写对了。
mem_init()函数下一句
kern_pgdir= (pde_t *) boot_alloc(PGSIZE);
memset(kern_pgdir,0, PGSIZE);
从pmap.c中的注释 pde_t *kern_pgdir; // Kernel's initial page directory我们得知mem_init()这一步是在为内核的页表来分配内存。然后我们接着去看boot_alloc函数。
// This simple physicalmemory allocator is used only while JOS is setting
// up its virtual memorysystem. page_alloc() is the realallocator.
//
// If n>0, allocatesenough pages of contiguous physical memory to hold 'n'
// bytes. Doesn't initialize the memory. Returns a kernel virtual address.
//
// If n==0, returns theaddress of the next free page without allocating
// anything.
//
// If we're out ofmemory, boot_alloc should panic.
// This function may ONLYbe used during initialization,
// before thepage_free_list list has been set up.
staticvoid *
boot_alloc(uint32_tn)
{
static char *nextfree; // virtual address of next byte of freememory
char *result;
// Initialize nextfree if this is the firsttime.
// 'end' is a magic symbol automaticallygenerated by the linker,
// which points to the end of the kernel'sbss segment:
// the first virtual address that the linkerdid *not* assign
// to any kernel code or global variables.
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 codehere.
return result;
}
在这个函数中,我们看到了第一处需要填写的代码,注释中告诉我们这个函数不是真正的内存分配函数,Page_alloc()函数才是,这个函数只是在jos刚刚启动时才调用的一个物理内存分配函数。然后接着说
// If n>0, allocatesenough pages of contiguous physical memory to hold 'n'
// bytes. Doesn't initialize the memory. Returns a kernel virtual address.
//
// If n==0, returns theaddress of the next free page without allocating
// anything.
// If we're out ofmemory, boot_alloc should panic.
这段就是告诉我们如何根据n来分配内存,然后如果超出内存,要启动panic。
所以添加代码:
result= nextfree;
nextfree = ROUNDUP(nextfree+n, PGSIZE);
if((uint32_t)nextfree - KERNBASE > (npages*PGSIZE))
panic("Out of memory!\n");
return result;
所以kern_pgdir会分配给它PGSIZE(一个页)大小的内存。
mem_init()下一句:
// Recursively insert PD in itself as a page table, to form
// a virtual page table atvirtual address UVPT.
// (For now, you don't haveunderstand the greater purpose of the
// following line.)
// Permissions: kernel R,user R
kern_pgdir[PDX(UVPT)]= PADDR(kern_pgdir) | PTE_U | PTE_P;
这句话根据注释我们可知现在还不用理解这句话的深层含义,只要知道它是在页表kern_pgdir中插入一个表项,然后在虚拟地址UVPT处形成一个虚拟页表。
mem_init()下一句:
// Allocate an array of npages 'struct PageInfo's and store it in'pages'.
// The kernel uses thisarray 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 inmemory. Use memset
// to initialize all fieldsof each struct PageInfo to 0.
// Your code goes here:
接着就是我们需要填写的代码:注释中说这一段代码要为结构体PageInfo的数组npages来分配内存。然后页表中的每一页都有一个对应的PageInfo,并提示npages是页的个数。
所以我们就用上面提到的boot_alloc(uint32_t n)这个函数来进行分配内存即可。
代码如下:
pages=(structPageInfo*)boot_alloc(sizeof(struct PageInfo )*npages);
memset(pages,0,sizeof(structPageInfo)*npages);
mem_init()下一句:
//////////////////////////////////////////////////////////////////////
// Now that we've allocatedthe initial kernel data structures, we set
// up the list of freephysical pages. Once we've done so, all further
// memory management willgo through the page_* functions. In
// particular, we can nowmap memory using boot_map_region
// or page_insert
page_init();
这句代码中调用了page_init()函数来初始化那些空闲的物理页,所以现在去看一下page_init()函数。
void
page_init(void)
{
// The examplecode here marks all physical pages as free.
// However this is nottruly the case. What memory is free?
// 1) Mark physical page 0 as in use.
// This way we preserve the real-mode IDT andBIOS 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. Whereis the kernel
// in physical memory? Which pages are already in use for
// page tables and other data structures?
//
// Change the code toreflect this.
// NB: DO NOT actuallytouch the physical memory corresponding to
// free pages!
size_ti;
for(i = 0; i < npages; i++) {
pages[i].pp_ref= 0;
pages[i].pp_link= page_free_list;
page_free_list= &pages[i];
}
}
这个函数的注释说这段示例代码中让所以pages中的物理页都标志成free了,但是实际中不是这样的,所以分了4类内存来告诉我们哪些内存是或不是free的。然后在代码中实现。这4类分别是:1.实模式中IDT和BIOS结构那部分的物理页不可用,需要保护起来。2.Base memory中[PGSIZE, npages_basemem * PGSIZE)这部分内存是可用的。3.IOHole这段内存[IOPHYSMEM, EXTPHYSMEM)不可用,不可分配。4. extended memory [EXTPHYSMEM, ...)这段内存中有些已被占用,有些是free的。
然后根据这段信息在page_free_list赋对应的free页即可。
添加代码如下:
void
page_init(void)
{
//num_alloc:在extmem区域已经被占用的页的个数
int num_alloc = ((uint32_t)boot_alloc(0) - KERNBASE) /PGSIZE;
//num_iohole:在io hole区域占用的页数
int num_iohole = 96;
size_t i;
for(i=0; i
{
if(i=0)
{
pages[i].pp_ref = 1;
}
else if(i >=npages_basemem && i < npages_basemem + num_iohole + num_alloc)
{
pages[i].pp_ref = 1;
}
else
{
pages[i].pp_ref = 0;
pages[i].pp_link= page_free_list;
page_free_list = &pages[i];
}
}
这样就写完了page_init()函数。
接着再返回mem_init()函数中,下一行代码是:
check_page_free_list(1);//检查page_free_list链表的空闲页是否合法。(就是检查page_init()函数有没有写对)
check_page_alloc();//检查page_alloc(),page_free()两个函数是否能正确运行
所以现在我们去写page_alloc(),page_free()这两个函数。
// Allocates a physicalpage. If (alloc_flags & ALLOC_ZERO),fills the entire
// returned physical pagewith '\0' bytes. Does NOT increment thereference
// count of the page - thecaller must do these if necessary (either explicitly
// or via page_insert).
//
// Be sure to set the pp_linkfield of the allocated page to NULL so
// page_free can check fordouble-free bugs.
//
// Returns NULL if out offree memory.
//
// Hint: use page2kva andmemset
structPageInfo *
page_alloc(intalloc_flags)
{
// Fill this function in
}
注释中说这个函数功能是分配物理页。并提示If(alloc_flags & ALLOC_ZERO),直接分配一个以0赋值的的物理页,确保分配的page的pp_link=NULL以便于page_free函数可以检查double-free 错误。如果内存溢出就返回NULL。并且提示page2kva和memset函数。
所以我们的分配思路就是:1.从page_free_list中取出一个Page_info,2.修改page_free_list链表信息,3.修改取出的Page_info信息,为这页初始化内存。
代码如下:
struct PageInfo *
page_alloc(int alloc_flags)
{
struct PageInfo *result;
if (alloc_flags & ALLOC_ZERO)
memset(page2kva(result), 0, PGSIZE);
if (page_free_list == NULL)
return NULL;
result= page_free_list;
page_free_list = result->pp_link;
result->pp_link = NULL;
return result;
}
然后写page_free()函数:
//
// Return a page to the freelist.
// (This function should onlybe called when pp->pp_ref reaches 0.)
//
void
page_free(structPageInfo *pp)
{
// Fill this function in
// Hint: You may want to panic if pp->pp_ref is nonzero or
// pp->pp_link is not NULL.
}
注释中说page_free函数就是将一个page_info重新放入page_free_list中,这个函数只能在pp->pp_ref=0时才能调用,当pp->pp_ref!=0或pp->pp_link!=NULL时应该调用panic函数。所以这个函数代码如下:
void
page_free(structPageInfo *pp)
{
if(pp->pp_ref!=0||pp->pp_link!=NULL)
{
panic(“can‘tfree this page! page in use or in the free list”);
}
pp->pp_link=page_free_list;
page_free_list=pp;
}
然后mem_init()下一行:
//////////////////////////////////////////////////////////////////////
// Now we set up virtual memory
//////////////////////////////////////////////////////////////////////
// Map 'pages' read-only by the user at linear address UPAGES
// Permissions:
// - the new image atUPAGES -- kernel R, user R
// (ie. perm = PTE_U |PTE_P)
// - pages itself --kernel RW, user NONE
// Your code goes here:
这一段注释告诉我们现在要开始分配虚拟内存了。
不过我们已经完成了exercise1中的任务,即物理页的分配,所以现在删除mem_init()函数中panic("mem_init: Thisfunction is not finished\n");这句话,然后make grade 即可得到20分。
不过我们还没有完成mem_init()这个函数。所以这句panic暂时还不要删。
Part 2:虚拟内存Virtual Memory
Part2:在做其他之前,先来熟悉一下x86保护模式下的内存管理结构,也就是说段和页的转换。
Exercise 2:阅读关于页转换和基于页保护机制的章节,虽然JOS是用分页来实现虚拟内训和保护,但是我们还是建议你略读一下有关段的章节,段转换和基于段的保护机制在x86上没有实现,所以你有基本了解即可。
首先我们先来了解 虚拟地址、线性地址、物理地址:
在x86体系中,一个虚拟地址(Virtual Address)是由两部分组成,一个是段选择子(segment selector),另一个是段内偏移(segment offset)。一个线性地址(Linear Address)是通过段转换机制将虚拟地址转换之后得到的地址,(这一步在分页转换之前)一个物理地址(Physical Addresses)是通过段转换和分页转换之后所得到的真实内存地址,这个地址将会最终输送到RAM的地址总线上。
Selector +------------+ +-----------+
---------->| | | |
|Segmentation| | Paging |
Software | 段机制 |------->| 分页机制 |-------> RAM
Offset | Mechanism | |Mechanism |
---------->| | | |
+------------+ +-----------+
Virtual Linear Physical
这个图就说明了虚拟地址、线性地址、物理地址的关系。
C语言程序中的指针的值就是虚拟地址中的偏移量,在boot/boot.S文件中,我们移入了一个全局描述表Global DescriptorTable (GDT),它的作用是将所有段基地址设置为0并且限制其最大为0xffffffff,这能有效使段转换机制失去作用,从而关闭了分段管理功能。因此段选择子实际已经没有意义了,然后线性地址其实相当于是虚拟地址的偏移量。在Lab3中,我们为了设置优先级必须和段打交道,但是在JOS实验(内存转换)这块,我们可以忽略分段,单独关注分页转换即可。
回顾lab1的part3,我们引入了一个简单的页表以便于内核可以在它的虚拟地址 0xf0100000处运行,尽管实际上它的物理地址是0x00100000,在ROM BIOS之上。这个页表仅仅映射了4MB的内存,在这次实验中,你将在虚拟内存布局里为JOS准备内存。我们将扩展这个映射到物理内存的第一块256MB内存上,并且把这部分物理空间映射到从0xf0000000开始的虚拟空间中,以及一些其他的虚拟地址空间中。
Exercise3:GDB只能通过虚拟地址查看QEMU内存的内容,但是在准备虚拟内存时,能够检查物理内存是很有用的。复习一下QEMU监控指令,尤其是xp命令,他可以让你检查物理内存。在运行着QEMU的terminal里按下Ctrl-a c,然后就可以进入QEMU 监控器。
利用QEMU 监控器中的xp命令和GDB中的x命令就能检查相关的物理内存和虚拟内存(确保你看到的是相同的数据)。
以下是一些QEMU monitor的常见指令:
xp/Nx paddr -- 查看paddr物理地址处开始的,N个字的16进制的表示结果。
info registers -- 展示所有内部寄存器的状态。
info mem -- 展示所有已经被页表映射的虚拟地址空间,以及它们的访问优先级。
info pg -- 展示当前页表的结构
从代码在CPU执行时,一旦进入保护模式后(最开始我们进入的是boot/boot.S),我们就没有办法用线性地址或物理地址了。所以内存中的地址引用都是用虚拟地址,然后利用MMU来转换,这也就是说C代码中的指针其实都是虚拟地址。
JOS内核经常用模糊的值或者int类型来操纵地址而不是对它解析引用,比如物理内存分配器。有时它是虚拟地址,有时是物理地址。那么为了记录代码,JOS源代码中的地址被分为两种类型:uintptr_t 代表虚拟地址;physaddr_t 代表物理地址。这两种类型实际上都是32位int类型(uint32_t),所以当你用一个类型给另一个类型赋值是,编译器不会报错,因为它们本身就都是int型。但是当你想要解析引用它们的时候编译器就会报错。
JOS内核能够通过先将uintptr_t型转化为指针这种方式来解析引用uintptr_t 。然而,内核不能这样解析引用一个物理地址(physaddr_t),因为内核需要用MMU来转换所有内存地址。如果你强制这样解析引用了一个物理地址(硬件会将它解释为一个虚拟地址),那么最后你得到的地址可能并不是你想要的。
总结一下:
C type |
Address type |
T* 指针 |
Virtual |
uintptr_t |
Virtual |
physaddr_t |
Physical |
问题:
1.假设下述JOS内核代码是正确的,那么变量x应该是uintptr_t类型呢,还是physaddr_t呢?
mystery_t x;
char* value = return_a_pointer();
*value = 10;
x = (mystery_t) value;
答:因为这里x使用了先转为指针再解析引用的方式,所以x应该是虚拟地址也就是uintptr_t类型。
物理地址与虚拟地址的相互转化
JOS内核有时需要读取或者修改内存,但是这时有可能他只知道这个要被修改的内存的物理地址。比如,当我们想要加入一个新的页表项时,我们需要分配一块物理内存来存放页目录项,然后初始化这块内存。然而内核也不能绕过虚拟地址转换这一步。不过JOS将所有物理内存重新从物理地址0映射到虚拟地址0xf0000000的一个理由就是帮助内核来在仅知道物理内存的情况下也能完成读和写。为了将一个物理地址转换为它可以读写的虚拟地址,内核必须添加0xf0000000到物理地址中以便于找到在二次映射中与它相关的虚拟地址。我们可以采用KADDR(pa)指令来获取。其中pa指的是物理地址。
JOS内核有时也需要通过虚拟地址来找对应的物理地址。内核中通过 boot_alloc()函数来分配空间的全局变量和内存都在内核被启动的一块区域(从0xf0000000开始,正好是映射的整块物理内存的那个区域)因此,将虚拟地址转化为物理地址,内核只要简单的用虚拟地址减0xf0000000即可得到物理地址。你可以使用PADDR(va)指令来做这个减法。其中va指的是虚拟地址。
引用计数Reference counting
在之后的实验中,你将会经常遇到一种情况,多个不同的虚拟地址被同时映射到相同的物理页上面。你要用一个记录每个物理页的引用次数,在这个物理页对应的结构体PageInfo的域pp_ref中记录即可。当这个物理页的引用次数为0时(也就是没有被使用时),这个物理页就可以被free了。通常,这个物理页的pp_ref值应该等于所有页表中UTOP之下的这个物理页的出现个数,UTOP之上的地址范围在启动的时候已经被映射完成了,之后不会被改动。
当使用page_alloc()函数时要小心,它所返回的引用计数一直是0,所以pp_ref值应该在你一旦做完something后马上加1(比如把这页插入页表时)。有时这也在其他函数中处理,比如page_insert(),或者调用了page_alloc()的函数,也要马上为pp_ref加1。
页表管理Page Table Management
现在你要写管理页表的程序了:插入、删除线性-物理地址映射,当有需求时创建页表的页。
Exercise 4.在kern/pmap.c中,你需要完成下列函数的代码:pgdir_walk(),boot_map_region(),page_lookup(), page_remove(),page_insert()。
mem_init()函数中调用的check_page()会测试这些代码是否正确。
首先来看pgdir_walk()函数:
pgdir_walk函数:
// Given 'pgdir', a pointer to a page directory,pgdir_walk returns
// a pointer to the page table entry (PTE) forlinear address 'va'.
// This requires walking the two-level page tablestructure.
//
// The relevant page table page might not exist yet.
// If this is true, and create == false, thenpgdir_walk returns NULL.
// Otherwise, pgdir_walk allocates a new page tablepage with page_alloc.
// - If theallocation fails, pgdir_walk returns NULL.
// -Otherwise, the new page's reference count is incremented,
// the page iscleared,
// andpgdir_walk returns a pointer into the new page table page.
//
// Hint 1: you can turn a Page * into the physicaladdress of the
// page it refers to with page2pa() fromkern/pmap.h.
//
// Hint 2: the x86 MMU checks permission bits inboth the page directory
// and the page table, so it's safe to leavepermissions in the page
// directory more permissive than strictlynecessary.
//
// Hint 3: look at inc/mmu.h for useful macros thatmainipulate page
// table and page directory entries.
//
pte_t *
pgdir_walk(pde_t *pgdir,const void *va, int create)
{
// Fill this function in
return NULL;
}
在这段注释中,首先解释了page_walk函数中的参数的含义,pgdir是一个指向page dictionary的指针,然后va是虚拟地址,这个函数要返回这个虚拟地址指向的页表项(PTE)。如果这个PTE存在,那么返回这个PTE即可,如果不存在,参数create是指这个页表项是否需要被创建,若需要就创建一个页表项,不需要就返回NULL。创建失败返回NULL,成功就为这个页表项的引用计数+1。这个函数做的事情就是: 给定一个页目录表指针 pgdir ,该函数应该返回线性地址va所对应的页表项指针。
所以这个函数代码如下:
pte_t * pgdir_walk(pde_t *pgdir, const void *va, int create)
{
unsigned int page_off;
pte_t * page_base = NULL;
struct PageInfo* new_page = NULL;
unsigned int dic_off = PDX(va);//得到这个虚拟地址所在的页目录偏移
pde_t * dic_entry_ptr = pgdir + dic_off;//通过页目录表pgdir+页目录偏移求得这页在page directory的地址。
if(!(*dic_entry_ptr & PTE_P))//如果不存在这页,就要判断是否创建
{
if(create)
{
new_page = page_alloc(1);
if(new_page == NULL) returnNULL;
new_page->pp_ref++;
*dic_entry_ptr =(page2pa(new_page) | PTE_P | PTE_W | PTE_U);//创建一个新的页目录项
}
else
return NULL;
}
page_off = PTX(va);//计算该虚拟地址对应的页表偏移量
page_base =KADDR(PTE_ADDR(*dic_entry_ptr));计算这个页目录项对应的页表页的基地址
return &page_base[page_off];//返回对应的页表项指针
}
然后来看boot_map_region()函数:
// Map [va, va+size) of virtual addressspace to physical [pa, pa+size)
// in the page table rooted at pgdir. Size is a multiple of PGSIZE, and
// va and pa are both page-aligned.
// Use permission bits perm|PTE_P for theentries.
//
// This function is only intended to setup the ``static'' mappings
// above UTOP. As such, it should *not*change the pp_ref field on the
// mapped pages.
//
// Hint: the TA solution uses pgdir_walk
static void
boot_map_region(pde_t*pgdir, uintptr_t va, size_t size, physaddr_t pa, int perm)
{
// Fill this function in
}
这段注释说明了boot_map_region()函数的功能,将虚拟内存空间[va, va+size)映射到物理空间[pa, pa+size)这个映射关系加入到页目录pagedir中。这个函数主要的目的是为了设置虚拟地址UTOP之上的地址范围,这一部分的地址映射是静态的,在操作系统的运行过程中不会改变,所以这个页的PageInfo结构体中的pp_ref域的值不会发生改变。
这个函数的代码如下:
static void
boot_map_region(pde_t *pgdir, uintptr_t va,size_t size, physaddr_t pa, int perm)
{
int nadd;
pte_t *entry = NULL;
for(nadd = 0; nadd < size; nadd +=PGSIZE)//一直循环到size个内存分配完
{//在每次循环中,把一个虚拟页和一个物理页的映射关系放到对应的页表项中
entry = pgdir_walk(pgdir,(void *)va,1); //从页目录中得到va这块地址的的表项
*entry = (pa | perm | PTE_P);
pa += PGSIZE;
va += PGSIZE;
}
}
接下来查看page_insert()函数,初始代码如下:
//Map the physical page 'pp' at virtual address 'va'.
//The permissions (the low 12 bits) of the page table entry
//should be set to 'perm|PTE_P'.
//
//Requirements
// - If there is already a page mapped at 'va',it should be page_remove()d.
// - If necessary, on demand, a page tableshould be allocated and inserted
// into 'pgdir'.
// - pp->pp_ref should be incremented if theinsertion succeeds.
// - The TLB must be invalidated if a page wasformerly present at 'va'.
//
//Corner-case hint: Make sure to consider what happens when the same
//pp is re-inserted at the same virtual address in the same pgdir.
//However, try not to distinguish this case in your code, as this
//frequently leads to subtle bugs; there's an elegant way to handle
//everything in one code path.
//
//RETURNS:
// 0 on success
// -E_NO_MEM, if page table couldn't beallocated
//
//Hint: The TA solution is implemented using pgdir_walk, page_remove,
//and page2pa.
//
int
page_insert(pde_t *pgdir, struct PageInfo*pp, void *va, int perm)
{
return0;
}
这段注释说page_insert()函数功能是:把一个物理页pp与虚拟地址va建立映射。需要注意的是:
如果虚拟内存va处已经有一个物理页与它映射,那么应该调用page_removed()。
如果必要的话,应该分配一个页表并把它插入页目录中。
pp->ref应该在插入成功后+1。
如果一页原来就在va处,那么TLB一定是无效的。
若成功则返回0,没有成功分配页表的话就返回-E_NO_MEM。
所以这个函数代码如下:
int
page_insert(pde_t *pgdir, struct PageInfo*pp, void *va, int perm)
{
pte_t *entry = NULL;
entry = pgdir_walk(pgdir, va, 1); //通过pgdir_walk函数求出va对应的页表项
if(entry == NULL) return -E_NO_MEM;
pp->pp_ref++;//修改引用计数值
if((*entry) & PTE_P) //如果这个虚拟地址已有物理页与之映射
{
tlb_invalidate(pgdir, va);//TLB无效
page_remove(pgdir, va);//删除这个映射
}
*entry = (page2pa(pp) | perm | PTE_P);
pgdir[PDX(va)] |= perm; //把va和pp的映射关系查到页目录中
return 0;
}
接着写page_lookup()函数
//
//Return the page mapped at virtual address 'va'.
//If pte_store is not zero, then we store in it the address
//of the pte for this page. This is usedby page_remove and
//can be used to verify page permissions for syscall arguments,
//but should not be used by most callers.
//
//Return NULL if there is no page mapped at va.
//
//Hint: the TA solution uses pgdir_walk and pa2page.
//
struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t**pte_store)
{
return NULL;
}
这一段注释说page_lookup()函数功能是:返回虚拟地址va映射的页pageInfo结构体的指针。然后如果参数pte_store!=0,那么我们将页表项pte的地址存储到pte_store中。如果va处没有物理页,那么返回NULL。
所以函数代码如下:
struct PageInfo *
page_lookup(pde_t *pgdir, void *va, pte_t**pte_store)
{
pte_t *entry = NULL;
struct PageInfo *ret = NULL;
entry = pgdir_walk(pgdir, va, 0);//根据虚拟地址va找到页表项
if(entry == NULL)
return NULL;
if(!(*entry & PTE_P))
return NULL;
ret = pa2page(PTE_ADDR(*entry));
if(pte_store != NULL)
{
*pte_store = entry;//将页表项pte的地址存储到pte_store中
}
return ret;
}
最后一个page_remove()函数:
//
//Unmaps the physical page at virtual address 'va'.
//If there is no physical page at that address, silently does nothing.
//
//Details:
// - The ref count on the physical page shoulddecrement.
// - The physical page should be freed if therefcount reaches 0.
// - The pg table entry corresponding to 'va'should be set to 0.
// (if such a PTE exists)
// - The TLB must be invalidated if you removean entry from
// the page table.
//
//Hint: The TA solution is implemented using page_lookup,
// tlb_invalidate, and page_decref.
//
void
page_remove(pde_t *pgdir, void *va)
{
}
这段注释说page_remove()函数功能是:取消虚拟地址va处的物理页映射。如果这个地址上本来就没有物理页,那么就不用取消。注意细节就是:这处物理页的引用计数要减1,然后要将它free,如果这个地址的页表项存在,那么页表项要置0,从页表中remove一个表项时要将TLB置为无效。
所以代码如下:
void
page_remove(pde_t *pgdir, void *va)
{
pte_t *pte;
pte_t **pte_store = &pte;
struct PageInfo *pp = page_lookup(pgdir,va, pte_store);//找到va对应的物理页
if(!pp)//如果不存在,就直接返回
return;
page_decref(pp);//将pp页的引用计数-1
**pte_store = 0;//将页表项置0
tlb_invalidate(pgdir, va);//将TLB置为无效
}
写完这些Part2结束。