ucore lab3 虚拟内存管理

做完实验二后,大家可以了解并掌握物理内存管理中的连续空间分配算法的具体实现以及如何建立二级页表。本次实验是在实验二的基础上,借助于页表机制和实验一中涉及的中断异常处理机制,完成Page Fault异常处理和FIFO页替换算法的实现。实验原理最大的区别是在设计了如何在磁盘上缓存内存页,从而能够支持虚存管理,提供一个比实际物理内存空间“更大”的虚拟内存空间给系统使用。

实验目的

  • 了解虚拟内存的Page Fault异常处理实现
  • 了解页替换算法在操作系统中的实现

实验内容

本次实验是在实验二的基础上,借助于页表机制和实验一中涉及的中断异常处理机制,完成Page Fault异常处理和FIFO页替换算法的实现,结合磁盘提供的缓存空间,从而能够支持虚存管理,提供一个比实际物理内存空间“更大”的虚拟内存空间给系统使用。这个实验与实际操作系统中的实现比较起来要简单,不过需要了解实验一和实验二的具体实现。实际操作系统系统中的虚拟内存管理设计与实现是相当复杂的,涉及到与进程管理系统、文件系统等的交叉访问。如果大家有余力,可以尝试完成扩展练习,实现extended clock页替换算法。

练习

练习0:填写已有实验

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

打开meld软件,将lab2和lab3文件夹进行比较,按照提示将lab2中的文件复制到lab3中。
在这里插入图片描述
找到带有*号的文件,右键copy to right即可将lab2的文件复制到lab3中
在这里插入图片描述
经过比较,要复制的文件为

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

其他文件不用作任何修改,可直接使用。

练习1:给未被映射的地址映射上物理页(需要编程)

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

make qemu

后,如果通过check_pgfault函数的测试后,会有“check_pgfault() succeeded!”的输出,表示练习1基本正确。

请在实验报告中简要说明你的设计实现过程。请回答如下问题:

  • 请描述页目录项(Page Directory Entry)和页表项(Page Table Entry)中组成部分对ucore实现页替换算法的潜在用处。
  • 如果ucore的缺页服务例程在执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?
准备知识

什么是虚拟内存?简单地说是指程序员或CPU“看到”的内存。但有几点需要注意:
1.虚拟内存单元不一定有实际的物理内存单元对应,即实际的物理 内存单元可能不存在;
2.如果虚拟内存单元对应有实际的物理内存单元,那二者的地址一般是不相等的;
3.通过操作系统实现的某种内存映射可建立虚拟内存与物理内存的对应关系,使得程序员或CPU访问的虚拟内存地址会自动转换为一个物理内存地址。

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

  • 目标页帧不存在(页表项全为0,即该线性地址与物理地址尚未建立映射或者已经撤销);
  • 相应的物理页帧不在内存中(页表项非空,但Present标志位=0,比如在swap分区或磁盘文件上),这在本次实验中会出现,我们将在下面介绍换页机制实现时进一步讲解如何处理;
  • 不满足访问权限(此时页表项P标志=1,但低权限的程序试图访问高权限的地址空间,或者有程序试图写只读页面)。

出现上述异常时,操作系统便会调用do_pgfault函数,进行相应的处理。其中一些错误是需要反馈给用户的(即不可修复的,如权限错误),而有一些是需要操作系统来进行相应的操作以便让程序顺利进行下去。

do_pgfault()的调用关系图:
在这里插入图片描述
do_pgfault()函数从CR2寄存器中获取页错误异常的虚拟地址,根据error code来查找这个虚拟地址是否在某一个VMA的地址范围内,并且具有正确的权限。如果满足上述两个要求,则需要为分配一个物理页。

所以出现page fault后,会有一个中断状态指针tf,传到trap()中处理:

void
trap(struct trapframe *tf) {
    // dispatch based on what type of trap occurred
    trap_dispatch(tf);
}

调用trap_dispatch():

