操作系统ucore lab2实验报告

Posted Bendawang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了操作系统ucore lab2实验报告相关的知识,希望对你有一定的参考价值。

操作系统lab2实验报告

实验二主要是完成Ucore操作系统的物理内存管理。
主要包括了解如何建立对物理内存的初步管理,即了解连续物理内存管理;最后了解页表相关的操作,即如何建立页表来实现虚拟内存到物理内存之间的映射,对段页式内存管理机制有一个比较全面的了解。

练习0:填写已有实验

lab2会依赖lab1,需要把做的lab1的代码填到lab2中缺失的位置上面。这道题就是一个工具的利用。这里我使用的是linux下的一个叫做meld的工具。如下图:

技术分享

直接将两个文件夹拖入,然后点击compare就行了。

技术分享

然后他就会分析两份代码的不同,然后就一个个比较比较复制过去就行了,在软件里面是可以支持打开对比复制了。当然bin目录下都是make生成的就不用复制了,其他需要修改的地方主要有以下两个文件:

kdebug.c
trap.c

然后直接通过这个一一对比粘贴就行了。

练习1:实现first-fit连续物理内存分配算法

在此之前我们要先熟悉两个数据结构:
第一个就是如下所示的每一个物理页的属性结构

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

该结构四个成员变量意义如下:

  • 1、ref表示这样页被页表的引用记数,应该就是映射此物理页的虚拟页个数。一旦某页表中有一个页表项设置了虚拟页到这个Page管理的物理页的映射关系,就会把Pageref加一。反之,若是解除,那就减一。
  • 2、 flags表示此物理页的状态标记,有两个标志位,第一个表示是否被保留,如果被保留了则设为1(比如内核代码占用的空间)。第二个表示此页是否是free的。如果设置为1,表示这页是free的,可以被分配;如果设置为0,表示这页已经被分配出去了,不能被再二次分配。
  • 3、property用来记录某连续内存空闲块的大小,这里需要注意的是用到此成员变量的这个Page一定是连续内存块的开始地址(第一页的地址)。
  • 4、page_link是便于把多个连续内存空闲块链接在一起的双向链表指针,连续内存空闲块利用这个页的成员变量page_link来链接比它地址小和大的其他连续内存空闲块

然后是下面这个结构。一个双向链表,负责管理所有的连续内存空闲块,便于分配和释放。

typedef struct {
    list_entry_t free_list;         // the list header
    unsigned int nr_free;           // # of free pages in this free list
} free_area_t;
  • free_list是一个list_entry结构的双向链表指针
  • nr_free则记录当前空闲页的个数

有了这样两个结构加上课上的讲解,我们可以开始做实验了。
首先根据实验指导书,我们第一个实验需要完成的主要是default_pmm.c中的default_initdefault_init_memmapdefault_alloc_pagesdefault_free_pages几个函数的修改。


先来看看default_init函数,该函数它已经实现好了,不用做修改:

/*default_init: you can reuse the  demo default_init fun to init the free_list and set nr_free to 0.
free_list is used to record the free mem blocks. nr_free is the total number for free mem blocks.*/
static void default_init(void) { 
    list_init(&free_list);
    nr_free = 0;
}

然后是default_init_memmap,这个函数是用来初始化空闲页链表的,初始化每一个空闲页,然后计算空闲页的总数。
注释如下:

default_init_memmap:  CALL GRAPH: kern_init --> pmm_init-->page_init-->init_memmap--> pmm_manager->init_memmap
 *              This fun is used to init a free block (with parameter: addr_base, page_number).
 *              First you should init each page (in memlayout.h) in this free block, include:
 *                  p->flags should be set bit PG_property (means this page is valid. In pmm_init fun (in pmm.c),
 *                  the bit PG_reserved is setted in p->flags)
 *                  if this page  is free and is not the first page of free block, p->property should be set to 0.
 *                  if this page  is free and is the first page of free block, p->property should be set to total num of block.
 *                  p->ref should be 0, because now p is free and no reference.
 *                  We can use p->page_link to link this page to free_list, (such as: list_add_before(&free_list, &(p->page_link)); )
 *              Finally, we should sum the number of free mem block: nr_free+=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 = 0;
        SetPageProperty(p);
        p->property = 0;
        set_page_ref(p, 0);//清空引用
        list_add_before(&free_list, &(p->page_link));//插入空闲页的链表里面
    }
    nr_free += n;  //说明连续有n个空闲块,属于空闲链表
    base->property=n; //连续内存空闲块的大小为n,属于物理页管理链表
}

然后是default_init_memmap,主要就是从空闲页块的链表中去遍历,找到第一块大小大于n的块,然后分配出来,把它从空闲页链表中除去,然后如果有多余的,把分完剩下的部分再次加入会空闲页链表中即可。

详细的代码以及注释如下:

