操作系统ucore lab2实验报告

练习0

填写已有实验
  本实验依赖实验1.请把要做的实验1的代码填入本实验中代码有lab1的注释相应部分

首先利用meld工具比较两个文件的差异
这里写图片描述

发现缺失的是kdebug.c、trap.c两个文件的相关代码,补全后进行下一练习
首先运行
这里写图片描述

报错,看来就是需要进行实验的所有编程才能完整的运行

练习1

实现firstfit连续物理内存分配算法(需要编程)
  在实现firstfit内存分配算法的回首函数时,要考虑地址连续的空闲块之间的合并操作

在实验前先仔细学习一下firstfit算法

原理

  要求空闲分区链以地址递增的次序链接。在分配内存时,从链首开始顺序查找,直至找到一个大小能满足要求的分区为止;然后再按照作业的大小,从该分取中划出一块内存空间分配给请求者,余下的空闲分区仍留在空闲链中。若从链首直到链尾都不能找到一个能满足要求的分区,则此次内存分配失败,返回。该算法倾向于优先利用内存中低地址部分的空闲分区,从而保留了高址部分的大空闲区。这给为以后到达的大作业分配大的内存空间创造了条件,其缺点是低址部分不断被划分,会留下许多难以利用的、很小的空闲分区,而每次查找又都是从低址部分开始,这无疑会增加查找可用空闲分区时的开销。

大致流程图

  为了与以后的分页机制配合,首先需要建立对整个计算机的每一个物理页的属性,用结构体Page来表示,它包含了映射此物理页的虚拟页个数,描述物理页属性的flags和双向链接各个Page结构的page_link双向链表。

struct Page{
    int ref;
    uint32_t flags;
    unsigned int property;
    list_entry_t page_link;
}
  • ref

      表示该页被页表的引用记数。如果这个页被页表引用了,即在某页表中有一个页表项设置一个虚拟页到这个Page管理的物理页的映射关系,就会把Page的ref加一。反之,若页表项取消,即映射关系解除,就减一。

  • flags

       表示此物理页的状态标记,有两种属性,bit 0表示是否被保留,如果被保留了则设为1,且不能放到空闲页链表中,即这样的页不是空闲页,不能动态分配与释放。比如内核代码占用的空间。bit 1表示此页是否是空闲的。如果设置为1,表示这页是空闲的,可以被分配;如果设置为0,表示这页已经被分配出去了,不能被再二次分配。

  • property

      用来记录某连续内存空闲块的大小(即地址连续的空闲页的个数)。这里需要注意的是用到此成员变量的这个Page比较特殊,是连续内存空闲地址最小的一夜(即第一页)。

  • page_link

      是便于把多个连续内存空闲块链接在一起的双向链表指针,连续内存空闲块利用这个页的成员变量page_link来链接比它地址小和大的其他连续内存空闲块

  为了有效的管理这些小连续内存空闲块,所有的连续内存空闲块可用一个双向链表来管理,便于分配和释放,为此定义一个free_area_t

typedef struct {
    list_entry_t free_list;         // the list header
    unsigned int nr_free;           // number 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

static void
default_init(void) {
    list_init(&free_list);
    nr_free = 0;
}

看下注释

(2) 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.

根据注释代码已经完成无需改动

default_init_memmap

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(&free_list, &(base->page_link));
}

查看一下注释

 * (3) 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 = p->property = 0;//设置标志位
        SetPageProperty(p);//设置bit
        set_page_ref(p, 0);//清空被引用的次数
        list_add(&free_list, &(p->page_link));//将此页插入到空闲页的链表里面
    }
    base->property = n;//连续内存空闲块的大小为n
    //SetPageProperty(base);
    nr_free += n;//说明有n个连续空闲块
    //list_add(&free_list, &(base->page_link));
}

default_alloc_pages

此函数是用于为进程分配空闲页。其分配的步骤如下:
- 寻找足够大的空闲块,如果找到了,重新设置标志位
- 从空闲链表中删除此页
- 判断空闲块大小是否合适 ,如果不合适,分割页块 ,如果合适则不进行操作
- 计算剩余空闲页个数
- 返回分配的页块地址

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;
    while ((le = list_next(le)) != &free_list) {
        struct Page *p = le2page(le, page_link);
        if (p->property >= n) {
            page = p;
            break;
        }
    }
    if (page != NULL) {
        list_del(&(page->page_link));
        if (page->property > n) {
            struct Page *p = page + n;
            p->property = page->property - n;
            list_add(&free_list, &(p->page_link));
    }
        nr_free -= n;
        ClearPageProperty(page);
    }
    return page;
}

