BUAA OS——Lab2实验报告

lab2实验报告

实验思考题

2.1

请思考cache用虚拟地址来查询的可能性,并且给出这种方式对访存带来的好处和坏处。另外,你能否能根据前一个问题的解答来得出用物理地址来查询的优势?

    • 使用虚拟地址的cache时,优点在于查询cache前不用访问TLB和二级页表进行地址转换。

    • 缺点在于一旦cache中数据缺失,更新cache时需要替换页表,时间开销更大。并且多个程序的虚拟地址可能访问同一物理地址,安全性以及数据正确性比较难以保障。这样每一个程序都需要单独配备一个cache,显然不行。而共用一个cache可能遇到同一虚拟地址对应不同物理地址。

  • 运用物理地址查询的优势便在于数据的安全性和方便共享,同时综合来看性能即访问命中率更高。

2.2

在我们的实验中,有许多对虚拟地址或者物理地址操作的宏函数(详见include/mmu.h ),那么我们在调用这些宏的时候需要弄清楚需要操作的地址是物理地址还是虚拟地址,阅读下面的代码,指出x是一个物理地址还是虚拟地址。

x是虚拟地址,在操作系统中能够获取的指针地址均为虚拟地址。

2.3

我们在include/queue.h中定义了一系列的宏函数来简化对链表的操作。实际上,我们在 include/queue.h 文件中定义的链表和 glibc 相关源码较为相似,这一链表设计也应用于 Linux 系统中 (sys/queue.h 文件)。请阅读这些宏函数的代码,说说它们的原理和巧妙之处。

宏函数中涉及到的链表名称、数据类型、成员变量都没有具体指出,而是以参数的形式出现并使用。这样的好处是在调用这些宏函数的时候,可以适应各种需求进行不同的链表定义,充分保证系统的可移植性。

2.4

我们注意到我们把宏函数的函数体写成了 do { /\* ... \*/ } while(0)的形式,而不是仅仅写成形如 { /\* ... \*/ } 的语句块,这样的写法好处是什么?

如使用形如 { /\* ... \*/ } 的语句块,在编写程序的时候习惯性的在每语句后加上分号,这会导致出现{xxx();};的形式,会导致编译报错。同时如果不加大括号,就会导致在if条件里使用宏出现逻辑错误,即宏中除第一条语句外均会无条件执行,而如果if后使用了else,则else会因为无if语句而报错。

2.5

注意,我们定义的 Page 结构体只是一个信息的载体,它只代表了相应物理内存页的信息,它本身并不是物理内存页。 那我们的物理内存页究竟在哪呢?Page 结构体又是通过怎样的方式找到它代表的物理内存页的地址呢? 请你阅读include/pmap.hmm/pmap.c 中相关代码,并思考一下。

物理页所在的物理地址等于物理页号左移12位。即只需要知道物理页号就能获取地址,而定义Page结构体可通过page2ppn计算当前Page结构体所指向的物理页号,再用过page2pa函数得到page的真实物理地址。

2.6

请阅读include/queue.h以及 include/pmap.h, 将Page_list的结构梳理清楚,选择正确的展开结构(请注意指针)。

正确结构为C

struct Page_list{ 				\*LIST_HEAD\*
    
	struct { 					\*Page\*
        
 		struct { 				\*LIST_ENTRY\*
            
 			struct Page *le_next;
 			struct Page **le_prev;
                
 		} pp_link;
            
 		u_short pp_ref;
            
	}* lh_first;
                 
}

2.7

mmu.h中定义了 bzero(void *b, size_t) 这样一个函数,请你思考,此处的b指针是一个物理地址, 还是一个虚拟地址呢?

虚拟地址,同样指针均为虚拟地址。

2.8

了解了二级页表页目录自映射的原理之后,我们知道,Win2k内核的虚存管理也是采用了二级页表的形式,其页表所占的 4M 空间对应的虚存起始地址为 0xC0000000,那么,它的页目录的起始地址是多少呢?

二级页表起始地址对应页目录的第一个页目录项,二级页表的起始地址对应的页表项为第0xC0000000 >> 12 = 0xC0000个页表项,一个页表项占4B空间,则该页表项的偏移量为0xC0000 << 2 = 0x300000,那么该页表项即页目录的第一个页目录项所在地址为,0xC0000000 + 0x300000 = 0xC0300000。

2.9

注意到页表在进程地址空间中连续存放,并线性映射到整个地址空间,思考:是否可以由虚拟地址直接得到对应页表项的虚拟地址?上一节末尾所述转换过程中,第一步查页目录有必要吗,为什么?

不能。需要通过查询页目录,获取对应二级页表的有效位,才能知道这个页面是否有对应的物理页面是否能读写。

2.10

Thinking 2.10 观察给出的代码可以发现,page_insert 会默认为页面设置PTE_V的权限。请问,你认为是否应该将PTE_R 也作为默认权限?并说明理由。

