ucore_lab3_虚拟内存管理

lab3:虚拟内存管理

练习零:填写已有实验

本实验依赖实验1/2。请把你做的实验1/2的代码填入本实验中代码中有“LAB1”,“LAB2”的注释 相应部分

lab3 依赖 lab1lab2,我们需要把做的 lab1lab2 的代码填到 lab3 中缺失的位置上面。同lab2一样,使用 Meld 工具找出不同文件然后再复制过来即可。主要是以下几个文件:

default_pmm.c
pmm.c
trap.c
kdebug.c

练习一:给未被映射的地址映射上物理页

完成do_pgfault(mm/vmm.c)函数,给未被映射的地址映射上物理页。设置访问权限 的时候 需要参考页面所在 VMA 的权限,同时需要注意映射物理页时需要操作内存控制 结构所指定 的页表,而不是内核的页表

页面异常

当启动分页机制以后,如果一条指令或数据的虚拟地址所对应的物理页框不在内 存中或者访问的类型有错误(比如写一个只读页或用户态程序访问内核态的数据等),就会发生 页错误异常。产生页面异常的原因主要有:

  • 目标页面不存在(页表项全为0,即该线性地址与物理地址尚未建立映射或者已经撤销);
  • 相应的物理页面不在内存中(页表项非空,但Present标志位=0,比如在swap分区或磁盘文件上)
  • 访问权限不符合(此时页表项P标志=1,比如企图写只读页面)