static void
trap_dispatch(struct trapframe *tf) {
    char c;

    int ret;

    switch (tf->tf_trapno) {
    case T_PGFLT:  //page fault页访问错误
        if ((ret = pgfault_handler(tf)) != 0) {
            print_trapframe(tf);
            panic("handle pgfault failed. %e\n", ret);
        }
        break;

因为此时应该是page fault,所以调用pgfault_handler():

static int
pgfault_handler(struct trapframe *tf) {
    extern struct mm_struct *check_mm_struct;
    print_pgfault(tf);
    if (check_mm_struct != NULL) {
        return do_pgfault(check_mm_struct, tf->tf_err, rcr2());
    }
    //CR2存储了产生异常的线性地址
    panic("unhandled page fault.\n");
}

然后调用了do_pgfault()。这和调用关系图完全吻合。

需要用到的数据结构:

vma_struct

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_struct是描述应用程序对虚拟内存“需求”的数据结构。vm_start和vm_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   //可执行

vm_mm是一个指针,指向一个比vma_struct更高的抽象层次的数据结构mm_struct 。

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之间的联系。

完成do_pgfault函数

首先查看 do_pgfault函数原型

int
do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr)

传入的三个参数分别为

  • mm:应用程序虚拟存储总管
  • error_code:错误的类型
  • addr:具体出错的虚拟地址

具体的函数代码为:

int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) {
    int ret = -E_INVAL; 
 /*对虚拟地址进行判断,如果虚拟地址的范围超过了限制,
 或者是虚拟地址无法被查找到,即可以说该地址是不合法的,
 进行了一次非法访问,那么可以直接报错。*/
    //尝试寻找包括addr的vma
    struct vma_struct *vma = find_vma(mm, addr);
    pgfault_num++;
    //判断addr是否在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位分别是:P标志(位0)最低位:表示当前的错误是
    由于不存在页面(0)引起,还是由于违反访问权限(1)引起。
    W / R标志(位1):表示当前错误是由于读操作(0)
    引起还是还是写操作(1)引起。*/
    switch (error_code & 3) {
    default:
            /* error code flag : default is 3 ( W/R=1, P=1): write, present */
    case 2: /* 申请写操作,物理内存中不存在,并且对应地址的内容不允许写*/
        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: /* 申请读操作,并且物理内存中存在(因此已经报错,所以是权限不够) */
        cprintf("do_pgfault failed: error code flag = read AND present\n");
        goto failed;
    case 0: /* 申请读操作但是物理内存中不存在,并且该地址数据不允许被读或者加载*/ 
        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;
        }
    }
    uint32_t perm = PTE_U;//prem:给物理页赋予权限的中间变量
    if (vma->vm_flags & VM_WRITE) {
        perm |= PTE_W;
    }
    addr = ROUNDDOWN(addr, PGSIZE);
    ret = -E_NO_MEM;
    pte_t *ptep=NULL;
    
    //修改部分
    //查找页目录,如果不存在则失败
    if ((ptep = get_pte(mm->pgdir, addr, 1)) == NULL) {
        cprintf("get_pte in do_pgfault failed\n");
        goto failed;
    }
    if (*ptep == 0) { // 如果是新创建的二级页表。
        //初始化建立虚拟地址与物理页之间的映射关系
        if (pgdir_alloc_page(mm->pgdir, addr, perm) == NULL) {
            //初始化失败报错并退出。
            cprintf("pgdir_alloc_page in do_pgfault failed\n");
            goto failed;
        }
    }
   //练习二实现    
   //页表项非空,尝试换入页面
    else { 
   
        if(swap_init_ok) {
            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);
	    //建立虚拟地址和物理地址之间的对应关系,perm设置物理页权限,为了保证和它对应的虚拟页权限一致
            swap_map_swappable(mm, addr, page, 1);//将此页面设置为可交换的 ,也添加到算法所维护的次序队列
	    page->pra_vaddr = addr;		//设置页对应的虚拟地址
        }
        else {
            cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
            goto failed;
        }
   }
   ret = 0;
failed:
    return ret;
}

对于修改部分,首先判断完错误类型后,如果能够顺利通过的合法性判断,那么此次虚拟内存访问就被认为是合法的,此时,页访问异常的原因,是由于该合法虚拟页,没有对应物理页的映射导致,因此下一步要建立起这个映射。建立起这个映射首先要改变一下待换入页面的权限值:首先,无论如何这个页面需要能够被用户访问,其次,如果对应映射的vma有写的权限,该物理页也需要可写。
然后,我们通过当前应用程序mm所指向的一级页表,以及虚拟地址,去查询有没有对应的二级页表,如果查询结果为NULL,那么报错,因为没有对应的二级页表项,它根本不存在,也不知道用什么物理页去映射(当然,这里不可能不存在,如果查找到不存在的情况,由于get_pte的create标记位为1,那么会创建一个新的二级页表);之后,如果是上述新创建的二级页表,那么*ptep就会是0,代表页表为空,此时调用pgdir_alloc_page,对它进行初始化。如果初始化失败则直接报错并退出。

