湖南大学操作系统实验ucore lab3

阅读前注意事项:

1、我的博客从lab2之后,如果没有特殊说明,所有标注的代码行数位置,以labcodes_answer(答案包)里的文件为准!!!因为你以后会发现做实验用meld软件比较费时费力,对于咱们学校的验收不如直接对着答案来;

2、感谢网上的各路前辈大佬们,本人在这学期初次完成实验的过程中,各位前辈们的博客给了我很多有用的指导;本人的博客内容在现有的内容上,做了不少细节的增补内容,有些地方属个人理解,如果有错在所难免,还请各位大佬们批评指正;

3、所有实验的思考题,我把它规整到了文章最后;

4、所有实验均默认不做challenge,对实验评分无影响。

一、实验内容

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

 

二、目的

了解虚拟内存的Page Fault异常处理实现;

了解页替换算法在操作系统中的实现。

 

三、实验设计思想和流程

 

练习0:填写已有实验

 

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

经过比较,需要更改的文件为:

 

kdebug.c

trap.c

default_pmm.c

pmm.c

 

其余地方无需修改,可以直接使用。

 

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

 

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

 

什么是虚拟内存?简单地说是指程序员或CPU“看到”的内存。但有几点需要注意:

 

1. 虚拟内存单元不一定有实际的物理内存单元对应,即实际的物理内存单元可能不存在;

2. 如果虚拟内存单元对应有实际的物理内存单元,那二者的地址一般是不相等的;

3. 通过操作系统实现的某种内存映射可建立虚拟内存与物理内存的对应关系,使得程序员或CPU访问的虚拟内存地址会自动转换为一个物理内存地址。

 

那么这个“虚拟”的作用或意义在哪里体现呢?在操作系统中,虚拟内存其实包含多个虚拟层次,在不同的层次体现了不同的作用。首先,在有了分页机制后,程序员或CPU“看到”的地址已经不是实际的物理地址了,这已经有一层虚拟化,我们可简称为内存地址虚拟化。有了内存地址虚拟化,我们就可以通过设置页表项来限定软件运行时的访问空间,确保软件运行不越界,完成内存访问保护的功能。

 

通过内存地址虚拟化,可以使得软件在没有访问某虚拟内存地址时不分配具体的物理内存,而只有在实际访问某虚拟内存地址时,操作系统再动态地分配物理内存,建立虚拟内存到物理内存的页映射关系,这种技术称为按需分页(demand paging)。把不经常访问的数据所占的内存空间临时写到硬盘上,这样可以腾出更多的空闲内存空间给经常访问的数据;当CPU访问到不经常访问的数据时,再把这些数据从硬盘读入到内存中,这种技术称为页换入换出(page swap in/out)。这种内存管理技术给了程序员更大的内存“空间”,从而可以让更多的程序在内存中并发运行。

 

do_pgfault的功能是给未被映射的(虚拟)地址映射上物理页,那么它和页面异常是一种什么样的关系?

 

首先需要了解虚拟地址存在的意义:

 

之所以要创建一个虚拟地址空间,目的是为了解决进程地址空间隔离的问题。但程序要想执行,必须运行在真实的内存上,所以,必须在虚拟地址与物理地址间建立一种映射关系。这样,通过映射机制,当程序访问虚拟地址空间上的某个地址值时,就相当于访问了物理地址空间中的另一个值。虚拟地址存在的意义,相当于是提供一个比实际物理内存空间“更大”的虚拟内存空间给系统使用。

 

通过内存地址虚拟化,可以使得软件在没有访问某虚拟内存地址时不分配具体的物理内存,而只有在实际访问某虚拟内存地址时,操作系统再动态地分配物理内存,建立虚拟内存到物理内存的页映射关系,这种技术称为按需分页(demand paging)。

 

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

 

(1)目标(虚拟)页面不存在(页表项全为0,即该线性地址与物理地址尚未建立映射或者已经撤销),此时应该直接报错;

 

(2)相应的物理页面不在内存中(页表项非空,但Present标志位=0,比如在swap分区或磁盘文件上),此时应该建立映射虚拟页和物理页的映射关系;

 

(3)目标访问页的权限不符合(此时页表项P标志=1,比如企图写只读页面),此时应该直接报错。

 

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

一块虚拟地址(vma)可能映射到一个或多个多个物理页。

 

