ucore lab3

UCORE实验3

实验目的

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

实验内容

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

练习0:填写已有实验

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

分析

使用meld修改即可。

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

完成do_pgfault(mm/vmm.c)函数,给未被映射的地址映射上物理页。设置访问权限的时候需要参考页面所在 VMA 的权限,同时需要注意映射物理页时需要操作内存控制 结构所指定的页表,而不是内核的页表。注意:在LAB2 EXERCISE 1处填写代码。执行make qemu后,如果通过check_pgfault函数的测试后,会有“check_pgfault() succeeded!”的输出,表示练习1基本正确。
请在实验报告中简要说明你的设计实现过程并回答如下问题:
请描述页目录项(Pag Director Entry)和页表(Page Table Entry)中组成部分对ucore实现页替换算法的潜在用处。
如果ucore的缺页服务例程在执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?

分析
1.完善do_pgfault函数练习一部分的代码

vmm.h描述了mm_struct,vma_struct等表述可访问的虚存地址访问的一些信息,所以vmm的设计实际上包括两个部分:mm_struct(mm)和vma_struct(vma),mm是一组具有相同PDT的连续虚拟内存区(vma)的内存管理器,vma是一个连续的虚拟内存区域,在mm中,有一个vma的线性链接表和一个vma的红黑链接表。vmm.c涉及mm,vma结构数据的创建/销毁/查找/插入等函数,这些函数在check_vma、check_vmm等中被使用,理解即可。而page fault处理相关的do_pgfault函数是本次实验需要涉及完成的。

do_pgfault是处理page fault(页故障)时执行的中断处理程序,其中mm是一组具有相同PDT的连续虚拟内存区的内存管理器,error_code是记录在trapframe->tf_err中的错误代码,由x86硬件设置,addr是导致内存访问异常的地址(CR2寄存器的内容)。其调用流程通常为: trap–>trap_dispatch–>pgfault_handler–>do_pgfault。

此外,处理器为do_pgfault函数提供了两种信息来帮助诊断异常并恢复:一是CR2寄存器中的内容。处理器在CR2寄存器中存储了产生异常的32位线性地址,do_pgfault函数可以使用这个地址来定位相应的页目录。page fault的错误代码的格式不同于其他异常的错误代码,这个错误代码告诉异常处理程序三件事:
– P标志(第0位)表明该异常是由于一个不存在的页面(0)造成的,还是由于违反访问权限或使用了一个保留位(1)造成的。
– W/R标志(位1)表示导致异常的内存访问是读(0)还是写(1)。
– U/S标志(位2)表示在异常发生时处理器是在用户模式(1)下执行的,还是管理员模式(0)。

完成练习1用到的一些宏和函数具体功能如下:
MACROs or Functions:
get_pte : get an pte and return the kernel virtual address of this pte for la, if the PT contians this pte didn’t exist, alloc a page for PT (notice the 3th parameter ‘1’)
获得一个pte,并返回这个pte的内核虚拟地址,并传给la。如果PT中不存在这个pte,则为PT分配一个新的页面(注意第3个参数’1’)。
pgdir_alloc_page : call alloc_page & page_insert functions to allocate a page size memory & setup an addr map pa<—>la with linear address la and the PDT pgdir
调用alloc_page和page_insert函数来分配一个页面大小的内存和设置一个具有线性地址la和PDT pgdir的地址映射——pa<—>la。
DEFINES:
VM_WRITE : If vma->vm_flags & VM_WRITE == 1/0, then the vma is writable/non writable
如果vma->vm_flags & VM_WRITE == 1/0, 那么vma是可写/不可写的。
PTE_W : 0x002 //page table/directory entry flags bit : Writeable
PTE_U : 0x004 //page table/directory entry flags bit : User can access
VARIABLES:
mm->pgdir : the PDT of these vma
这些vma的PDT