运行结果:
在这里插入图片描述

可以看到,通过check_pgfault函数的测试,练习1通过。

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

分页机制的实现,确保了虚拟地址和物理地址之间的对应关系,一方面,通过查找虚拟地址是否存在于一二级页表中,可以容易发现该地址是否是合法的,同时可以通过修改映射关系即可实现页替换操作。另一方面,在实现页替换时涉及到换入换出:换入时需要将某个虚拟地址对应的磁盘的一页内容读入到内存中,换出时需要将某个虚拟页的内容写到磁盘中的某个位置。

另外,基于页表实现了地址的分段操作,在这里,一个物理地址不同的位数上,会存储一系列不同的信息,比如,pg_fault函数中的权限判断就用到了这方面的操作,观察代码中的宏定义,我们得到部分标记位的含义如下:

#define PTE_P       0x001          // Present 对应物理页面是否存在
 
#define PTE_W       0x002          // Writeable 对应物理页面是否可写
 
#define PTE_U       0x004          // User 对应物理页面用户态是否可以访问
 
#define PTE_PWT     0x008          // Write-Through 对应物理页面在写入时是否写透(即向更低级储存设备写入)
 
#define PTE_PCD     0x010          //Cache-Disable 对应物理页面是否能被放入高速缓存
 
#define PTE_A       0x020          // Accessed 对应物理页面是否被访问
 
#define PTE_D       0x040          // Dirty 对应物理页面是否被写入
 
#define PTE_PS      0x080          // Page Size 对应物理页面的页面大小
 
#define PTE_MBZ     0x180          // Bits must be zero 必须为零的部分
 
#define PTE_AVAIL   0xE00          // Available for software use 用户可自定义的部分
  • 如果ucore的缺页服务例程在执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?

CPU会把产生异常的线性地址存储在CR2寄存器中,并且把表示页访问异常类型的值(简称页访问异常错误码,errorCode)保存在中断栈中。之后通过上述分析的trap–> trap_dispatch–>pgfault_handler–>do_pgfault调用关系,一步步做出处理。

练习2:补充完成基于FIFO的页面替换算法(需要编程)

完成vmm.c中的do_pgfault函数,并且在实现FIFO算法的swap_fifo.c中完成map_swappable和swap_out_victim函数。通过对swap的测试。注意:在LAB3 EXERCISE 2处填写代码。执行

make qemu

后,如果通过check_swap函数的测试后,会有“check_swap() succeeded!”的输出,表示练习2基本正确。

请在实验报告中简要说明你的设计实现过程。

请在实验报告中回答如下问题:

  • 如果要在ucore上实现"extended clock页替换算法"请给你的设计方案,现有的swap_manager框架是否足以支持在ucore中实现此算法?如果是,请给你的设计方案。如果不是,请给出你的新的扩展和基此扩展的设计方案。并需要回答如下问题
  • 需要被换出的页的特征是什么?
  • 在ucore中如何判断具有这样特征的页?
  • 何时进行换入和换出操作?
准备知识

在练习1中,当页错误异常发生时,有可能是因为页面保存在swap区或者磁盘文件上造成的,所以我们需要利用页面替换算法解决这个问题。
而练习1和这里的关联就在于:页面替换主要分为两个方面,页面换出和页面换入。练习1实现的是页面换入,主要在上述的do_pgfault()函数实现;而练习2这里主要实现,页面换出,主要是在swap_out_vistim()函数。另外,在练习1还有一个函数叫做swappable,代表将该页面设置为可交换的。于是,练习2主要是对于这两个函数的实现。

实现页面的替换需要换入和换出,这里使用的是FIFO策略

先进先出(First In First Out, FIFO)页替换算法:该算法总是淘汰最先进入内存的页,即选择在内存中驻留时间最久的页予以淘汰。只需把一个应用程序在执行过程中已调入内存的页按先后次序链接成一个队列,队列头指向内存中驻留时间最久的页,队列尾指向最近被调入内存的页。这样需要淘汰页时,从队列头很容易查找到需要淘汰的页。FIFO算法只是在应用程序按线性顺序访问地址空间时效果才好,否则效率不高。因为那些常被访问的页,往往在内存中也停留得最久,结果它们因变“老”而不得不被置换出去。FIFO算法的另一个缺点是,它有一种异常现象(Belady现象),即在增加放置页的页帧的情况下,反而使页访问异常次数增多。

