#
Lab2实验报告 赵岳 142321027 ##Thinking 3.1 ####我们注意到我们把宏函数的函数体写成了 do { // ... } while(0) 的形式,而不是仅仅写成形如 {// ... } 的语句块,这样的写法好处是什么? 答:简单来说,就是为了避免一些歧义和语法错误,do保证了大括号内的语句始终会被执行,while(0)保证了大括号的语句只被执行一遍,并且do{}while()
是一个一定语法正确的独立整体,不会与其他部分发生语法冲突。下面,我举一个使用大括号产生语法错误的例子:
#define f(x) {x++;}
...
...
for(i=0;i<n;i++)
f(x);
return;
考虑上面的例子,直观上来看没有什么问题,可是利用代换就会发现,实际上代码是这样的:
#define f(x) {x++;}
...
...
for(i=0;i<n;i++)
{x++;};
return;
产生了语法问题。而采用do while的形式则没有这种问题。
##Exercise3.1
####我们需要在mips_detect_memory()函数中这几个全局变量,以确定内核可用的物理内存的大小和范围。根据代码注释中的提示,完成mips_detect_memory() 函数。
本部分完成对四个全局变量的赋值即可。运行gexmul仿真器,可以看到我们做实验的机器的内存为64MB,且无外部存储设备:
/* Step 2: Align `freemem` up to multiple of BY2PG. */
freemem = ROUND(freemem,BY2PG);
/* Step 3: Mark all memory blow `freemem` as used(set `pp_ref`
* filed to 1) */
int j = 0;
while(j < PPN(PADDR(freemem))){ // j < (freemem-0x80000000)/4KB?
pages[j].pp_ref = 1;
j++;
}
/* Step 4: Mark the other memory as free. */
while(j<npage){
pages[j].pp_ref = 0;
LIST_INSERT_HEAD(&page_free_list,&page[j],pp_link);
j++;
}
}
其中值得注意的是,在mmu.h中定义了许多计算的宏定义,例如PADDR、PPN等。每个计算都有用途,用这些宏定义可以大大增强我们的代码可读性,也能对该步操作的目的一目了然。(显然地, PPN(PADDR(freemem))比 (freemem-0x80000000)/4KB要清楚得多)。
##Exercise3.3
#### 完成 mm/pmap.c 中的 page\_alloc 和 page\_free 函数,基于空闲内存链表 page\_free\_list ,以页为单位进行物理内存的管理。
#####page\_alloc
代码如下:
```cpp
int
page_alloc(struct Page **pp)
{
struct Page *ppage_temp;
/* Step 1: Get a page from free memory. If fails, return the error code.*/
if(LIST_EMPTY(&page_free_list)){
return -E_NO_MEM; //#define E_NO_MEM 4 in error.h
}
ppage_temp = LIST_FIRST(&page_free_list);
LIST_REMOVE(ppage_temp,pp_link);
/* Step 2: Initialize this page.
* Hint: use `bzero`. */
bzero(ppage_temp,sizeof(struct Page));
*pp=ppage_temp;
return 0;
}
具体实现过程是:先探测是否还有可以使用的空闲物理页面,即用LIST_EMPTY宏定义来探测page_free_list是否是一个空链表。如果空,那么我们根据error.h中的值,将返回-4(具体为什么加负号,猜测一般错误值都会小于0,并且根据alloc函数中举一反三,推测这里内存不足时也应返回-4)。如果不空,那么我们将该链表的头部第一个元素赋给在本函数中定义的ppage_temp变量,然后将这个节点从空闲链表中移除,并将这个节点全部置0,为我们对该页面的使用做好准备。如果申请成功,那么返回0(由于指导书中并没有提及分配成功的返回值,猜测该返回值并不重要,暂且认为其缺省值为0。) #####page_free 代码如下:
void page_free(struct Page *pp)
{
/* Step 1: If there's still virtual address refers to this page, do nothing. */
if(pp->pp_ref>0){
return;
}
/* Step 2: If the `pp_ref` reaches to 0, mark this page as free and return. */
else if(pp->pp_ref==0){
LIST_INSERT_HEAD(&page_free_list,pp,pp_link);
}
/* If the value of `pp_ref` less than 0, some error must occurred before,
* so PANIC !!! */
else
panic("cgh:pp->pp_ref is less than zero\n");
}
具体实现过程是:由于pp_ref记录的是当前物理页面是否还在使用,那么如果大于零,就不能对其进行 free操作,直接return,什么都不做。如果pp_ref==0,说明该页面已经不再需要存在于物理内存中,可以调用LIST_INSERT将其插入到page_free_list链表当中了。如果pp_ref的值小于零,那么输出一个panic警告。
##Thinking 3.2
####了解了二级页表页目录自映射的原理之后,我们知道,Win2k 内核的 虚存管理也是采用了二级页表的形式,其页表所占的 4M 空间对应的虚存起始地址 为 0xC0000000,那么,它的页目录的起始地址是多少呢?
答:0xc0000000+((0xc0000000>>12)<<2) = 0xc0000000+0x00300000 = 0xc0300000.
所以页目录起始地址为0xc0300000。
##Exercise3.4
####完成 mm/pmap.c 中的 boot_pgdir_walk 和 pgdir_walk 函数,实现 虚拟地址到物理地址的转换以及创建页表的功能。
static Pte *boot_pgdir_walk(Pde *pgdir, u_long va, int create)
{
Pde *pgdir_entryp;
Pte *pgtable, *pgtable_entry;
/* Step 1: Get the corresponding page directory entry and page table. */
/* Hint: Use KADDR and PTE_ADDR to get the page table from page directory
* entry value. */
pgdir_entryp=pgdir+PDX(va);
if(*pgdir_entryp&PTE_V)
pgtable=(Pte *)KADDR(PTE_ADDR(*pgdir_entryp));
/* Step 2: If the corresponding page table is not exist and parameter `create`
* is set, create one. And set the correct permission bits for this new page
* table. */
else if(create){
/*temp=(Pte *)alloc(BY2PG,BY2PG,1);
if(temp==NULL){
panic("boot_pgdir_walk failed. Run out of memory!\n");
return NULL;
}*/
*pgdir_entryp=PADDR((Pde)alloc(BY2PG,BY2PG,1))|PTE_V;
//printf("%x\n%x",*pgdir_entryp,PTE_ADDR(*pgdir_entryp));
pgtable=(Pte *)KADDR(PTE_ADDR(*pgdir_entryp));
}
else{
panic("boot_pgdir_walk failed! No valid memory and create is 0!\n");
return NULL;
}
/* Step 3: Get the page table entry for `va`, and return it. */
pgtable_entry=pgtable+PTX(va);
return pgtable_entry;
}
int
pgdir_walk(Pde *pgdir, u_long va, int create, Pte **ppte)
{
Pde *pgdir_entryp;
Pte *pgtable;
struct Page *ppage;
/* Step 1: Get the corresponding page directory entry and page table. */
pgdir_entryp=pgdir+PDX(va);
if(*pgdir_entryp&PTE_V){
pgtable=(Pte *)KADDR(PTE_ADDR(*pgdir_entryp));
}
/* Step 2: If the corresponding page table is not exist(valid) and parameter `create`
* is set, create one. And set the correct permission bits for this new page
* table.
* When creating new page table, maybe out of memory. */
else if(create){
if(page_alloc(&ppage)==-E_NO_MEM){
*ppte=NULL;
return -E_NO_MEM;
}
ppage->pp_ref++;
*pgdir_entryp=page2pa(ppage)|PTE_V|PTE_R;
pgtable=(Pte *)KADDR(PTE_ADDR(*pgdir_entryp));
}
else{
//panic("pgdir_walk failed! Page is not valid and create is 0\n");
*ppte=NULL;
return -E_INVAL;
}
/* Step 3: Set the page table entry to `*ppte` as return value. */
*ppte=pgtable+PTX(va);
return 0;
}
##Exercise3.5 ####实现 mm/pmap.c 中的 boot_map_segment 函数,实现将制定的物理 内存与虚拟内存建立起映射的功能。
void boot_map_segment(Pde *pgdir, u_long va, u_long size, u_long pa, int perm)
{
int i, va_temp;
Pte *pgtable_entry;
/* Step 1: Check if `size` is a multiple of BY2PG. */
if(size%BY2PG){
panic("size must be a multiple of BY2PG!\n");
return;
}
/* Step 2: Map virtual address space to physical address. */
/* Hint: Use `boot_pgdir_walk` to get the page table entry of virtual address `va`. */
for(i=0;i<size/BY2PG;i++){
pgtable_entry=boot_pgdir_walk(pgdir,va,1);
if(!pgtable_entry)
panic("segment fail!(run out of memory!)\n");
*pgtable_entry=pa|perm|PTE_V;
va=va+BY2PG;
pa=pa+BY2PG;
}
}
##Thinking 3.3
####思考一下 tlb_out 汇编函数,结合代码阐述一下跳转到 NOFOUND 的流程?
首先,我们需要明确几个宏定义:
#define CP0_INDEX $0
#define CP0_ENTRYHI $10
#define CP0_ENTRYLO0 $2
这三个宏定义定义了CP0寄存器中的3个寄存器号。
最终通过bltz指令实现跳转,只要K0寄存器中的值小于0,则会跳转到NOFOUND。而K0实际上是CP0_INDEX的值的拷贝,也就是说,只要CP0_INDEX的值小于0,也就是CP0的0号寄存器的值小于0,就会跳转到NOFOUND。如果找到了(即没有跳转),那么会将CP0_ENTRYHI和 CP0_ENTRYLO0的值置零,如果跳转了,则就直接将K1的值又重新返还给CP0_ENTRYHI。
#总结
###实验结果