查看并根据注释完善do_pgfault练习一部分代码:

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,尝试获取触发pgfault的虚拟地址所在的虚拟页
    struct vma_struct *vma = find_vma(mm, addr);

    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;
    }
    //check the error_code,检测错误代码
    switch (error_code & 3) {
    default:
            /* 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,读,并且物理页存在,这种情况一般不会调用pgfault,因此直接failed*/
        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;
        }
    }
    /* IF (write an existed addr ) OR
     *    (write an non_existed addr && addr is writable) OR
     *    (read  an non_existed addr && addr is readable)
     * THEN
     *    continue process
     */
     //设置页表项对应权限
    uint32_t perm = PTE_U;
    if (vma->vm_flags & VM_WRITE) {
        perm |= PTE_W;
    }
    addr = ROUNDDOWN(addr, PGSIZE);
    ret = -E_NO_MEM;
    pte_t *ptep=NULL;

    /* LAB3 EXERCISE 1: 202008010404 */
    //try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT.
    //查找当前虚拟地址所对应的页表项
    if((ptep=get_pte(mm->pgdir,addr,1))=NULL){
        cprintf("get_pte in do_pgfault failed\n");
        goto failed; //failed:return ret,而ret等于-E_NO_MEM(error code),error.h中定义E_NO_MEM为Request failed due to memory shortage
    }
    //if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr.
    //如果有这个页表项,但页表项所对应的物理页不存在,则
    if(*ptep==0){
        if(pgdir_alloc_page(mm->pgdir, addr, perm) == NULL){ //分配一块物理页,并设置映射关系
            cprintf("pgdir_alloc_page in do_pgfault failed\n");
            goto failed;
        }
    }

结果如下:
屏幕截图 2022-05-06 210934

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

页目录项:
屏幕截图 2022-04-27 195747
页表项:
屏幕截图 2022-04-27 195952
由表可知无论是页目录项还是页表项,表项中均保留了3位忽略位供操作系统进行使用,能为实现一些页替换算法的时候提供支持。而事实上当PTE的Present位为0的时候,即该页不在内存中,有可能在磁盘中,因此OS将不会使用PTE上的内容,这就使得此时PTE上的其他位可以用于保存操作系统需要的信息,ucore就利用这些位来保存页替换算法里被换出的物理页在交换分区中的位置。又比如,PTE中的脏位,代表该页是否被修改过,PTE和PDE中都有访问位,用来判断某页是否被访问过。我们希望替换的页是访问少的,没修改过的,因为这样能将常用的页保留到内存中且减少了磁盘的I/O操作次数,而通过PDE和PTE中的信息,能帮助我们实现这样的一个页替换算法。

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

当启动分页机制以后,如果一条指令或数据的虚拟地址所对应的物理页框不在内存中或者访问的类型有错误(比如写一个只读页或用户态程序访问内核态的数据等),就会发生页访问异常。产生页访问异常的原因主要有:
目标页帧不存在(页表项全为0,即该线性地址与物理地址尚未建立映射或者已经撤销)。
相应的物理页帧不在内存中(页表项非空,但Present标志位=0,比如在swap分区或磁盘文件上)。
不满足访问权限(此时页表项P标志=1,但低权限的程序试图访问高权限的地址空间,或者有程序试图写只读页面)。