为实现各种页替换算法设计了一个页替换算法的类框架swap_manager

struct swap_manager
{
     const char *name;
     /* Global initialization for the swap manager */
     int (*init)            (void);
     /* Initialize the priv data inside mm_struct */
     int (*init_mm)         (struct mm_struct *mm);
     /* Called when tick interrupt occured */
     int (*tick_event)      (struct mm_struct *mm);
     /* Called when map a swappable page into the mm_struct */
     int (*map_swappable)   (struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in);
     /* When a page is marked as shared, this routine is called to
      * delete the addr entry from the swap manager */
     int (*set_unswappable) (struct mm_struct *mm, uintptr_t addr);
     /* Try to swap out a page, return then victim */
     int (*swap_out_victim) (struct mm_struct *mm, struct Page **ptr_page, int in_tick);
     /* check the page relpacement algorithm */
     int (*check_swap)(void);     
};

这里关键的两个函数指针是map_swappable和swap_out_vistim,前一个函数用于记录页访问情况相关属性,后一个函数用于挑选需要换出的页。显然第二个函数依赖于第一个函数记录的页访问情况。tick_event函数指针也很重要,结合定时产生的中断,可以实现一种积极的换页策略。

完成函数

首先完成do_pgfault()函数中的部分代码完成页面换入部分

do_pgfault()函数

如果页表项非空,尝试换入页面,接着根据mm结构和addr地址,尝试将硬盘中的内容换入至page中,建立虚拟地址和物理地址之间的对应关系 ,再将此页面设置为可交换的 。

   //页表项非空,尝试换入页面
    else { 
   
        if(swap_init_ok) {
            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);
	    //建立虚拟地址和物理地址之间的对应关系,perm设置物理页权限,为了保证和它对应的虚拟页权限一致
            swap_map_swappable(mm, addr, page, 1);//将此页面设置为可交换的 ,也添加到算法所维护的次序队列
	    page->pra_vaddr = addr;		//设置页对应的虚拟地址
        }
        else {
            cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
            goto failed;
        }
   }

在换入时,需要先检查产生访问异常的地址是否属于某个vma表示的合法虚拟地址,并且保存在硬盘的swap文件中(对应的PTE的高24位不为0)。如果满足以上亮点,则执行swap_in() 函数换入页面。

下面具体实现FIFO算法:
完成FIFO算法主要在于三个函数

_fifo_init_mm函数

函数功能:

FIFO算法的初始化

static int _fifo_init_mm(struct mm_struct *mm)
{     
     list_init(&pra_list_head);
     mm->sm_priv = &pra_list_head;
     cprintf(" mm->sm_priv %x in fifo_init_mm\n",mm->sm_priv);
     return 0;
}
_fifo_map_swappable函数

函数功能:

将最近被用到的页面添加到算法所维护的次序队列。

实现思路:

建立一个指针指向最近使用的页面,然后在列表中新建一个空指针,将最近用到的页面添加到次序队尾

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);          
        assert(entry != NULL && head != NULL);   
        list_add(head, entry); //将最近用到的页面添加到次序队尾  
        return 0;   
    }
_fifo_swap_out_victim函数

函数功能:

​查询哪个页面需要被换出,并执行换出操作

实现思路:

​使用链表操作,删除掉最早进入的那个页,并按照注释将这个页面给到传入参数ptr_page。

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); 
    // 找到对应的物理页面的Page结构
    struct Page *page = le2page(le, pra_page_link);
    // 从链表上删除取出的即将被换出的物理页面
    list_del(le); 
    *ptr_page = page;
     return 0;
}

运行结果
在这里插入图片描述

查看成绩
在这里插入图片描述

可以看到,替换算法通过,练习2完成。

回答问题
  • 如果要在ucore上实现"extended clock页替换算法"请给你的设计方案,现有的swap_manager框架是否足以支持在ucore中实现此算法?如果是,请给你的设计方案。如果不是,请给出你的新的扩展和基此扩展的设计方案。并需要回答如下问题
  • 需要被换出的页的特征是什么?
  • 在ucore中如何判断具有这样特征的页?
  • 何时进行换入和换出操作?