static struct Page *default_alloc_pages(size_t n) {
    assert(n > 0);
    if (n > nr_free) {  //如果所有的空闲页的加起来的大小都不够,那直接返回NULL
        return NULL;
    }
    list_entry_t *le, *len;
    le = &free_list;    //从空闲块链表的头指针开始
    while((le=list_next(le)) != &free_list) {//依次往下寻找直到回到头指针处,即已经遍历一次
      struct Page *p = le2page(le, page_link);//将地址转换成页的结构
      if(p->property >= n){ //由于是first-fit,则遇到的第一个大于N的块就选中即可
        int i;
        for(i=0;i<n;i++){//递归把选中的空闲块链表中的每一个页结构初始化
          len = list_next(le);
          struct Page *pp = le2page(le, page_link);
          SetPageReserved(pp);
          ClearPageProperty(pp);
          list_del(le);//从空闲页链表中删除这个双向链表指针
          le = len;
        }
        if(p->property>n){
          (le2page(le,page_link))->property = p->property - n;//如果选中的第一个连续的块大于n,只取其中的大小为n的块
        }
        ClearPageProperty(p);
        SetPageReserved(p);
        nr_free -= n;//当前空闲页的数目减n
        return p;
      }
    }
    return NULL;//没有大于等于n的连续空闲页块,返回空
}

练习2:实现寻找虚拟地址对应的页表项

这里我们需要实现的是get_pte函数,函数找到一个虚地址对应的二级页表项的内核虚地址,如果此二级页表项不存在,则分配一个包含此项的二级页表。

由于我们已经具有了一个物理内存页管理器default_pmm_manager,我们就可以用它来获得所需的空闲物理页。
在二级页表结构中,页目录表占4KB空间,ucore就可通过default_pmm_managerdefault_alloc_pages函数获得一个空闲物理页,这个页的起始物理地址就是页目录表的起始地址。同理,ucore也通过这种方式获得各个页表所需的空间。页表的空间大小取决与页表要管理的物理页数n,一个页表项(32位,即4字节)可管理一个物理页,页表需要占 n/1024个物理页空间(向上取整)。这样页目录表和页表所占的总大小约为 4096+4?n 字节。
根据LAZY,这里我们并没有一开始就存在所有的二级页表,而是等到需要的时候再添加对应的二级页表。当建立从一级页表到二级页表的映射时,需要注意设置控制位。这里应该设置同时设置 上PTE_UPTE_WPTE_P(定义可在mm/mmu.h)。如果原来就有二级页表,或者新建立了页表,则只需返回对应项的地址即可。
如果 create参数为 0,则get_pte返回NULL;如果 create参数不为 0,则 get_pte 需要申请一个新的物理页
然后根据注释一步步操作以及参照别人的代码,把注释翻译以及代码展示如下:

pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
    pde_t *pdep = &pgdir[PDX(la)];                              // (1) find page directory entry
    if (!(*pdep & PTE_P)) {                                     // (2) check if entry is not present
        struct Page *page;
        if (!create || (page = alloc_page()) == NULL) { // (3) check if creating is needed, then alloc page for page table
           return NULL;
        } 
        set_page_ref(page, 1);                                  // (4) set page reference
        uintptr_t pa = page2pa(page);                           // (5) get linear address of page
        //注释中给了提示,If you need to visit a physical address, please use KADDR()
        memset(KADDR(pa), 0, PGSIZE);                           // (6) clear page content using memset
        *pdep = pa | PTE_U | PTE_W | PTE_P;                     // (7) set page directory entry‘s permission
       }
       return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];      // (8) return page table entry
}
 +--------10------+-------10-------+---------12----------+
 | Page Directory |   Page Table   | Offset within Page  |
 |      Index     |     Index      |                     |
 +----------------+----------------+---------------------+
 |--- PDX(la) ----|---- PTX(la) ---|---- PGOFF(la) ------|
 |----------- PPN(la) -------------|
  • pde_t 全称为page directory entry,也就是一级页表的表项(注意:pgdir实际不是表项,而是一级页表本身,pgdir给出页表起始地址。)
  • pte_t 全称为page table entry,表示二级页表的表项。
  • uintptr_t 表示为线性地址,由于段式管理只做直接映射,所以它也是逻辑地址。
  • PTE_U: 位3,表示用户态的软件可以读取对应地址的物理内存页内容
  • PTE_W: 位2,表示物理内存页内容可写
  • PTE_P: 位1,表示物理内存页存在

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

这里主要是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) {      //判断是否只被引用了一次
        free_page(page);                //如果只被引用了一次,那么可以释放掉此页
    }
    *ptep = 0;                          //如果被多次引用,则不能释放此页,只用释放二级页表的表项
    tlb_invalidate(pgdir, la);          //更新页表
    }
}

实验结果

最后实验运行截图如下:
技术分享

技术分享

运行成功。











以上是关于操作系统ucore lab2实验报告的主要内容,如果未能解决你的问题,请参考以下文章

操作系统ucore lab1实验报告

ucore实验

操作系统ucore lab4实验报告

ucore lab8

操作系统ucore lab5实验报告

ucore实验二