由于一开始,分配虚拟空间超过了物理空间的大小,比如在上图中只有5个物理页帧,但其实我们给它分了7个虚拟页,那很明显一定会有两个虚拟页没有对应的物理页帧。

 

那么如果访问到这两个虚拟页,在二级页表里面它会没有对应映射关系,一旦没有对应映射关系,那就会产生缺页异常,因此就是do_pgfault函数的实现功能,它要建立这个映射关系。

 

一旦产生缺页异常,会出现一个异常的中断,此时经过前两个实验中的中断处理过程,一直调用了do_pgfault的具体实现。

下图反映了该过程的调用关系:

那么,下面开始首先是关键的数据结构的解释:

 

1、虚拟内存块空间(vma,virtual memory area,kern/mm/vmm.h,12——19行)

// the virtual continuous memory area(vma)
struct vma_struct {
    struct mm_struct *vm_mm; 		// the set of vma using the same PDT 
    uintptr_t vm_start;      		//    start addr of vma    
    uintptr_t vm_end;        		// end addr of vma
    uint32_t vm_flags;       		// flags of vma
    list_entry_t list_link;  	       // linear list link which sorted by start addr of vma
};

VMA数据结构,用于管理应用程序的虚拟内存,更准确来说,VMA是描述应用程序对虚拟内存“需求”的结构,它包含五个成员:

 

mm:是一个更高级更抽象的数据结构,代表整个应用程序所用到的所有VMA(该VMA的占有程序),接下来马上讨论。

 

vm_start、vm_end:描述一个合理的地址空间范围(确保 vm_start < vm_end的关系);

 

vm_flags:该虚拟内存空间的属性,目前的属性包括可读、可写、可执行;

它们被定义在(kern/mm/vmm.h,24——26行):

#define VM_READ                	0x00000001
#define VM_WRITE              	0x00000002
#define VM_EXEC               	0x00000004
//可以看出来三个权限是在uint32的后三位上。

list_link:一个双向链表,按照从小到大的顺序把一系列用vma_struct表示的虚拟内存空间链接起来,并且还要求这些链起来的vma_struct应该是不相交的,即vma之间的地址空间无交集。

 

2、应用程序映射到的虚拟空间块综述结构mm(kern/mm/vmm.h,28——35行)

// the control struct for a set of vma using the same PDT
struct mm_struct {
    list_entry_t mmap_list;             // linear list link which sorted by start addr of vma
    struct vma_struct *mmap_cache; 	// current accessed vma, used for speed purpose
    pde_t *pgdir;                  	// the PDT of these vma
    int map_count;                 	// the count of these vma
    void *sm_priv;                   	// the private data for swap manager
};

如果说vma描述和管理的是一系列虚拟内存块结构,那么mm则用来描述,究竟是哪个应用程序使用了这些vma,它们是通过vma中的成员mm联系在一起的。

 

mm也有五个成员,分别解释如下:

 

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:指向用来链接记录页访问情况的链表头。用于FIFO替换策略的访问。

 

那么,接下来讨论页访问异常的调用关系过程:

1、汇编代码中Vector.S中,当出现了一个页访问错误的时候,会得到一个错误码14,经过一系列处理之后保存为中断状态指针tf,交给trap处理;

 

2、trap函数:(kern/trap/trap.c,231+行):

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

直接调用trap_dispatch函数进行下一步处理。

 

3、trap_dispatch函数:(kern/trap/trap.c,只关心 172——183行)

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;
……

这是实验一中的中断处理函数,操作系统中,中断产生的原因很多。

 

传入参数是一个中断指针tf,接下来会对于这个中断指针进行switch进行判断,确认一下在各种中断情况中,它具体是由于什么情况中断的,这里我到的中断原因是T_PGFLT,即页访问错误,那么我们会调用一个函数pgfault_handler函数去进一步处理页访问错误的情况。

 

4、pgfault_handler(kern/trap/trap.c,158——166行)

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函数来返回一个页错误处理结果。

 

5、do_pgfault(需实现函数,kern/vmm.c,304+行)

 

函数实现如下:

