操作系统实验报告 lab2

练习0:填写已有实验

本实验依赖实验1。请把要做的实验1的代码填入本实验中代码有lab1的注释部分。

直接利用ubuntu的开源工具meld进行比较
下面是比较的图片

这里写图片描述

这里写图片描述

由meld比较可知,两个文件需要补全,直接复制即可。

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

首先了解一下什么是first-fir内存分配算法,该算法是最先适应算法。

该算法从空闲分区链首开始查找,直至找到一个能满足其大小要求的空闲分区为止。然后再按照作业的大小,从该分区中划出一块内存分配给请求者,余下的空闲分区仍留在空闲分区链中。
特点: 该算法倾向于使用内存中低地址部分的空闲区,在高地址部分的空闲区很少被利用,从而保留了高地址部分的大空闲区。显然为以后到达的大作业分配大的内存空间创造了条件。
缺点:低地址部分不断被划分,留下许多难以利用、很小的空闲区,而每次查找又都从低地址部分开始,会增加查找的开销。

物理页结构属性如下

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管理的物理页的映射关系,就会把Page的ref加一。反之,若是解除,那就减一。
  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;

物理内存页管理器顺着双向链表进行搜索空闲内存区域,直到找到一个足够大的空闲区域,这是一种速度很快的算法,因为它尽可能少地搜索链表。如果空闲区域的大小和申请分配的大小正好一样,则把这个空闲区域分配出去,成功返回;否则将该空闲区分为两部分,一部分区域与申请分配的大小相等,把它分配出去,剩下的一部分区域形成新的空闲区。其释放内存的设计思路很简单,只需把这块区域重新放回双向链表中即可。


以上是内存管理用到的两大数据结构。接下来利用这些结构完成实验。

实验要求


/* In the first fit algorithm, the allocator keeps a list of free blocks (known as the free list) and,
   on receiving a request for memory, scans along the list for the first block that is large enough to
   satisfy the request. If the chosen block is significantly larger than that requested, then it is 
   usually split, and the remainder added to the list as another free block.
   Please see Page 196~198, Section 8.2 of Yan Wei Min's chinese book "Data Structure -- C programming language"
*/
// LAB2 EXERCISE 1: YOUR CODE
// you should rewrite functions: default_init,default_init_memmap,default_alloc_pages, default_free_pages.
/*
 * Details of FFMA
 * (1) Prepare: In order to implement the First-Fit Mem Alloc (FFMA), we should manage the free mem block use some list.
 *              The struct free_area_t is used for the management of free mem blocks. At first you should
 *              be familiar to the struct list in list.h. struct list is a simple doubly linked list implementation.
 *              You should know howto USE: list_init, list_add(list_add_after), list_add_before, list_del, list_next, list_prev
 *              Another tricky method is to transform a general list struct to a special struct (such as struct page):
 *              you can find some MACRO: le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc.)
 * (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.
 * (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
 * (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
 * (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.
 */

根据实验指导书,我们第一个实验需要完成的主要是default_pmm.c中的default_init,default_init_memmap,default_alloc_pages, default_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

 * (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

基本的要求已经知道了,下面修改代码
源代码

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;//说明连续有n个空闲块,属于空闲链表
    SetPageProperty(base);
    nr_free += n;//说明连续有n个空闲块,属于空闲链表
    list_add(&free_list, &(base->page_link));//
}

修改为

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);
        set_page_ref(p, 0);//清空引用
        list_add_before(&free_list, &(base->page_link));//插入空闲链表
    }
    base->property = n;//说明连续有n个空闲块,属于物理页管理链表
//    SetPageProperty(base);
    nr_free += n;//说明连续有n个空闲块,属于空闲链表

}

default_alloc_pages

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

    static struct Page *  
    default_alloc_pages(size_t n) {  
        assert(n > 0);  
        if (n > nr_free) {  
            return NULL;  
        }  
        list_entry_t  *len;  
    list_entry_t  *le = &free_list;  
    //在空闲链表中寻找合适大小的页块  
        while ((le = list_next(le)) != &free_list) {  
            struct Page *p = le2page(le, page_link);  
    //找到了合适大小的页块  
            if (p->property >= n) {  
    int i;  
    for(i=0;i<n;i++){  
    len = list_next(le);  
    //让pp指向分配的那一页  
    //le2page宏可以根据链表元素获得对应的Page指针p  
    struct Page *pp = le2page(temp_le, page_link);  
    //设置每一页的标志位  
    SetPageReserved(pp);  
    ClearPageProperty(pp);  
    //清除free_list中的链接  
    list_del(le);  
    le = len;  
    }  
    if(p->property>n){  
    //分割的页需要重新设置空闲大小  
    (le2page(le,page_link))->property = p->property - n;  
    }  
    //第一页重置标志位  
    ClearPageProperty(p);  
    SetPageReserved(p);  
    nr_free -= n;  
    return p;  
    }  
    }  
    //否则分配失败  
        return NULL;  
    }  

default_free_pages

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


    static void  
    default_free_pages(struct Page *base, size_t n) {  
    assert(n > 0);  
    assert(PageReserved(base));  
    struct Page *p = base;  
    //查找该插入的位置le  
    list_entry_t *le = &free_list;  
    while((le=list_next(le)) != &free_list){  
    p = le2page(le, page_link);  
    if(p>base) break;  
    }  
    //向le之前插入n个页(空闲),并设置标志位  
        for (p = base;p<base+n;p++) {  
            list_add_before(le, &(p->page_link));  
            p->flags = 0;  
            set_page_ref(p, 0);  
    ClearPageProperty(p);  
    SetPageProperty(p);  
    }  
    //将页块信息记录在头部  
        base->property = n;  
    //是否需要合并  
    //向高地址合并  
    p = le2page(le, page_link);        
        if (base + n == 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函数,函数找到一个虚地址对应的二级页表项的内核虚地址,如果此二级页表项不存在,则分配一个包含此项的二级页表。有可能根本就没有对应的二级页表的情况,所以二级页表不必要一开始就分配,而是等到需要的时候再添加对应的二级页表。如果在查找二级页表项时,发现对应的二级页表不存在,则需要根据create参数的值来处理是否创建新的二级页表。如果create参数为0,则get_pte返回NULL;如果create参数不为0,则get_pte需要申请一个新的物理页(通过alloc_page来实现,可在mm/pmm.h中找到它的定义),再在一级页表中添加页目录项指向表示二级页表的新物理页。
当建立从一级页表到二级页表的映射时,需要注意设置控制位。这里应该设置同时设置 上PTE_U、PTE_W和PTE_P(定义可在mm/mmu.h)。如果原来就有二级页表,或者新建立了页表,则只需返回对应项的地址即可。

    pte_t *  
    get_pte(pde_t *pgdir, uintptr_t la, bool create) {  
        /*  
         * 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 pte_t;  
        pde_t *pdep = &pgdir[PDX(la)];//若获取不成功则执行下面的语句  
    if (!(*pdep & PTE_P)) {//申请一页  
    struct Page *page;  
    if(!creat || (page = all_page())==NULL){  
    return NULL;  
    } //引用次数需要加1  
    set_page_ref(page, 1);//获取页的线性地址                     
    uintptr_t pa = page2pa(page);   
    memset(KADDR(pa), 0, PGSIZE);//设置权限  
    *pdep  = pa | PTE_U | PTE_W | PTE_P;                   
    }//返回页表地址  
    return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(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);          //更新页表
    }
}

运行结果如下
这里写图片描述

发布了99 篇原创文章 · 获赞 51 · 访问量 71万+

猜你喜欢

转载自blog.csdn.net/qq_31481187/article/details/68957255