首页 > 技术文章 > lab2——内存管理

blshuidi 2020-08-26 09:50 原文

思考题

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

用虚拟地址查询的好处是不需要经过tlb和页表查询,如果cache命中的话极大地提高了性能。坏处是多个虚拟地址有可能对应同样的物理地址,且虚拟地址的大小一般比物理地址大,所以对于同样大小的cache采用虚拟地址查询会导致命中率的下降,导致cache替换,效率可能更低。另外,对于每个进程都有自己独立的虚拟空间,不可能给每个进程配备一个cache,而使用同一个cache又会面临相同虚拟地址可能对应不同物理地址的问题。

物理地址查询的优势就是没有上面的坏处,总体来说性能和命中率更高。

Thinking 2.2
请查阅相关资料,针对我们提出的疑问,给出一个上述流程的优化版本,新的版本需要有更快的访存效率。(提示:考虑并行执行某些步骤)

查询TLB的同时进行页表查询工作。如果TLB命中则提前终止页表查询;如果TLB没有命中的话,得到物理地址后,更新TLB的同时查询cache。

Thinking 2.3

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

int x;
char *value = return_a_pointer();
*value = 10;
x = (int) value;

虚拟地址。因为value是一个指针,而在c语言中指针的地址使用的都是虚拟地址

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

宏函数中涉及到的链表名称、数据类型、成员变量都没有具体指出,而是以参数的形式出现并使用。这样的好处是在调用这些宏函数的时候,可以适应各种情况而不需要进行重复定义

Thinking 2.5

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

引用自https://www.cnblogs.com/lanxuezaipiao/p/3535626.html

do{...}while(0)在C中是唯一的构造程序,让你定义的宏总是以相同的方式工作,这样不管怎么使用宏(尤其在没有用大括号包围调用宏的语句),宏后面的分号也是相同的效果。

如果没有do while(0)结构的话,在if语句中宏定义会被展开成多条语句,从而出现逻辑错误

do能确保大括号里的逻辑能被执行,while(0)能确保该逻辑只执行一次

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

我们的物理页所在的物理地址等于 物理页号左移12位。Page结构体通过page2ppn计算当前页的物理页号,再用过page2pa函数得到page的真实物理地址

Thinking 2.7

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

C

A:

struct Page_list{

struct {

​ struct {

​ struct Page *le_next;

​ struct Page **le_prev;

​ }* pp_link;

​ u_short pp_ref;

}* lh_first;

}

B:

struct Page_list{

struct {

​ struct {

​ struct Page *le_next;

​ struct Page **le_prev;

​ } pp_link;

​ u_short pp_ref;

} lh_first;

}

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;

}

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

页目录相对于页表起始地址的偏移量为 0xC0000000 >> 10 = 0x00030000

页目录的虚拟地址起始值是 0x00030000+0xC0000000=0xC0030000

Thinking 2.10

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

不能。有必要,只有通过查询页目录才能知道这个页面是否有效

Thinking 2.11

TLB汇编函数

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

LEAF(tlb_out)
    nop
    mfc0    k1,CP0_ENTRYHI
    mtc0    a0,CP0_ENTRYHI
    nop
    // insert tlbp or tlbwi
    nop
    nop
    nop
    nop
    mfc0    k0,CP0_INDEX
    bltz    k0,NOFOUND
    nop
    mtc0    zero,CP0_ENTRYHI
    mtc0    zero,CP0_ENTRYLO0
    nop
    // insert tlbp or tlbwi
NOFOUND:

    mtc0    k1,CP0_ENTRYHI
    
    j   ra
    nop
END(tlb_out)

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

从ENTRYHI中读取tlb的信息并将其修改,用tlbp寻找对应tlb的入口并更新INDEX

根据INDEX的结果得知是否找到,如果没有跳转至NOFOUND

tlbp寻找tlb中一个对应的入口

tlbwi把Index寄存器的值作为索引写入一个tlb入口

四条nop是因为tlbp更新了CPO_INDEX的值,而mfc0又需要用到更新后的CP0_INDEX的结果,所以只能等待四条指令

Thinking 2.12

    u_long* va = 0x12450;
    u_long* pa;

    page_insert(boot_pgdir, pp, va, PTE_R);
    pa = va2pa(boot_pgdir, va);
    printf("va: %x -> pa: %x\n", va, pa);
    *va = 0x88888;
    printf("va value: %x\n", *va);
    printf("pa value: %x\n", *((u_long *)((u_long)pa + (u_long)ULIM)));
  

这段代码旨在计算出相应va与pa的对应关系,设置权限位为PTE_R是为了能够将数据写入内存。

如果MMU能够正常工作,实际输出将会是:

    va: 12450 -> pa: 3ffd000
    va value: 88888
    pa value: 0
    page_check() succeeded!

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

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

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

答:PSE开启时直接用连续的4MB储存1024个页表,通过高十位找到对应页表,再用接下来10位页表索引找到具体的物理页面

实验难点图示

难点一

在练习2.5中使用的PTE_V作为有效标志位给我带来了很大的困扰,我以为根据命名规则这应该是表示PTE类型的表格内容是否有效的,因而误以为需要分配物理内存并把物理地址填写到二级页表中。并且没有想到PTE_V可以同时作为页目录和页表中的有效标志位,想当然地以为只能作为两者之一的有效标志,所以花费了很长时间思考另一个标志位是什么。

还有对于查询后未命中并且也不置create=1的情况该如何处理也纠结了很长时间。

本次实验之所以花费了大量的时间,我觉得还是因为没法理解提供的代码的意思。很多变量虽然有了定义但是没有相应的说明,并不能准确知道这些变量的用途。

难点二

另一个卡了我几个小时的bug就是在pgdir_walk的时候没有给pp_ref加一。也是因为惯性思维,基本模仿了boot_pgdir_walk的写法,忘记了因为此时调用了page_alloc抽出了一页空闲页表使用,所以需求修改相应的pp_ref。

体会与感想

这次实验的难度明显高于lab1和lab0,花费的总时间达到了惊人的28小时,让人感到非常受折磨。

说实话理论课上关于二级页表的知识感觉并不难,对于实验总体上需要干什么基本也是清楚的。但一落实到具体每一个函数应该怎么写的时候,关于每个变量的含义、各种函数的使用就产生了诸多问题。debug的过程因为不熟悉gxemul和tmux的功能也非常吃力。时间好像莫名奇妙就在debug的过程中消失了。

这次算是勉强通过了,不知道下个lab能不能做得更轻松一些。

推荐阅读