查看一下注释

 * (4) default_alloc_pages: search find a first free block (block size >=n) in free list and reszie the free block, return the addr
 *              of malloced block.
 *              (4.1) So you should search freelist like this:
 *                       list_entry_t le = &free_list;
 *                       while((le=list_next(le)) != &free_list) {
 *                       ....
 *                 (4.1.1) In while loop, get the struct page and check the p->property (record the num of free block) >=n?
 *                       struct Page *p = le2page(le, page_link);
 *                       if(p->property >= n){ ...
 *                 (4.1.2) If we find this p, then it means we find a free block(block size >=n), and the first n pages can be malloced.
 *                     Some flag bits of this page should be setted: PG_reserved =1, PG_property =0
 *                     unlink the pages from free_list
 *                     (4.1.2.1) If (p->property >n), we should re-caluclate number of the the rest of this free block,
 *                           (such as: le2page(le,page_link))->property = p->property - n;)
 *                 (4.1.3)  re-caluclate nr_free (number of the the rest of all free block)
 *                 (4.1.4)  return p
 *               (4.2) If we can not find a free block (block size >=n), then return NULL

相关定义
这里写图片描述

这里写图片描述

这里写图片描述

根据注释修改代码

static struct Page *
default_alloc_pages(size_t n) {
    assert(n > 0);
    if (n > nr_free) {//当空闲页不够时,返回NULL
        return NULL;
    }
    list_entry_t *le = &free_list;
    while ((le = list_next(le)) != &free_list) {//遍历所有指针
        struct Page *p = le2page(le, page_link);//转换为页结构
        if (p->property >= n) {//如果找到空闲页大小大于等于n时选中
            int i;
            list_entry_t *len;
            for(i = 0; i < n; i++)//初始化分配内存
            {
                len = list_next(le);
                struct Page *p2 = le2page(temp_le, page_link);    //转换页结构
                SetPageReserved(p2);  //初始化标志位
                ClearPageProperty(p2);   
                list_del(le);  //清除双向链表指针
                le = len;  
            }
            if(p->property > n)
            {  
                //若大小>n,只取大小为n的块
                (le2page(le,page_link))->property = p->property - n;  
            }  
            ClearPageProperty(p);  //初始化标志位
            SetPageReserved(p);  
            nr_free -= n;  //空闲页大小-n
            return p;  
        }
    }
    return NULL;//分配失败
}

default_free_pages

这个函数的作用是释放已经使用完的页,把他们合并到free_list中。 具体步骤如下:
- 在free_list中查找合适的位置以供插入
- 改变被释放页的标志位,以及头部的计数器
- 尝试在free_list中向高地址或低地址合并

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;
    list_add(&free_list, &(base->page_link));
}

查看注释

 * (5) default_free_pages: relink the pages into  free list, maybe merge small free blocks into big free blocks.
 *               (5.1) according the base addr of withdrawed blocks, search free list, find the correct position
 *                     (from low to high addr), and insert the pages. (may use list_next, le2page, list_add_before)
 *               (5.2) reset the fields of pages, such as p->ref, p->flags (PageProperty)
 *               (5.3) try to merge low addr or high addr blocks. Notice: should change some pages\'s p->property correctly.

相关定义
这里写图片描述

这里写图片描述

这里写图片描述

根据注释修改代码

static void
default_free_pages(struct Page *base, size_t n) {
    assert(n > 0);
    assert(PageReserved(base));

    list_entry_t *le = &free_list;//找合适的位置
    struct Page *p = base;
    while((le=list_next(le)) != &free_list) 
    {
        p = le2page(le, page_link);
        if(p > base)
        {
            break;
        }
    }
    for(p = base; p < base + n; p++)//在之前插入n个空闲页
    {
        list_add_before(le, &(p->page_link));
        p->flags = 0;//设置标志
        set_page_ref(p, 0);
        ClearPageProperty(p);
        SetPageProperty(p);
    }

    base->property = n;//设置连续大小为n
    //如果是高位,则向高地址合并
    p = le2page(le,page_link);
    if(base + base->property == p )
    {
        base->property += p->property;
        p->property = 0;
    }
    //如果是低位且在范围内,则向低地址合并
    le = list_prev(&(base->page_link));
    p = le2page(le, page_link);
    if(le != &free_list && p == base-1)//满足条件,未分配则合并
    {
        while(le != &free_list)
        {
            if(p->property)//当连续时
            {
                p->property += base->property;
                base->property = 0;
                break;
            }
            le = list_prev(le);
            p = le2page(le,page_link);
        }
    }

    nr_free += n;
}

练习2

实现寻找虚拟地址对应的页表项(需要编程)
  通过设置页表和对应的页表项,可建立虚拟内存地址和物理内存地址的对应关系。其中的get_pte函数是设置页表项缓解中的一个重要步骤。此函数找到一个虚地址对应的二级页表项的内核虚地址,如果此二级页表项不存在,则分配一个包含此项的二级页表。

相关定义
这里写图片描述

  • PDX(la): 返回虚拟地址la的页目录索引
  • KADDR(pa): 返回物理地址pa相关的内核虚拟地址
  • set_page_ref(page,1): 设置此页被引用一次
  • page2pa(page): 得到page管理的那一页的物理地址
  • struct Page * alloc_page() : 分配一页出来
  • memset(void * s, char c, size_t n) : 设置s指向地址的前面n个字节为字节‘c’
  • PTE_P 0x001 表示物理内存页存在
  • PTE_W 0x002 表示物理内存页内容可写
  • PTE_U 0x004 表示可以读取对应地址的物理内存页内容