当出现上面情况之一,那么就会产生页面page fault(#PF)异常。CPU会把产生异常的线性地址存储在CR2中,并且把EFLAGS,CS, EIP,以及页访问异常码error code保存在中断栈中。中断服务例程会调用页访问异常处理函数do_pgfault进行具体处理,ucore中do_pgfault函数是完成页访问异常处理的主要函数,它根据从CPU的控制寄存器CR2中获取的页访问异常的物理地址以及根据errorCode的错误类型来查找此地址是否在某个VMA的地址范围内以及是否满足正确的读写权限,如果在此范围内并且权限也正确,这认为这是一次合法访问,但没有建立虚实对应关系。所以需要分配一个空闲的内存页,并修改页表完成虚地址到物理地址的映射,刷新TLB,然后调用iret中断,返回到产生页访问异常的指令处重新执行此指令。如果该虚地址不在某VMA范围内,则认为是一次非法访问。

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

完成vmm.c中的do_pgfault函数,并且在实现FIFO算法的swap_fifo.c中完成map_swappable和swap_out_vistim函数。通过对swap的测试。注意:在LAB2 EXERCISE 2处填写代码。执行make qemu后,如果通过check_swap函数的测试后,会有“check_swap() succeeded!”的输出,表示练习2基本正确,请在实验报告中简要说明你的设计实现过程。
请在实验报告中回答如下问题:
如果要在ucore上实现"extended clock页替换算法"请给你的设计方案,现有的swap_manager框架是否足以支持在ucore中实现此算法?如果是,请给你的设计方案。如果不是,请给出你的新的扩展和基此扩展的设计方案。并需要回答如下问题:
需要被换出的页的特征是什么?
在ucore中如何判断具有这样特征的页?
何时进行换入和换出操作?

分析
1.完善do_pgfault函数练习二部分的代码

现在我们认为这个PTE是一个交换页表项,我们应该把数据从磁盘加载到一个有物理地址的页面,并将物理地址与逻辑地址进行映射,触发交换管理器来记录这个页面的访问情况。
完成练习2用到的一些宏和函数具体功能如下:
MACROs or Functions:
swap_in(mm, addr, &page) : alloc a memory page, then according to the swap entry in PTE for addr,find the addr of disk page, read the content of disk page into this memroy page.
分配一个内存页,然后根据PTE中交换项中的地址,找到磁盘页的地址,将磁盘页的内容读到这个内存页中。
page_insert :build the map of phy addr of an Page with the linear addr la.
通过线性地址建立与一个Page的物理地址的映射。
swap_map_swappable : set the page swappable.
设置页面为可交换。

    /*LAB3 EXERCISE 2: 202008010404*/
    //如果这个页表项所对应的物理页存在,但不在内存中
    else {
        if(swap_init_ok) { //如果swap已经初始化完成
            struct Page *page=NULL;
            if ((ret = swap_in(mm, addr, &page)) != 0) { //According to the mm AND addr, try to load the content of right disk page into the memory which page managed.根据内存管理器和地址尝试将磁盘页内容读到内存页中
                cprintf("swap_in in do_pgfault failed\n");
                goto failed;
            }    
            page_insert(mm->pgdir, page, addr, perm); //According to the mm, addr AND page, setup the map of phy addr <---> logical addr.将该物理页与对应的虚拟地址关联,同时设置页表
            swap_map_swappable(mm, addr, page, 1); //make the page swappable.当前缺失的页已经加载回内存中,所以设置当前页为可swap
            page->pra_vaddr = addr;
        }
        else {
            cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
            goto failed;
        }
   }
2.查看并完善map_swappable和swap_out_vistim函数

操作系统迟早会碰到没有内存空闲空间而必须要置换出内存中某个“不常用”的页的情况。如何判断内存中哪些是“常用”的页,哪些是“不常用”的页,把“常用”的页保持在内存中,在物理内存空闲空间不够的情况下,把“不常用”的页置换到硬盘上就是页替换算法着重考虑的问题。容易理解,一个好的页替换算法会导致页访问异常次数少,也就意味着访问硬盘的次数也少,从而使得应用程序执行的效率就高。

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

FIFO实现过程:
(1)准备阶段:为了实现先进先出的页替换算法,我们应该管理所有可交换的页面,这样我们就可以根据时间顺序将这些页面链接到pra_list_head。首先,你应该熟悉list.h中的struct list,struct list是一个简单的双链表实现。其次你应该知道如何使用:list_init, list_add(list_add_after), list_add_before, list_del, list_next, list_prev等函数。
(2)_fifo_init_mm:初始化pra_list_head并让mm->sm_priv指向pra_list_head的addr。现在,从内存控制结构mm_struct,我们可以访问FIFO PRA。
(3)_fifo_map_swappable:根据FIFO PRA,我们应该在pra_list_head qeueue后面链接最近到达的页面。

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);
    //record the page access situlation
    /*LAB3 EXERCISE 2: 202008010404*/ 
    //(1)link the most recent arrival page at the back of the pra_list_head qeueue.
    list_add(head, entry);
    return 0;
}

(4)_fifo_swap_out_victim:根据FIFO PRA,我们应该在pra_list_head qeueue的前面断开最早到达的页面的链接,然后将*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: 202008010404*/ 
     //(1)  unlink the  earliest arrival page in front of pra_list_head qeueue
     //(2)  assign the value of *ptr_page to the addr of this page
     /* Select the tail */
     list_entry_t *le = head->prev;
     assert(head!=le);
     struct Page *p = le2page(le, pra_page_link);
     list_del(le);
     assert(p !=NULL);
     *ptr_page = p;
     return 0;
}