int
do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) {
    int ret = -E_INVAL;
struct vma_struct *vma = find_vma(mm, addr); 	
//查询vma,判断addr是否在vma中

    pgfault_num++;
    if (vma == NULL || vma->vm_start > addr) { 	//vma->vm_start<=addr end
        cprintf("not valid addr %x, and  can not find it in vma\n", addr);
        goto failed;
}

    switch (error_code & 3) {
    default:
    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:
        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;         //perm是给物理页赋予权限的中间变量
    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; 
            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;
}

该函数主要完成的是具体的页访问异常处理工作:

 

首先是三个传入参数,它们分别是:应用程序虚拟存储总管mm、错误码error_code和具体出错的虚拟地址addr。

 

接下来,我们讨论对于具体可能出现页访问错误的情况进行一一处理和报错。

 

(1)如果是虚拟地址的问题,如虚拟地址的范围超过了限制,或者是虚拟地址无法被查找到,即可以说该地址是不合法的,进行了一次非法访问,那么可以直接报错。

 

(2)如果是目标访问页的权限不符合,比如对一个只读页进行写操作,或者读了一个不可读的页,那么此时可以直接报错。

 

这里涉及到了一个对于传入的错误码error_code的理解,在该函数的注释中,我们了解到,错误码的低3位分别是:

 

P标志(位0)最低位:表示当前的错误是由于不存在页面(0)引起,还是由于违反访问权限(1)引起。

 

W / R标志(位1):表示当前错误是由于读操作(0)引起还是还是写操作(1)引起。

 

因此,和权限相关的判断,只需要对P和W/R,即error_code的最低两位判断即可。

如果能够顺利通过上述的合法性判断,那么此次虚拟内存访问就被认为是合法的,此时,页访问异常的原因,是由于该合法虚拟页,没有对应物理页的映射导致,因此下一步要建立起这个映射。

 

建立起这个映射首先要改变一下待换入页面的权限值:

 

PTE_W          0x002    // page table/directory entry flags bit : Writeable

PTE_U          0x004     // page table/directory entry flags bit : User can access

 

首先,无论如何这个页面需要能够被用户访问,其次,如果对应映射的vma有写的权限,该物理页也需要可写。

 

然后,我们通过当前应用程序mm所指向的一级页表,以及虚拟地址,去查询有没有对应的二级页表,如果查询结果为NULL,那么报错,因为没有对应的二级页表项,它根本不存在,也不知道用什么物理页去映射(当然,这里不可能不存在,如果查找到不存在的情况,由于get_pte的create标记位为1,那么会创建一个新的二级页表);

 

之后,如果是上述新创建的二级页表,那么*ptep就会是0,代表页表为空,此时调用pgdir_alloc_page,对它进行初始化;接下来的else语句就是对它进行一个映射的替换:

 

swap_init_ok是一个标记位,代表交换初始化成功,可以开始替换的过程了,

 

首先声明了一个页,之后将结构mm、虚拟地址和这个空页,调用了swap_in函数

(kern/mm/swap.c,119——136行)

int
swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result)//addr是出错地址
//page是新找到的页
{
     struct Page *result = alloc_page();
     assert(result!=NULL);

     pte_t *ptep = get_pte(mm->pgdir, addr, 0);
     // cprintf("SWAP: load ptep %x swap entry %d to vaddr 0x%08x, page %x, No %d\n", ptep, (*ptep)>>8, addr, result, (result-pages));
    
     int r;
     if ((r = swapfs_read((*ptep), result)) != 0)
     {
        assert(r!=0);
     }
     cprintf("swap_in: load disk swap entry %d with swap_page in vadr 0x%x\n", (*ptep)>>8, addr);
     *ptr_result=result;
     return 0;
}

该函数首先为传入的空页page分配初始化,之后获取了mm一级页表对应的二级页表,通过swapfs_read尝试将硬盘中的内容换入到新的page中,即可返回,最后的return 0是一个正常返回值。

 

执行完上述函数,最后,建立起该页的虚拟地址和物理地址之间的对应关系,然后设置为可交换,该页的虚拟地址设置为传入的地址。至此,do_pgfault结束,建立起了新的映射关系,下次访问不会有异常。

 

最后,将练习1总结如下:

 

(1)do_pgfault函数的输入是一个出错的应用程序、错误码、出错的虚拟地址;

(2)通过虚拟地址和错误码权限的判断,可以确认是否可以直接报错;

(3)如果数据都是合法的,那么需要建立映射关系;

(4)如果虚拟地址所映射的物理页已经存在,那么新加入映射关系即可,如果不存在需要另外创建。

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

 

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

 