根据注释完成代码

//get_pte - get 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
// parameter:
//  pgdir:  the kernel virtual base address of PDT
//  la:     the linear address need to map
//  create: a logical value to decide if alloc a page for PT
// return vaule: the kernel virtual address of this pte
pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
    /* LAB2 EXERCISE 2: YOUR CODE
     *
     * If you need to visit a physical address, please use KADDR()
     * please read pmm.h for useful macros
     *
     * Maybe you want help comment, BELOW comments can help you finish the code
     *
     * Some Useful MACROs and DEFINEs, you can use them in below implementation.
     * MACROs or Functions:
     *   PDX(la) = the index of page directory entry of VIRTUAL ADDRESS la.
     *   KADDR(pa) : takes a physical address and returns the corresponding kernel virtual address.
     *   set_page_ref(page,1) : means the page be referenced by one time
     *   page2pa(page): get the physical address of memory which this (struct Page *) page  manages
     *   struct Page * alloc_page() : allocation a page
     *   memset(void *s, char c, size_t n) : sets the first n bytes of the memory area pointed by s
     *                                       to the specified value c.
     * DEFINEs:
     *   PTE_P           0x001                   // page table/directory entry flags bit : Present
     *   PTE_W           0x002                   // page table/directory entry flags bit : Writeable
     *   PTE_U           0x004                   // page table/directory entry flags bit : User can access
     */
    //typedef uintptr_t pde_t;
    pde_t *pdep = &pgdir[PDX(la)];  // (1)获取页表
    if (!(*pdep & PTE_P))             // (2)假设页目录项不存在
    {      
        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)设置被引用1次
        uintptr_t pa = page2pa(page);                  // (5)得到该页物理地址
        memset(KADDR(pa), 0, PGSIZE);                  // (6)物理地址转虚拟地址,并初始化
        *pdep = pa | PTE_U | PTE_W | PTE_P;            // (7)设置可读,可写,存在位
    }
    return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];     // (8) return page table entry
    //KADDR(PDE_ADDR(*pdep)):这部分是由页目录项地址得到关联的页表物理地址,再转成虚拟地址
    //PTX(la):返回虚拟地址la的页表项索引
    //最后返回的是虚拟地址la对应的页表项入口地址
}

练习3

释放某虚拟地址所在的页并取消对应的二级页表项的映射(需要编程)
  当释放一个包含某虚地址的物理内存页时,需要让对应此物理内存页的管理数据结构Page做相关的清除处理,使得次物理内存页成为空闲;另外还需把表示虚地址与物理地址对应关系的二级页表项清除

相关定义
这里写图片描述

这里写图片描述

这里写图片描述

这里写图片描述

  • struct Page *page pte2page(*ptep):得到页表项对应的那一页
  • free_page : 释放一页
  • page_ref_dec(page) : 减少该页的引用次数,返回剩下引用次数
  • tlb_invalidate(pde_t * pgdir, uintptr_t la) : 当修改的页表是进程正在使用的那些页表,使之无效
//page_remove_pte - free an Page sturct which is related linear address la
//                - and clean(invalidate) pte which is related linear address la
//note: PT is changed, so the TLB need to be invalidate 
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
    /* LAB2 EXERCISE 3: YOUR CODE
     *
     * Please check if ptep is valid, and tlb must be manually updated if mapping is updated
     *
     * Maybe you want help comment, BELOW comments can help you finish the code
     *
     * Some Useful MACROs and DEFINEs, you can use them in below implementation.
     * MACROs or Functions:
     *   struct Page *page pte2page(*ptep): get the according page from the value of a ptep
     *   free_page : free a page
     *   page_ref_dec(page) : decrease page->ref. NOTICE: ff page->ref == 0 , then this page should be free.
     *   tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being
     *                        edited are the ones currently in use by the processor.
     * DEFINEs:
     *   PTE_P           0x001                   // page table/directory entry flags bit : Present
     */
    if (*ptep & PTE_P)                 //(1) check if this page table entry is present
    {      //假如页表项存在
        struct Page *page = pte2page(*ptep);//(2)找到页表项的那一页信息
        if (page_ref_dec(page) == 0)//(3)如果没有被引用
        { 
            free_page(page);//(4)释放该页
        }
        *ptep = 0;         //(5)该页目录项清零
        tlb_invalidate(pgdir, la); //(6) flush tlb当修改的页表是进程正在使用的那些页表,使之无效
    }

}

运行结果

这里写图片描述

这里写图片描述

收获

  通过本次实验,我了解如何发现系统中的物理内存,了解如何建立对物理内存的初步管理,了解了页表的相关的操作,即如何建立页表来实现虚拟内存到物理内存之间的映射,对段页式内存管理机制有一个比较全面的了解。基本上在试验中学习,根据注释以及函数定义可以动手完成一个简单的物理内存管理系统。完成后发现运行错误,通过一遍一遍的核对代码,查阅资料,比对正确答案,终于得以修改,成功运行。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章