• OS课程 ucore_lab2实验报告


    练习零:填写已有实验

       本实验依赖实验1。请把你做的实验1的代码填入本实验中代码中有“LAB1”的注释相应部分。提示:可采用diff和patch工具进行半自动的合并(merge),也可用一些图形化的比较/merge工具来手动合并,比如meld,eclipse中的diff/merge工具,understand中的diff/merge工具等。

    其实 lab1 中只有 kern/debug/kdebug.c kern/init/init.c kern/trap/trap.c 被改过了
    所以只用把这三个文件复制到lab2中就可以了。
    

    练习一:实现 first-fit 连续物理内存分配算法(需要编程)

       在实现first fit 内存分配算法的回收函数时,要考虑地址连续的空闲块之间的合并操作。提示:在建立空闲页块链表时,需要按照空闲页块起始地址来排序,形成一个有序的链表。可能会修改default_pmm.c中的default_init,default_init_memmap,default_alloc_pages, default_free_pages等相关函数。请仔细查看和理解default_pmm.c中的注释。

      查看注释后,发现代码已经写了,于是make qemu 了一发,发现错了,断言失败。仔细看了后,代码是不完整的。

      断言失败之前的检查都是针对页分配成功、失败的测试,从失败的断言这里开始是对分配的页的位置进行的检查。

      实际上,最初的实现 default_*_pages 只是简单的插入到链表,根本没有关心被插入到了哪里,所以在多次 alloc/free 之后页块在链表中的位置乱得一塌糊涂。如果需要保序,需要对每个改动链表的地方注意一下位置。

    struct Page {
        // 页帧的 引用计数
        int ref;
        // 页帧的状态 Reserve 表示是否被内核保留 另一个是 表示是否 可分配
        uint32_t flags;
        // 记录连续空闲页块的数量 只在第一块进行设置
        unsigned int property;
        // 用于将所有的页帧串在一个双向链表中 这个地方很有趣 直接将 Page 这个结构体加入链表中会有点浪费空间 因此在 Page 中设置一个链表的结点 将其结点加入到链表中 还原的方法是将 链表中的 page_link 的地址 减去它所在的结构体中的偏移 就得到了 Page 的起始地址
        list_entry_t page_link;
    };
    
    // 初始化空闲页块链表
    static void default_init(void) {
        list_init(&free_list);
        nr_free = 0; // 空闲页块一开始是0个
    }
    // 初始化n个空闲页块
    static void default_init_memmap(struct Page *base, size_t n) {
        assert(n > 0);
        struct Page *p = base;
        for (; p != base + n; p ++) {
            assert(PageReserved(p)); // 看看这个页是不是被内核保留的
            p->flags = p->property = 0;
            set_page_ref(p, 0);
        }
        base->property = n; // 头一个空闲页块 要设置数量
        SetPageProperty(base);
        nr_free += n;
        // 初始化玩每个空闲页后 将其要插入到链表每次都插入到节点前面 因为是按地址排序
        list_add_before(&free_list, &(base->page_link));
    }
    // 分配n个页块
    static struct Page * default_alloc_pages(size_t n) {
        assert(n > 0);
        if (n > nr_free) {
            return NULL;
        }
        struct Page *page = NULL;
        list_entry_t *le = &free_list;
        // 查找 n 个或以上 空闲页块 若找到 则判断是否大过 n 则将其拆分 并将拆分后的剩下的空闲页块加回到链表中
        while ((le = list_next(le)) != &free_list) {
            // 此处 le2page 就是将 le 的地址 - page_link 在 Page 的偏移 从而找到 Page 的地址
            struct Page *p = le2page(le, page_link);
            if (p->property >= n) {
                page = p;
                break;
            }
        }
        if (page != NULL) {
            if (page->property > n) {
                struct Page *p = page + n;
                p->property = page->property - n;
                SetPageProperty(p);
                // 将多出来的插入到 被分配掉的页块 后面
                list_add(&(page->page_link), &(p->page_link));
            }
            // 最后在空闲页链表中删除掉原来的空闲页
            list_del(&(page->page_link));
            nr_free -= n;
            ClearPageProperty(page);
        }
        return page;
    }
    // 释放掉 n 个 页块
    static void default_free_pages(struct Page *base, size_t n) {
        assert(n > 0);
        struct Page *p = base;
        for (; p != base + n; p ++) {
            assert(!PageReserved(p) && !PageProperty(p));
            p->flags = 0;
            set_page_ref(p, 0);
        }
        base->property = n;
        SetPageProperty(base);
        list_entry_t *le = list_next(&free_list);
        // 合并到合适的页块中
        while (le != &free_list) {
            p = le2page(le, page_link);
            le = list_next(le);
            if (base + base->property == p) {
                base->property += p->property;
                ClearPageProperty(p);
                list_del(&(p->page_link));
            }
            else if (p + p->property == base) {
                p->property += base->property;
                ClearPageProperty(base);
                base = p;
                list_del(&(p->page_link));
            }
        }
        nr_free += n;
        le = list_next(&free_list);
        // 将合并好的合适的页块添加回空闲页块链表
        while (le != &free_list) {
            p = le2page(le, page_link);
            if (base + base->property <= p) {
                break;
            }
            le = list_next(le);
        }
        list_add_before(le, &(base->page_link));
    }
    

    成功后会提示:

    然后会在另一个地方断言挂掉,那是接下来的实验了。

    Question 1
    你的first fit算法是否有进一步的改进空间

    当然啦,这种东西(对于一个区间中某些数字的存在性维护)用线段树可以实现 alloc 和 free 达到 O(log n) 级别的时间复杂度,不过空间(复杂度虽然还是 O(n) 不变)要增加一倍。

    练习二:实现寻找虚拟地址对应的页表项(需要编程)

      通过设置页表和对应的页表项,可建立虚拟内存地址和物理内存地址的对应关系。其中的get_pte函数是设置页表项环节中的一个重要步骤。此函数找到一个虚地址对应的二级页表项的内核虚地址,如果此二级页表项不存在,则分配一个包含此项的二级页表。本练习需要补全get_pte函数 in kern/mm/pmm.c,实现其功能。请仔细查看和理解get_pte函数中的注释。get_pte函数的调用关系图如下所示:

      具体是在 check_pgdir 函数中一个断言 get_pte 返回值失败的,看到 get_pte 函数,就是第二个练习需要编写修改的地方。

    get_pte ,是给出 Page Directory 基址和物理地址,求对应的 Page Table Entry 地址。

    pte_t *get_pte(pde_t *pgdir, uintptr_t la, bool create) {
        pde_t *pdep = &pgdir[PDX(la)]; // 找到 PDE 这里的 pgdir 可以看做是 页目录表的基址
        if (!(*pdep & PTE_P)) {         // 看看 PDE 指向的页表 是否存在
            struct Page* page = alloc_page(); // 不存在就申请一页物理页
            /* 通过 default_alloc_pages() 分配的页 的地址 并不是真正的页分配的地址
                实际上只是 Page 这个结构体所在的地址而已 故而需要 通过使用 page2pa() 将 Page 这个结构体
                的地址 转换成真正的物理页地址的线性地址 然后需要注意的是 无论是 * 或是 memset 都是对虚拟地址进行操作的
                所以需要将 真正的物理页地址再转换成 内核虚拟地址
                */
            if (!create || page == NULL) {
                return NULL;
            }
            set_page_ref(page, 1);
            uintptr_t pa = page2pa(page);
            memset(KADDR(pa), 0, PGSIZE); // 将这一页清空 此时将 线性地址转换为内核虚拟地址
            *pdep = pa | PTE_U | PTE_W | PTE_P; // 设置 PDE 权限
        }
        return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];
    }
    

      首先注释中的 pdep ( Page Directory Entry Pointer ),就是 PD 基质加上 PDE 编号,编号就是 LA 的高十位( x86 的约定),可以通过 PDX 宏获取。

      然后检查是否设置了 Present 位,也就是 PDE_P 位。不过实际上并没有 PDE_P 这个宏,使用注释里告诉我们的等价的 PTE_P 来检查(注释里告诉我们这个位是 PDE 和 PTE 通用的,虽然这三个位的确是通用的,不过毕竟 PD 和 PT 的保留位还是有所不同的)。

      如果没有设置而且 bool create 设置为需要申请,就需要按注释 (3) ~ (7) 中提示一般的 alloc 一个页、设置引用计数、获得线性地址、使用 memset 清空页内容、设置 PDE 项中权限位。

    最后再从 la 中获取一下中间十位,也就是 PTE 编号,从 PD 获取一下基址,相加就是 PTE 的线性地址了,用 KADDR 处理一下即可。

    请回答如下问题:
      请描述页目录项(Pag Director Entry)和页表(Page Table Entry)中每个组成部分的含义和以及对ucore而言的潜在用处。

    PDE 详解
    从低到高,分别是:

    P (Present) 位:表示该页保存在物理内存中。
    R (Read/Write) 位:表示该页可读可写。
    U (User) 位:表示该页可以被任何权限用户访问。
    W (Write Through) 位:表示 CPU 可以直写回内存。
    D (Cache Disable) 位:表示不需要被 CPU 缓存。
    A (Access) 位:表示该页被写过。
    S (Size) 位:表示一个页 4MB 。
    9-11 位保留给 OS 使用。
    12-31 位指明 PTE 基质地址。

    PTE 详解

    从低到高,分别是:

    0-3 位同 PDE。
    C (Cache Disable) 位:同 PDE D 位。
    A (Access) 位:同 PDE 。
    D (Dirty) 位:表示该页被写过。
    G (Global) 位:表示在 CR3 寄存器更新时无需刷新 TLB 中关于该页的地址。
    9-11 位保留给 OS 使用。
    12-31 位指明物理页基址。

      如果ucore执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?

      进行换页操作 首先 CPU 将产生页访问异常的线性地址 放到 cr2 寄存器中 ;然后就是和普通的中断一样 保护现场 将寄存器的值压入栈中 ;然后压入 error_code 中断服务例程将外存的数据换到内存中来 ;最后 退出中断回到进入中断前的状态。

    练习3:释放某虚地址所在的页并取消对应二级页表项的映射

      当释放一个包含某虚地址的物理内存页时,需要让对应此物理内存页的管理数据结构Page做相关的清除处理,使得此物理内存页成为空闲;另外还需把表示虚地址与物理地址对应关系的二级页表项清除。请仔细查看和理解page_remove_pte函数中的注释。为此,需要补全在 kern/mm/pmm.c中的page_remove_pte函数。page_remove_pte函数的调用关系图如下所示:

      之前断言失败是因为在 page_insert 中调用了 page_remove_pte ,而 page_remove_pte 并没有修改引用计数所以导致了对于引用计数的断言失败。

    static inline void page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
        if ((*ptep & PTE_P)) {
            struct Page *page = pte2page(*ptep);
            if (page_ref_dec(page) == 0) { // 若引用计数减一后为0 则释放该物理页
                free_page(page);
            }
            *ptep = 0; // 清空 PTE
            tlb_invalidate(pgdir, la); // 刷新 tlb
        }
    }
    

    首先判断一下 ptep 是不是合法——检查一下 Present 位就是了;然后通过注释中所说的,通过 pte2page(*ptep) 获取相应页,减少引用计数并决定是否释放页;最后把 TLB 中该页的缓存刷掉就可以了。

      数据结构Page的全局变量(其实是一个数组)的每一项与页表中的页目录项和页表项有无对应关系?如果有,其对应关系是啥?

      可以参照 kern/mm/pmm.h 中的转换函数。其实就是 Page 全局数组中以 Page Directory Index Page Table Index 的组合 PPN (Physical Page Number) 为索引的那一项。

    剩下的不会;

    参考链接:
    [1].https://yuerer.com/操作系统-uCore-Lab-2/
    [2].https://xr1s.me/2018/05/27/ucore-lab2-report/

  • 相关阅读:
    SpringCloud面试题
    网工必知:(1)Cisco 路由器PPPOE拨号配置与NAT简单上网配置
    【网工的福利来了!】用Excel表做的“子网划分&路由聚合计算器”
    肝了,一文让你看懂《Docker极简入门指南》
    ImportError: libopenblas.so.0: cannot open shared object file
    Linux软件包管理工具 Snap 常用命令
    squashfs文件系统
    回环设备
    Mac homebrew报错Error: homebrew-core is a shallow clone.
    公钥和私钥
  • 原文地址:https://www.cnblogs.com/fans-fan/p/11853076.html
Copyright © 2020-2023  润新知