结果如下:
屏幕截图 2022-05-06 211104

3.现有的swap_manager框架是否足以支持在ucore中实现"extended clock页替换算法"

1.查看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函数指针也很重要,结合定时产生的中断,可以实现一种积极的换页策略。因此现有的swap_manager框架可以支持ucore实现extended clock页替换算法,设计方案如下:
①到实验二为止,我们知道目前表示内存中物理页使用情况的变量是基于数据结构Page的全局变量pages数组,pages的每一项表示了计算机系统中一个物理页的使用情况。为了表示物理页可被换出或已被换出的情况,可对Page数据结构进行扩展:

struct Page { 
    ...
    list_entry_t pra_page_link;     // used for pra (page replace algorithm)
    uintptr_t pra_vaddr;            // used for pra (page replace algorithm)
    ...      
} 

②完成了上述对物理页描述信息的拓展之后,考虑对FIFO算法的框架进行修改得到拓展时钟算法的框架。由于这两种算法都是将所有可以换出的物理页面均按照进入内存的顺序连成一个环形链表,然后初始化,将某个页面置为可以/不可以换出,这些函数均不需要进行大的修改(小的修改包括在初始化当前指针等),唯一需要进行重写的函数是选择换出物理页的函数swap_out_victim,对该函数的修改如下:
从当前指针开始,对环形链表进行扫描,根据指针指向的物理页的状态(表示为(access, dirty))来确定应当进行何种修改:
如果状态是(0, 0),表示最近未被引用也未被修改,首先选择此页淘汰。将该物理页面从链表上除去,该物理页面记为换出页面,但是由于这个时候这个页面不是dirty的,因此事实上不需要将其写入swap分区。
如果状态是(0, 1),表示最近未被使用,但被修改,其次选择。将该物理页对应的虚拟页的PTE中的dirty位都改成0,并且将该物理页写入到外存中,然后指针跳转到下一个物理页;
如果状态是(1, 0), 最近使用而未修改,再次选择。将该物理页对应的虚拟页的PTE中的访问位都置成0,然后指针跳转到下一个物理页面;
如果状态是(1, 1),最近使用且修改,最后选择。将该物理页的所有对应虚拟页的PTE中的访问为置成0,然后指针跳转到下一个物理页面;

2.需要被换出的页的特征是什么
一个基本的原则是:并非所有的物理页都可以交换出去的,只有映射到用户空间且被用户程序直接访问的页面才能被交换,而被内核直接使用的内核空间的页面不能被换出。然后基于这个原则,并结合上述设计方案不难的出可以替换的页的特征为:PTE_A(Acess)和PTE_D(Dirty)位均为0。

3.在ucore中如何判断具有这样特征的页
获取线性地址所对应的页表项,之后使用位运算判断PTE_A和PTE_D。

4.何时进行换入和换出操作
换入:缺页时换入,即在产生page fault的时候进行换入操作。
换出:ucore目前大致有两种策略,即积极换出策略和消极换出策略。积极换出策略是指操作系统周期性地(或在系统不忙的时候)主动把某些认为“不常用”的页换出到硬盘上,从而确保系统中总有一定数量的空闲页存在,这样当需要空闲页时,基本上能够及时满足需求;消极换出策略是指,只是当试图得到空闲页时,发现当前没有空闲的物理页可供分配,这时才开始查找“不常用”页面,并把一个或多个这样的页换出到硬盘上。

实验心得

本实验在前面的基础上再次延伸,已经建立分页机制后,我们还有一些问题需要解决。比如出现Page Fault我们应该怎么处理,又比如建立分页机制后,如何使经常访问的页能尽量保存到内存中。解决第一个问题就涉及到我们练习一完成的do_pgfault函数,解决第二个问题涉及到练习二中完成的页面替换算法。课本上也对页面替换算法有所提及,但是相关内容是存在不同和互补的。像交换页的时机,书本上只是简单地说当操作系统发现只有少量的页可以用时就会开始清除页,比较笼统且强调换出,而本实验中考虑的是换入换出两种情况,并且十分具体。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值