操作系统为何要进行页面置换呢?这是由于操作系统给用户态的应用程序提供了一个虚拟的“大容量”内存空间,而实际的物理内存空间又没有那么大。所以操作系统就就“瞒着”应用程序,只把应用程序中“常用”的数据和代码放在物理内存中,而不常用的数据和代码放在了硬盘这样的存储介质上。

 

因此,在练习1中,当页错误异常发生时,有可能是因为页面保存在swap区或者磁盘文件上造成的,所以我们需要利用页面替换算法解决这个问题。

 

而练习一和这里的关联就在于:页面替换主要分为两个方面,页面换出和页面换入。练习一实现的是页面换入,主要在上述的do_pgfault()函数实现;而练习二这里主要实现,页面换出,主要是在swap_out_vistim()函数。另外,在练习一还有一个函数叫做swappable,代表将该页面设置为可交换的。于是,练习二主要是对于这两个函数的实现。

 

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

 

先进先出(First In First Out, FIFO)页替换算法:该算法总是淘汰最先进入内存的页,即选择在内存中驻留时间最久的页予以淘汰。只需把一个应用程序在执行过程中已调入内存的页按先后次序链接成一个队列,队列头指向内存中驻留时间最久的页,队列尾指向最近被调入内存的页。这样需要淘汰页时,从队列头很容易查找到需要淘汰的页。

 

下面是具体实现部分,页调度算法也是基于框架的,该框架定义在

(kern/mm/swap.h,33——51行)

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);     
};

都是成员函数,通过之前一样的方法来进行绑定,这样就能执行一套完整的算法。

 

在实验二中,物理页的数据结构在这里也有所改动:(kern/mm/memlayout.h,100——107行)

struct Page {
    int ref;                  	// page frame's reference counter
    uint32_t flags;          	// array of flags that describe the status of the page frame
    unsigned int property;    	// the num of free block, used in first fit pm manager
    list_entry_t page_link;    	// free list link
    list_entry_t pra_page_link;  	// used for pra (page replace algorithm)
uintptr_t pra_vaddr;      	// used for pra (page replace algorithm)
//未命中的虚拟地址
};

主要是加入了一个双向链表pra_page_link,用于记录加入内存顺序这方面的内容。pra_page_link可用来构造按页的第一次访问时间进行排序的一个链表,这个链表的开始表示第一次访问时间最近的页,链表结尾表示第一次访问时间最远的页。当然链表头可以就可设置为pra_list_head(定义在swap_fifo.c中),构造的时机是在page fault发生后,进行do_pgfault函数时。pra_vaddr可以用来记录此物理页对应的虚拟地址。

 

首先来看FIFO的初始化(kern/swap_fifo.c,33——40行):

_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;
}

首先,没有页在内存中,因此将列表清空,表头指向队列最开始。

 

那么,调用了swappable函数之后,执行的是一个页加入队列的操作,由page fault触发

(kern/swap_fifo.c,44——56行)

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: YOUR CODE*/ 
    //(1)link the most recent arrival page at the back of the pra_list_head qeueue.
    list_add(head, entry);
    return 0;
}

我们看到,符合算法的定义,每次都在表头加入新的page。

 

接下来,就是换出的策略,FIFO换出最早进入内存的页面,也就是在队尾,该函数的位置是(kern/swap_fifo.c,61——79行)

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
     /* 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;
}

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

 

四、思考题

 

思考题1:描述页目录项(Pag Director 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 用户可自定义的部分

 

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

 

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

 

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

 

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

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

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

 

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

 

解:目前的swap_manager框架足以支持在ucore中实现extended clock算法,之所以能够支持该算法,是因为在kern/mm/mmu.h文件中有如下定义

#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 用户可自定义的部分

其中PTE_A中的内容的即标志着该页是否被访问过,由此我们可以对kern/mm/swap_fifo.c做相应的修改,判断是否被访问过即可

 

需要被换出的页的特征是:最早被换入,且最近没有被访问过的页。

 

在ucore中如何判断具有这样特征的页:首先判断其最近有没有被访问过(利用位运算,让条件*ptep & PTE_A是否为1),若无,则按照FIFO原则进行置换。

 

何时进行换入和换出操作:类似于FIFO算法,当需要调用的页不在页表中时,并且在页表已满的情况下,需要进行换入和换出操作。

 

五、运行结果

1、qemu中出现页表信息:

2、如果一切正常,得分应当是45/45:

猜你喜欢

转载自blog.csdn.net/yyd19981117/article/details/86692601
今日推荐