当出现上面情况之一,那么就会产生页面page fault异常。产生异常的线性地址存储在 CR2中,并且将是page fault`的产生类型保存在 error code 中

关键数据结构

page_fault函数不知道哪些是“合法”的虚拟页,原因是ucore还缺少一定的数据结构来描述这种不在物理内存中的“合法”虚拟页。为此ucore通过建立mm_struct和vma_struct数据结构,描述了ucore模拟应用程序运行所需的合法内存空间。当访问内存产生page fault异常时,可获得访问的内存的方式(读或写)以及具体的虚拟内存地址,这样ucore就可以查询此地址,看是否属于vma_struct数据结构中描述的合法地址范围中,如果在,则可根据具体情况进行请求调页/页换入换出处理;如果不在,则报错。

vma_struct

vma_struct描述应用程序对虚拟内存“需求”,以及针对vma_struct的函数操作。里把一个vma_struct结构的变量简称为vma变量。

struct vma_struct {  //虚拟内存空间
        struct mm_struct *vm_mm;  //指向一个比vma_struct更高的抽象层次的数据结构mm_struct 
        uintptr_t vm_start;      //vma的开始地址
        uintptr_t vm_end;      // vma的结束地址
        uint32_t vm_flags;     // 虚拟内存空间的属性
        list_entry_t list_link;  //双向链表,按照从小到大的顺序把虚拟内存空间链接起来
    };  

VMA是描述应用程序对虚拟内存“需求”的变量。vm_startvm_end描述的是一个合理的地址空间范围(即严格确保 vm_start < vm_end的关系),list_link是一个双向链表,按照从小到大的顺序把一系列用vma_struct表示的虚拟内存空间链接起来,并且还要求这些链起来的vma_struct应该是不相交的,即vma之间的地址空间无交集。

vm_flags表示了这个虚拟内存空间的属性,目前的属性包括

 #define VM_READ 0x00000001   //只读
 #define VM_WRITE 0x00000002  //可读写
 #define VM_EXEC 0x00000004   //可执行
mm_struct
struct mm_struct {  //描述一个进程的虚拟地址空间
        list_entry_t mmap_list;  //双向链表头,链接了所有属于同一页目录表的虚拟内存空间
        struct vma_struct *mmap_cache;  //指向当前正在使用的虚拟内存空间
        pde_t *pgdir; //指向的就是 mm_struct数据结构所维护的页表
        int map_count; //记录mmap_list里面链接的vma_struct的个数
        void *sm_priv; //指向用来链接记录页访问情况的链表头
 };  

mmap_list是双向链表头,链接了所有属于同一页目录表的虚拟内存空间,mmap_cache是指向当前正在使用的虚拟内存空间,由于操作系统执行的“局部性”原理,当前正在用到的虚拟内存空间在接下来的操作中可能还会用到,这时就不需要查链表,而是直接使用此指针就可找到下一次要用到的虚拟内存空间。pgdir 所指向的就是 mm_struct数据结构所维护的页表。通过访问pgdir可以查找某虚拟地址对应的页表项是否存在以及页表项的属性等。map_count记录mmap_list 里面链接的 vma_struct的个数。sm_priv指向用来链接记录页访问情况的链表头,这建立了mm_struct和后续要讲到的swap_manager之间的联系。

page fault处理流程

trap–> trap_dispatch–>pgfault_handler-- >do_pgfault

产生页访问异常后,CPU把引起页访问异常的线性地址装到寄存器CR2中,并给出了出错码errorCode,说明了页访问异常的类型。ucore OS会把这个值保存在struct trapframe 中tf_err成员变量中。而中断服务例程会调用页访问异常处理函数do_pgfault进行具体处理。这里的页访问异常处理是实现按需分页、页换入换出机制的关键之处。

do_pgfault()函数

do_pgfault()的第三个参数是一个保存在cr2寄存器(页故障线性地址寄存器)中的线性地址,也就是出现 了page fault的线性地址,第二个参数是记在trapframe里面的硬件产生的error code,第一个参数是 pgfault_handler()传过来的mm数据结构,它将PDT保存在链表中(数据结构定义见vmm.h)

  1. 首先查询mm_struct中的合法的虚拟地址链表(vma),用于确定当前出现page fault的线性地址是否合法,合法则继续,反之直接返回;

        int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) 
        {
            int ret = -E_INVAL;
            //try to find a vma which include addr
            struct vma_struct *vma = find_vma(mm, addr);
            //找到对应地址和mm的vma
            pgfault_num++;
            //If the addr is in the range of a mm's vma?
            if (vma == NULL || vma->vm_start > addr) {//不合法地址或不在范围内
            cprintf("not valid addr %x, and can not find it in vma\n", addr);
            goto failed;
        }
    
    
  2. 检查error code(switch(error_code&3)):因为按照注释中的内容我们其实只需要检查0-1位,所以 是&3,可写且存在的页就是3,如果是其他不满足我们要映射物理页的要求的时候就打印错误并直接返回

    switch (error_code & 3) 
    {
    	default://默认3即合理的
    	/* error code flag : default is 3 ( W/R=1, P=1): write, present */
    //以下是各种形式的报错
    	case 2: /* error code flag : (W/R=1, P=0): write, not present */
    		if (!(vma->vm_flags & VM_WRITE)) {
    			cprintf("do_pgfault failed: error code flag = write AND not present,but the addr's vma cannot write\n");
    			goto failed;
    	}
    	break;
    	case 1: /* error code flag : (W/R=0, P=1): read, present */
    		cprintf("do_pgfault failed: error code flag = read AND present\n");
    		goto failed;
    	case 0: /* error code flag : (W/R=0, P=0): read, not present */
    	if (!(vma->vm_flags & (VM_READ | VM_EXEC))) {
    		cprintf("do_pgfault failed: error code flag = read AND not present,
    but the addr's vma cannot read or exec\n");
    		goto failed;
         }
    }
     
    
  3. 接下来根据合法虚拟地址(mm_struct中保存的合法虚拟地址链表中可查询到)的标志,来生成对应产生的物理页的权限

        uint32_t perm = PTE_U;
        if (vma->vm_flags & VM_WRITE) { //VM_WRITE 0x00000002
        	perm |= PTE_W; //可写的
        }
        addr = ROUNDDOWN(addr, PGSIZE);
        //把原来的addr按PSIZE=4096的倍数向下舍去
        ret = -E_NO_MEM;//E_NO_MEM 4 因为内存不足请求失败
        pte_t *ptep=NULL;
    
    
  4. 之后就是要代码实现的部分

    ①获取发生缺页的虚拟地址

    ②如果需要的物理页是没有被分配的,分配物理页并将其于虚拟页建立映射关系,perm是一个PTE可写标志位

    相关的宏定义及函数:

    get_pte //pmm.c lab2完成的根据虚拟线性地址返回pte的函数,如果不存在就分配一个
    pgdir_alloc_page //分配一个物理页并将它与其对应的虚拟地址建立映射关系
    VM_WRITE //vmm.h =0x00000002 即vm_flags第1位,1可写0只读
    PTE_W PTE_U//同lab2分别表示一级/二级页表是否可写 用户是否有访问权
    mm->pgdir //vmm.h vma的对应PDT
    

    实现

    ptep = get_pte(mm->pgdir, addr, 1);
    if (*ptep == 0) {
    // 如果需要的物理页是没有分配(没有映射)且没有被换出到外存中
    //addr就是前面的经过rounddown的虚拟页地址
    //perm 是前面PTE_W PTE_U处理后有权限的flag
    struct Page* page = pgdir_alloc_page(mm->pgdir, addr, perm);
    // 分配物理页,并且与对应的虚拟页建立映射关系
    }
    

    回答问题

    1. 请描述页目录项(Page Directory Entry)和页表项(Page Table Entry)中组成部分对 ucore实现页替换算法的潜在用处。

    2. 如果ucore的缺页服务例程在执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事 情?

    1、PDE和PTE中的低位都有许多保留位以供操作系统使用,OS可以利用这些位来完成一些其他的内存 管理相关的算法,比如在这里保存最近一段时间内该页被访问的次数,可以借助位判断实现 Enhanced Clock算法。这些保留位有利于OS进行功能的拓展

    2、Page Fault的缺页异常应与正常出现页访问异常的处理相一致

    ​ ①将发生错误的线性地址保存在cr2寄存器中;

    ​ ②在中断栈中依次压入EFLAGS,CS, EIP,以及页访问异常码error code,由于ISR一定是运行在内核态下的,因此不需要压入ss和esp以及进行栈的切换;

    ​ ③根据中断描述符表查询到对应页访问异常的ISR,跳转到对应的ISR处执行,接下来将由软件进行处理;

练习二:补充完成基于FIFO的页面替换算法

页错误异常

页错误异常发生时,有可能是因为页面保存在swap区或者磁盘文件上造成的,所以我们需要通过页面分配解决这个问题。 页面替换主要分为两个方面,页面换出和页面换入。

  • 页面换入主要在上述的do_pgfault()函数实现;
  • 页面换出主要在swap_out_vistim()函数实现。

页面换入部分

do_pgfault()函数中的部分代码

已知该物理页被换到外存,首先判断是否对交换机制进行了正确的初始化

根据mm和addr提供的信息将虚拟页对应的物理页从外存换入内存

给换入的物理页与虚拟页建立映射关系

将物理页设置为可被换出

else {  //页表项非空,尝试换入页面 
        if(swap_init_ok) {// 判断交换机制是否正确初始化swapinit后会将该变量置1
            struct Page *page=NULL; //根据mm结构和addr地址,尝试将硬盘中的内容换入至page中
            if ((ret = swap_in(mm, addr, &page)) != 0) {
                cprintf("swap_in in do_pgfault failed\n");
                goto failed;
            }    
            page_insert(mm->pgdir, page, addr, perm); //建立虚拟地址和物理地址之间的对应关系 
            swap_map_swappable(mm, addr, page, 1); //将此页面设置为可交换的  
            page->pra_vaddr = addr;// 同时在物理页中维护其对应到的虚拟页的信息
									// pra_vaddr用来记录此物理页对应的虚拟页起始地址
        }
        else {
            cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
            goto failed;
        }
   }
   ret = 0;
failed:
    return ret;
}

页面换出部分

换出页面的时机相对复杂一些,针对不同的策略有不同的时机。ucore目前大致有两种策略,即积极换出策略和消极换出策略。积极换出策略是指操作系统周期性地(或在系统不忙的时候)主动把某些认为“不常用”的页换出到硬盘上,从而确保系统中总有一定数量的空闲页存在,这样当需要空闲页时,基本上能够及时满足需求;消极换出策略是指,只是当试图得到空闲页时,发现当前没有空闲的物理页可供分配,这时才开始查找“不常用”页面,并把一个或多个这样的页换出到硬盘上。在实验三中的基本练习中,支持上述的第二种情况。对于第一种积极换出策略,即每隔1秒执行一次的实现积极的换出策略,可考虑在扩展练习中实现。对于第二种消极的换出策略,则是在ucore调用alloc_pages函数获取空闲页时,此函数如果发现无法从物理内存页分配器获得空闲页,就会进一步调用swap_out函数换出某页,实现一种消极的换出策略。

FIFO替换算法会维护一个队列,队列按照页面调用的次序排列,越早被加载到内存的页面会越早被换出。

_fifo_map_swappable: 将最近被用到的页面添加到算法所维护的次序队列 因为FIFO基于双向链表实现,所以加到链表的最后其实只用list_add_before将它加到head的前面就可以 了

static int _fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int
swap_in)
{
list_entry_t *head=(list_entry_t*) mm->sm_priv;// 找到链表入口
list_entry_t *entry=&(page->pra_page_link);//找到当前物理页用于组织成链表的list_entry_t
assert(entry != NULL && head != NULL);
//record the page access situlation
/*LAB3 EXERCISE 2: YOUR CODE*/
//(1)link the most recent arrival page at the back of the pra_list_head
qeueue.
list_add_before(head,entry); // 将当前指定的物理页插入到链表的末尾
return 0;
}

_fifo_swap_out_victim 根据FIFO思想,找出最先访问即第一个进入链表的也就是头结点并删掉就好了

static int _fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int
in_tick)
{
list_entry_t *head=(list_entry_t*) mm->sm_priv;// 找到链表的入口
assert(head != NULL);
assert(in_tick==0);
/* Select the victim */
/*LAB3 EXERCISE 2: YOUR CODE*/
//(1) unlink the earliest arrival page in front of pra_list_head qeueue
//(2) set the addr of addr of this page to ptr_page
list_entry_t *le = list_next(head); // 取出链表头,即最先进入的物理页
assert(le != head); // 确保不是head
//pra_page_link是Page结构中用来构造按第一次访问时间排序的链表
struct Page *page = le2page(le, pra_page_link);//找到其对应的物理页的Page结构
list_del(le); // 从链表上删除将被换出的物理页
*ptr_page = page;
return 0;
}

回答问题

如果要在ucore上实现"extended clock页替换算法"请给你的设计方案,现有的swap_manager框 架是否足以支持在ucore中实现此算法?如果是,请给你的设计方案。如果不是,请给出你的新的 扩展和基此扩展的设计方案。并需要回答如下问题:

  1. 需要被换出的页的特征是什么?
  2. 在ucore中如何判断具有这样特征的页?
  3. 何时进行换入和换出操作?

1、被换出的页特征: 该物理页在当前指针上一次扫过之前没有被访问; 该物理页的内容与其在外存中保存的数据是一致的, 即没有被修改过的;

2、如何判断: 当内存页被访问后,MMU将在对应的页表项的 PTE_A 置1(Access) 当内存页被修改后,MMU将在对应的页表项的 PTE_D 置1(Dirty)

3、何时操作: 在产生缺页现象page fault中断的时候进行换入操作 当位于物理页框中的内存被页面替换算法所选择时,需要进行换出操作

实验结果

moocos-> cd lab3
[~/moocos/ucore_lab/labcodes/lab3]
moocos-> make qemu
(THU.CST) os is loading ...

Special kernel symbols:
  entry  0xc010002a (phys)
  etext  0xc0108b5c (phys)
  edata  0xc0120a68 (phys)
  end    0xc0121bb0 (phys)
Kernel executable memory footprint: 135KB
ebp:0xc011ff38 eip:0xc01009df args:0x00010094 0x00000000 0xc011ff68 0xc01000cb 
    kern/debug/kdebug.c:308: print_stackframe+21
ebp:0xc011ff48 eip:0xc0100cce args:0x00000000 0x00000000 0x00000000 0xc011ffb8 
    kern/debug/kmonitor.c:129: mon_backtrace+10
ebp:0xc011ff68 eip:0xc01000cb args:0x00000000 0xc011ff90 0xffff0000 0xc011ff94 
    kern/init/init.c:56: grade_backtrace2+33
ebp:0xc011ff88 eip:0xc01000f4 args:0x00000000 0xffff0000 0xc011ffb4 0x0000002a 
    kern/init/init.c:61: grade_backtrace1+38
ebp:0xc011ffa8 eip:0xc0100112 args:0x00000000 0xc010002a 0xffff0000 0x0000001d 
    kern/init/init.c:66: grade_backtrace0+23
ebp:0xc011ffc8 eip:0xc0100137 args:0xc0108b7c 0xc0108b60 0x00001148 0x00000000 
    kern/init/init.c:71: grade_backtrace+34
ebp:0xc011fff8 eip:0xc010007f args:0x00000000 0x00000000 0x0000ffff 0x40cf9a00 
    kern/init/init.c:31: kern_init+84
memory management: default_pmm_manager
e820map:
  memory: 0009fc00, [00000000, 0009fbff], type = 1.
  memory: 00000400, [0009fc00, 0009ffff], type = 2.
  memory: 00010000, [000f0000, 000fffff], type = 2.
  memory: 07efe000, [00100000, 07ffdfff], type = 1.
  memory: 00002000, [07ffe000, 07ffffff], type = 2.
  memory: 00040000, [fffc0000, ffffffff], type = 2.
check_alloc_page() succeeded!
check_pgdir() succeeded!
check_boot_pgdir() succeeded!
-------------------- BEGIN --------------------
PDE(0e0) c0000000-f8000000 38000000 urw
  |-- PTE(38000) c0000000-f8000000 38000000 -rw
PDE(001) fac00000-fb000000 00400000 -rw
  |-- PTE(000e0) faf00000-fafe0000 000e0000 urw
  |-- PTE(00001) fafeb000-fafec000 00001000 -rw
--------------------- END ---------------------
check_vma_struct() succeeded!
page fault at 0x00000100: K/W [no page found].
check_pgfault() succeeded!
check_vmm() succeeded.
ide 0:      10000(sectors), 'QEMU HARDDISK'.
ide 1:     262144(sectors), 'QEMU HARDDISK'.
SWAP: manager = fifo swap manager
BEGIN check_swap: count 31995, total 31995
setup Page Table for vaddr 0X1000, so alloc a page
setup Page Table vaddr 0~4MB OVER!
set up init env for check_swap begin!
page fault at 0x00001000: K/W [no page found].
page fault at 0x00002000: K/W [no page found].
page fault at 0x00003000: K/W [no page found].
page fault at 0x00004000: K/W [no page found].
set up init env for check_swap over!
write Virt Page c in fifo_check_swap
write Virt Page a in fifo_check_swap
write Virt Page d in fifo_check_swap
write Virt Page b in fifo_check_swap
write Virt Page e in fifo_check_swap
page fault at 0x00005000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x1000 to disk swap entry 2
write Virt Page b in fifo_check_swap
write Virt Page a in fifo_check_swap
page fault at 0x00001000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x2000 to disk swap entry 3
swap_in: load disk swap entry 2 with swap_page in vadr 0x1000
write Virt Page b in fifo_check_swap
page fault at 0x00002000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x3000 to disk swap entry 4
swap_in: load disk swap entry 3 with swap_page in vadr 0x2000
write Virt Page c in fifo_check_swap
page fault at 0x00003000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x4000 to disk swap entry 5
swap_in: load disk swap entry 4 with swap_page in vadr 0x3000
write Virt Page d in fifo_check_swap
page fault at 0x00004000: K/W [no page found].
swap_out: i 0, store page in vaddr 0x5000 to disk swap entry 6
swap_in: load disk swap entry 5 with swap_page in vadr 0x4000
count is 7, total is 7
check_swap() succeeded!
++ setup timer interrupts
100 ticks
100 ticks

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值