lab2 练习

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

1.first-fit算法原理(最先匹配)

在这里插入图片描述
从前往后找,找到第一个比我要分配空间大的分区

2.实现过程

实现过程中需要用到的数据结构:
第一个是每一个物理页的属性结构
代码如下(示例):

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来链接比它地址小和大的其他连续内存空闲块。这里需要注意的是用到此成员变量的这个Page比较特殊,是这个连续内存空闲块地址最小的一页(即头一页, Head Page)。连续内存空闲块利用这个页的成员变量page_link来链接比它地址小和大的其他连续内存空闲块。

第二个是双向链表:
代码如下(示例):

struct list_entry {
    struct list_entry *prev, *next;
};
typedef struct list_entry list_entry_t;

c中定义的双向链表

第三个是负责管理所有的连续内存空闲块的双向链表——记录空闲页
代码如下(示例):

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_init函数
代码如下(示例):

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

简单的free_area_t初始化

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 = 0;//初始化
        SetPageProperty(p);//使能该页
        p->property = 0;//如果此页是空闲页,并且不是自由块的第一页,则p->property应设置为0。
        set_page_ref(p, 0);//p->ref应该是0,因为现在p是空闲的,没有引用。
        list_add_before(&free_list, &(p->page_link));//使用p->page_link将这个页面链接到free_list
    }
    nr_free += n;//该空闲块所有页的大小
    //first block
    base->property = n;//如果此页是空闲页,并且是空闲块的第一页,则p->property应设置为total num of block。
}

用来初始化空闲页链表,初始化每一个空闲页,然后计算空闲页的总数。
函数说明:
函数的两个参数:*base表示该空闲块的开始地址,n表示页数量
如果此页是空闲页,并且不是空闲块的第一页,则p->property应设置为0。
如果此页是空闲页,并且是空闲块的第一页,则p->property应设置为total num of block。
p->ref应该是0,因为现在p是空闲的,没有引用。

default_alloc_pages函数

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的连续空闲页块,返回空
}

详细说明已在代码中注释,该函数主要实现的流程:从空闲页块的链表中去遍历,找到第一块大小大于n的块,然后分配出来,把它从空闲页链表中除去,然后如果有多余的,把分完剩下的部分再次加入会空闲页链表中。

default_free_pages函数

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;
    while((le=list_next(le)) != &free_list) {
      p = le2page(le, page_link);
      if(p>base){
        break;
      }
    }
    //list_add_before(le, base->page_link);
    for(p=base;p<base+n;p++){
      list_add_before(le, &(p->page_link));
    }
    base->flags = 0;
    set_page_ref(base, 0);
    ClearPageProperty(base);
    SetPageProperty(base);
    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;
    return ;
}

函数功能:将页面重新链接到空闲列表中,或者将小的空闲块合并到大的空闲块中。
实现流程:
(1)根据要归还分区的基址查找空闲链表,找到正确位置,(地址从低到高查),归还页面
(2)重新设置页面属性p->ref, p->flags等
(3)尝试合并相邻的空闲低地址或空闲高地址;

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

get_pte函数找到一个虚地址对应的二级页表项的内核虚地址,如果此二级页表项不存在,则分配一个包含此项的二级页表。

//pde_t为一级页表的表项,pte_t为二级页表的表项
    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
        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) -------------|

uintptr_t 表示为线性地址,由于段式管理只做直接映射,所以它也是逻辑地址。
PTE_U: 位3,表示用户态的软件可以读取对应地址的物理内存页内容
PTE_W: 位2,表示物理内存页内容可写
PTE_P: 位1,表示物理内存页存在

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

page_remove_pte函数先判断该页被引用的次数,如果只被引用了一次,那么直接释放掉这页, 否则就删掉二级页表的该表项,即该页的入口。

    if (*ptep & PTE_P) {//判断页表中该表项是否存在
        struct Page *page = pte2page(*ptep);
        if (page_ref_dec(page) == 0) {//判断是否只被引用了一次
            free_page(page);//如果只被引用了一次,那么可以释放掉此页
        }
        *ptep = 0;//如果被多次引用,则不能释放此页,只用释放二级页表的表项
        tlb_invalidate(pgdir, la);//更新页表
    }

lab2运行结果如下所示
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值