我认为不应该。是否可写应该由系统决定,即调用函数是参数perm决定。如果可写权限为默认权限,系统的稳定性与安全性难以保障。

2.11

思考一下tlb_out 汇编函数,结合代码阐述一下跳转到NOFOUND的流程?从MIPS手册中查找tlbptlbwi指令,明确其用途,并解释为何第10行处指令后有4条nop指令。

#include <asm/regdef.h>
#include <asm/cp0regdef.h>
#include <asm/asm.h>

LEAF(tlb_out)
//1: j 1b
nop
//把CP0_ENTRYHI原有值存储到$k1中
    mfc0    k1,CP0_ENTRYHI
//把a0中值存放到CP0_ENTRYHI;
    mtc0    a0,CP0_ENTRYHI
    nop
//查询CP0_ENTRYHI中虚拟地址是否存在TLB中:
//如果有则把匹配项的index保存到Index寄存器中;
//没有匹配则置Index的最高位为1.
    tlbp
//4条nop用于等待tlbp执行完毕(清空流水线),保证mfc0能够使用最新的CP0_INDEX
    nop
    nop
    nop
    nop
//读取改写后的CP0_INDEX到$k0
    mfc0    k0,CP0_INDEX
//如果$k0中值小于0,即TLB缺失,跳转到NOFOUND,将原有值写回
    bltz    k0,NOFOUND
    nop
//清空CP0_ENTRYHI和CP0_ENTRYLOW
    mtc0    zero,CP0_ENTRYHI
    mtc0    zero,CP0_ENTRYLO0
    nop
//更新TLB
    tlbwi
NOFOUND:
    mtc0    k1,CP0_ENTRYHI
    j       ra
    nop
END(tlb_out)

2.12

显然,运行后结果与我们预期的不符,va值为0x88888,相应的pa中的值为0。这说明我们的代码中存在问题,请你仔细思考我们的访存模型,指出问题所在。

static inline u_long
va2pa(Pde *pgdir, u_long va)
{
	Pte *p;

	pgdir = &pgdir[PDX(va)];

	if (!(*pgdir & PTE_V)) {
		return ~0;
	}

	p = (Pte *)KADDR(PTE_ADDR(*pgdir));

	if (!(p[PTX(va)]&PTE_V)) {
		return ~0;
	}

	return PTE_ADDR(p[PTX(va)]);
}

因为va的地址不是页面积的整数倍,而在va2pa的过程中,会被强制转化为物理页面的起始地址即PTE_ADDR函数低12位清零。所以在执行va = 0x88888的过程中,并没有把这个值赋到pa所在的位置,所以pa的值仍然是0

2.13

在X86体系结构下的操作系统,有一个特殊的寄存器CR4,在其中有一个PSE位,当该位设为1时将开启4MB大物理页面模式,请查阅相关资料,说明当PSE开启时的页表组织形式与我们当前的页表组织形式的区别。

当开启PSE时,在一级页表中增加使用了一位用于标识,用于区分从一级页表中寻找得到的地址是二级页表的入口地址还是4MB大小的页面地址。由于页目录即一页一级页表能够映射到的地址本就是4MB,所以整体结构变化不大。


实验难点图示

  • 难点一:理解空闲链表的结构和queue.h头文件中的操作

struct { 				\*LIST_ENTRY\*
	struct Page *le_next;
	struct Page **le_prev; 
}

难点就在于le_prev这个指针的指针,其设置的理由是为了方便对链表的插入和删除提供便利,但又不等同于双向链表,因此理解链表的操作有难度。

  • 难度2:页面的寻找以及创建页表和完成映射

个人认为,相对最难的就是完成页表的映射,需要理解当前指针是虚拟地址,而当前页表指针存储的应该是对应页面的物理地址,在获取虚拟地址之前需要使用PTE_ADDR获取物理页面的起始地址,最后还需要注意完成权限位的设置。


体会与感想

本次实验相比于前两次有了明显的难度提升,花费在其上的时间成倍提升。自己对于前半部分实验(2.1-2.4)部分所花费的时间明显多于后部分,一方面在于阅读代码是积累的过程,前面要阅读更多代码,后面则是应用,另一方面是自己对于指针的操作不够熟练。

操作系统的启动是一层套一层慢慢向上累加。在对页面操作时,我们用到了两种函数,一种是在内核刚刚启动的时候,这一部分内存通常用于存放内存控制块和进程控制块等数据结构,只能使用基础的alloc,一种则是在为用户创造环境之后按根据用户申请页面的需要进行操作。这体现了操作系统启动中每一步都是紧密相连的。

最重要的是就是学会阅读代码,以及尝试去使用现有的函数来完成新的需求,而不自己编写格外的函数或操作,在寻找现有的函数并使用的过程中就是在读懂代码。

posted @ 2021-08-15 17:12  Fight扬尘  阅读(724)  评论(0)    收藏  举报