扫清外围

注意,在lab3中仅实现了简单的页面置换机制,还没有涉及lab4和lab5才实现的内核线程和用户进程,所以还无法通过内核线程机制实现一个完整意义上的虚拟内存页面置换功能。

须知:哪些页面可以被换出?

在操作系统的设计中,一个基本的原则是:并非所有的物理页都可以交换出去的,只有映射到用户空间且被用户程序直接访问的页面才能被交换,而被内核直接使用的内核空间的页面不能被换出。这里面的原因是什么呢?操作系统是执行的关键代码,需要保证运行的高效性和实时性,如果在操作系统执行过程中,发生了缺页现象,则操作系统不得不等很长时间(硬盘的访问速度比内存的访问速度慢 2~3 个数量级),这将导致整个系统运行低效。而且,不难想象,处理缺页过程所用到的内核代码或者数据如果被换出,整个内核都面临崩溃的危险。

但在实验三实现的 ucore 中,我们只是实现了换入换出机制,还没有设计用户态执行的程序,所以我们在实验三中仅仅通过执行 check_swap 函数在内核中分配一些页,模拟对这些页的访问,然后通过 do_pgfault 来调用 swap_map_swappable 函数来查询这些页的访问情况并间接调用相关函数,换出“不常用”的页到磁盘上。

当我们引入了虚拟内存,就意味着虚拟内存的空间可以远远大于物理内存,意味着程序可以访问"不对应物理内存页帧的虚拟内存地址",这时CPU应当抛出Page Fault这个异常。

回想一下,我们处理异常的时候,是在kern/trap/trap.cexception_handler()函数里进行的。按照scause寄存器对异常的分类里,有CAUSE_LOAD_PAGE_FAULTCAUSE_STORE_PAGE_FAULT两个case。之前我们并没有真正对异常进行处理,只是简单输出一下就返回了。现在我们要真正进行Page Fault的处理。

// kern/trap/trap.c
static inline void print_pgfault(struct trapframe *tf) {
    cprintf("page falut at 0x%08x: %c/%c\n", tf->badvaddr,
            trap_in_kernel(tf) ? 'K' : 'U',
            tf->cause == CAUSE_STORE_PAGE_FAULT ? 'W' : 'R');
}

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->cause, tf->badvaddr);
    }
    panic("unhandled page fault.\n");
}

void exception_handler(struct trapframe *tf) {
    int ret;
    switch (tf->cause) {
        /* .... other cases */
        case CAUSE_FETCH_PAGE_FAULT:// 取指令时发生的Page Fault先不处理
            cprintf("Instruction page fault\n");
            break;
        case CAUSE_LOAD_PAGE_FAULT:
            cprintf("Load page fault\n");
            if ((ret = pgfault_handler(tf)) != 0) {
                print_trapframe(tf);
                panic("handle pgfault failed. %e\n", ret);
            }
            break;
        case CAUSE_STORE_PAGE_FAULT:
            cprintf("Store/AMO page fault\n");
            if ((ret = pgfault_handler(tf)) != 0) { //do_pgfault()页面置换成功时返回0
                print_trapframe(tf);
                panic("handle pgfault failed. %e\n", ret);
            }
            break;
        default:
            print_trapframe(tf);
            break;
    }
}

这里的异常处理程序,把Page Fault分发给kern/mm/vmm.cdo_pgfault()函数,尝试进行页面置换。

之前我们物理页帧管理有个功能没有实现,那就是动态的内存分配。管理虚拟内存的数据结构(页表)需要有空间进行存储,而我们又没有给它预先分配内存(也无法预先分配,因为事先不确定我们的页表需要分配多少内存),就需要有malloc/free的接口来分配释放内存。我们在这里顺便看看pmm.h里对物理页面和虚拟地址,物理地址进行转换的一些函数。

接下来我们处理多级页表。之前的初始页表占据一个页的物理内存,只有一个页表项是有用的,映射了一个大大页(Giga Page)。

最后更新于

这有帮助吗?