首先补充一下时钟页替换算法

时钟(Clock)页替换算法:是LRU算法的一种近似实现。时钟页替换算法把各个页面组织成环形链表的形式,类似于一个钟的表面。然后把一个指针(简称当前指针)指向最老的那个页面,即最先进来的那个页面。另外,时钟算法需要在页表项(PTE)中设置了一位访问位来表示此页表项对应的页当前是否被访问过。当该页被访问时,CPU中的MMU硬件将把访问位置“1”。当操作系统需要淘汰页时,对当前指针指向的页所对应的页表项进行查询,如果访问位为“0”,则淘汰该页,如果该页被写过,则还要把它换出到硬盘上;如果访问位为“1”,则将该页表项的此位置“0”,继续访问下一个页。该算法近似地体现了LRU的思想,且易于实现,开销少,需要硬件支持来设置访问位。时钟页替换算法在本质上与FIFO算法是类似的,不同之处是在时钟页替换算法中跳过了访问位为1的页。

现有的swap_manager框架足以支持在ucore中实现此算法。在练习1已经回答了PTE的组成,在mmu.h通过宏定义的方式定义了PTE组成,其中包括脏位和访问位,因此我们可以根据这两位知道虚拟页是否被访问过以及是否被写过,之后将FIFO的队列式链表改造成环形循环列表,原来的初始化函数不需要改,维护链表的函数_fifo_map_swappable函数需要进行小修改,令每次加入新结点后不仅将其加在最后一个链表项后面,而且要将其指针指向head,形成环形循环链表。

现需要对swap_out_victim进行显著改变:

​ 总是从当前指针开始,对循环链表逐一扫描,通过判断每个链表项所指的物理页状态来决定进行何种操作:
(用<access,dirty>表示物理页状态)

  • 如果状态是(0, 0),说明当前数据无效且没有被修改,只需将该物理页面从链表上去下,该物理页面记为换出 面,且不需要将其写入swap分区;
  • 如果状态是(1, 0),将该物理页对应的虚拟页的PTE中的访问位都置成0,然后指针跳转到下一个物理页面;
  • 如果状态是(0,1),则将该物理页对应的虚拟页的PTE中的dirty位改成0,并且将该物理页写入到外存中,然后指针跳转到下一个物理页;
  • 如果状态是(1,1),则该物理页的所有对应虚拟页的PTE中的访问为置成0,然后指针跳转到下一个物理页面;

基于此即可实现简单的时钟页替换算法

下面回答问题

1.需要被换出的页的特征是什么?

最早被换入,且最近没有再被访问的页

2.在ucore中如何判断具有这样特征的页?

通过判断是否访问过,对未访问过的物理页FIFO即可

3.何时进行换入和换出操作?

当需要调用的页不在页表中时,并且在页表已满的情况下,会引发PageFault,此时需要进行换入和换出操作。具体是当保存在磁盘中的内存需要被访问时,需要进行换入操作;当位于物理页框中的内存被页面替换算法选择时,需要进行换出操作。

扩展练习 Challenge 1:实现识别dirty bit的 extended clock页替换算法(需要编程)

沿着链表遍历,将最近未被访问且未被修改的页换出内存。

kern/mm/mmu.h文件有如下定义:

#define PTE_A           0x020                   // Accessed
#define PTE_D           0x040                   // Dirty

PTE_A是表示访问的标志位,PTE_D是表示修改的标志位

仿照swap_fifo.c中的_fifo_check_swap,编写_clock_check_swap

list_entry_t pra_list_head;

static int
_clock_init_mm(struct mm_struct *mm)
{     
     list_init(&pra_list_head);
     mm->sm_priv = &pra_list_head;
     return 0;
}
//此处和FIFO的初始化方法相同
static int
_clock_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);

    assert(entry != NULL && head != NULL);// 将新页插入到链表最后
    list_add(head -> prev, entry);// 新插入的页dirty bit标记为0.
    struct Page *ptr = le2page(entry, pra_page_link);
    pte_t *pte = get_pte(mm -> pgdir, ptr -> pra_vaddr, 0);
    *pte &= ~PTE_D;
    return 0;
}
static int
_clock_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);

     list_entry_t *p = head;
     while (1) {
         p = list_next(p);
         if (p == head) {
             p = list_next(p);
         }
         struct Page *ptr = le2page(p, pra_page_link);
         pte_t *pte = get_pte(mm -> pgdir, ptr -> pra_vaddr, 0);
         //获取页表项
         if ((*pte & PTE_D) == 1) {// 如果dirty bit为1,改为0
             *pte &= ~PTE_D;
         } 
         else 
         {// 如果dirty bit为0,则标记为换出页
             *ptr_page = ptr;
             list_del(p);
             break;
         }
     }
     return 0;
}
static int
_clock_check_swap(void) {
    cprintf("write Virt Page c in clock_check_swap\n");
    *(unsigned char *)0x3000 = 0x0c;
    assert(pgfault_num==4);
    cprintf("write Virt Page a in clock_check_swap\n");
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num==4);
    cprintf("write Virt Page d in clock_check_swap\n");
    *(unsigned char *)0x4000 = 0x0d;
    assert(pgfault_num==4);
    cprintf("write Virt Page b in clock_check_swap\n");
    *(unsigned char *)0x2000 = 0x0b;
    assert(pgfault_num==4);
    cprintf("write Virt Page e in clock_check_swap\n");
    *(unsigned char *)0x5000 = 0x0e;
    assert(pgfault_num==5);
    cprintf("write Virt Page b in clock_check_swap\n");
    *(unsigned char *)0x2000 = 0x0b;
    assert(pgfault_num==5);
    cprintf("write Virt Page a in clock_check_swap\n");
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num==6);
    cprintf("write Virt Page b in clock_check_swap\n");
    *(unsigned char *)0x2000 = 0x0b;
    assert(pgfault_num==7);
    cprintf("write Virt Page c in clock_check_swap\n");
    *(unsigned char *)0x3000 = 0x0c;
    assert(pgfault_num==8);
    cprintf("write Virt Page d in clock_check_swap\n");
    *(unsigned char *)0x4000 = 0x0d;
    assert(pgfault_num==9);
    cprintf("write Virt Page e in clock_check_swap\n");
    *(unsigned char *)0x5000 = 0x0e;
    assert(pgfault_num==10);
    cprintf("write Virt Page a in clock_check_swap\n");
    assert(*(unsigned char *)0x1000 == 0x0a);
    *(unsigned char *)0x1000 = 0x0a;
    assert(pgfault_num==11);
    return 0;
}
static int
_clock_init(void)
{
    return 0;
}

static int
_clock_set_unswappable(struct mm_struct *mm, uintptr_t addr)
{
    return 0;
}

static int
_clock_tick_event(struct mm_struct *mm)
{ return 0; }

最后对swap_manager_clock进行初始化:

struct swap_manager swap_manager_clock =
{
     .name            = "extend_clock swap manager",
     .init            = &_clock_init,
     .init_mm         = &_clock_init_mm,
     .tick_event      = &_clock_tick_event,
     .map_swappable   = &_clock_map_swappable,
     .set_unswappable = &_clock_set_unswappable,
     .swap_out_victim = &_clock_swap_out_victim,
     .check_swap      = &_clock_check_swap,
};

为了调用extended clock算法,将swap.cswap_init函数的

sm = &swap_manager_fifo;改成sm = &swap_manager_clock;

结果如下:
在这里插入图片描述

可以看到,成功实现clock页替代算法

实验总结

这次实验涉及到了很多知识点,包括虚拟内存管理的基本概念与原理,page fault异常的处理流程,页替换算法,物理内存的管理。其对应的操作系统知识点为操作系统中虚拟内存机制的实现,操作系统中对具体某一个页面替换算法的实现,操作系统中的中断处理机制等。前者的知识点为后者中操作系统中的具体实现提供了理论基础

1.整个实验下来感觉自己收获很多,通过上网查找资料,自己深刻学习了虚拟内存管理的机制,通过实验更是加深了印象。
2.实验中遇到了很多的问题,比如进程虚拟内存的管理涉及到的一些结构体的关系,由于种类比较多很容易混乱,通过列图梳理了他们之间的关系。
3.操作系统这门课程比较基础,涉及到的内容比较多。有时学起来会比较吃力,但这门课程非常重要,是以后专业课的基石,希望在以后自己留给更多的时间在做实验上,从而更加深入的理解、掌握操作系统

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值