UCORE实验二 物理内存管理(动态分区分配、分页、页表)

实验二

该实验主要是动态分区分配、分页以及页表的相关内容。

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

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

请在实验报告中简要说明你的设计实现过程。请回答如下问题:

  • 你的first fit算法是否有进一步的改进空间
1-1 思路:

该问题是实现基于分页的动态分区分配算法,大致过程为:内存管理器顺着双向链表进行空闲区域的搜索,如果找到大小合适的区域,则将该空闲区域进行分配。但与单纯的动态分区分配不同,由于是基于分页的,所以当搜索的分区大小大于所需的内存时,就可以将分区分成两部分,一部分提供给申请的部分,剩下的则形成新的空闲区。

1-2 数据结构、宏以及用到的函数
//定义页的数据结构 
struct Page {
 int ref;
 //ref表示的是,这个页被页表的引用记数,也就是映射此物理页的虚拟页个数。
 //如果这个页被页表引用了,即在某页表中有一个页表项设置了一个虚拟页到这个Page管理的物理页的映射关系,
 //就会把Page的ref加一;反之,若页表项取消,即映射关系解除,就会把Page的ref减一。
 uint32_t flags;
 //flags表示此物理页的状态标记,有两个标志位状态,为1的时候,代表这一页是free状态,
 //可以被分配,但不能对它进行释放;如果为0,那么说明这个页已经分配了,不能被分配,但是可以被释放掉。
 unsigned int property;
 //property用来记录某连续空闲页的数量,这里需要注意的是用到此成员
//变量的这个Page一定是连续内存块的开始地址(第一页的地址)。
 list_entry_t page_link;
 //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_area_t free_area;
#define free_list (free_area.free_list) //维护所有空闲的内存块,是一个双向链表,在最开始时它的prev和next都指向自身。
#define nr_free (free_area.nr_free) //空闲页的数目
//所调用函数
assert(expression) //这里的含义是对表达式进行判断,如果为假则报错
PageReserved() //判断是否为保留页
set_page_ref() //更改映射到该页的虚拟页数
SetPageProperty() //设置连续页数
1-3 修改函数

default_init_memmap函数:该函数完成对空闲块列表的构建和初始化的功能。

修改方法:不难发现,应当在for循环中每初始化一次就将指针p自加,但没有连接到双向链表上,故需要增加连接的操作(提示中写到使用list_add_before),最后设置base(头部)的property与nr_free,设置的原因详见数据结构。

static void
default_init_memmap(struct Page *base, size_t n) {
	//该函数用于初始化n个空闲块
	//base为基础页,n为生成物理页的个数 
    assert(n > 0);
	//若为假则报错,退出程序 
    struct Page *p = base;
    for (; p != base + n; p ++) { //初始化n块物理页 
        assert(PageReserved(p)); //检查是否为保留页,如果是,初始化
        p->flags = p->property = 0; //flag = 0表示可以分配,property = 0 表示连续空页为0 
        set_page_ref(p, 0); //映射到该页的虚拟页为0 
        list_add_before(&free_list, &(p->page_link));//插入空闲页的链表里面 添加代码
    }
    base->property = n; //第一页连续页有n 
    //SetPageProperty(base);被修改
    nr_free += 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); //换成对应page指针的页指针,记为p
        if (p->property >= n) { //说明为第一块,且空间足够
            page = p;
            //break; 删除
            //添加代码
            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;
        }
    }
    /* 删除
    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;
}

default_free_pages函数:用于释放使用完的页,同时将使用完的空间再次融合进原链表。

修改方法:原代码的缺陷主要在三点,一是插入链表是并没有找到 base 相对应的位置,二是没有把页插入到空闲页表中,三是合并不合理。

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++){
        p->flags = 0;
        set_page_ref(p, 0);
        ClearPageReserved(p);
        ClearPageProperty(p);
        list_add_before(le, &(p->page_link));
    }
    base->property = n;
    SetPageProperty(base);
    //高位地址合并
    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(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 ;
}

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

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

2-0 补充知识

下图为二级页表结构,在进行地址转换的过程中,先通过线性地址的前10位找到一级页表的entry,内含二级页表的基址,加上中间十位作为偏移量,即可得到二级页表的表项,内含物理页的基址,再加上offset即可得到具体的虚拟地址。

在这里插入图片描述

获取directory,table部分:(kern/mm/mmu.h,204——207行):

#define PDX(la) ((((uintptr_t)(la)) >> PDXSHIFT) & 0x3FF) //获得一级页表的一个表项,该表项内含二级页表的地址,但要注意,如果加入计算需要考虑偏移的问题,因为单纯十位不可能找到合适的地址。
#define PTX(la) ((((uintptr_t)(la)) >> PTXSHIFT) & 0x3FF) //获得二级页表的偏移值,和PDX(la)的值加和可以得到二级页表的一个表项,该表项内含物理地址的位置。

其中,***PDXSHIFT的值为22,*****右移22位,再与10个1与,就可以获取directory;

***PTXSHIFT的值为11,*****右移10位,再与11个1与,由于地址对齐的原因,0x3FF的11位之前都是0,这样就能提取table部分。

set_page_ref(page,1): //设置此页被引用一次
page2pa(page): //得到page管理的那一页的物理地址
KADDR(pa): //返回pa对应的虚拟地址(线性地址),注释里面如此说:takes a physical address and returns the //corresponding kernel virtual address.
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

其中,PDE_ADDR被定义在(kern/mm/mmu.h,219——220行):

#define PTE_ADDR(pte)   ((uintptr_t)(pte) & ~0xFFF)
#define PDE_ADDR(pde)   PTE_ADDR(pde)
数据结构和宏
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 表示可以读取对应地址的物理内存页内容
2-2 思路

该问题思路较为简单,即根据二级页表的结构进行翻译即可。首先找到使用基址+offset(或者数组表示法)找到一级页表的具体表项,内含二级页表的基址。如果此时找到,则可以直接返回表达式,即二级页表的虚拟地址。

pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) { 
//  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 返回二级页表的内核虚拟地址
 
    pde_t *pdep = &pgdir[PDX(la)]; //一级页表的具体表项,内含二级页表的基址(如果有的话)
    if (!(*pdep & PTE_P)) { // 如果不存在
        struct Page *page;
        if (!create || (page = alloc_page()) == NULL) { //不决定分配或者分配失败
            return NULL;
        }
        set_page_ref(page, 1); //此页被引用
        uintptr_t pa = page2pa(page); //得到page的物理地址
        memset(KADDR(pa), 0, PGSIZE); //使用memset将新建的这个页表虚拟地址,全部设置为0,因为这个页所代表的虚拟地址都没有被映射。
        *pdep = pa | PTE_U | PTE_W | PTE_P;
        //设置二级页表为可写、可读、可使用
    }
return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];
    //PDE_ADDR(*pdep) 为线性地址 
    //KADDR(PDE_ADDR(*pdep)) 为虚拟地址
    //PTX(la) 为二级页表的偏移值,此处使用数组形式表示等同于 虚拟基地址+offset
    //最后转化为pte_t类型,返回pte的虚拟地址。
}

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

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

3-1 思路

与上一个实验相同,直接进行翻译即可:首先释放物理页,将页帧进行清空,同时要将二级页表的对应表项清除即可。对应文件与实验二相同,找到page_remove_pte函数。

3-2 函数
//取消映射关系,-1说明只被一个表项引用过
page_ref_dec(struct Page *page) {
    page->ref -= 1;
    return page->ref;
}
 
// invalidate a TLB entry, but only if the page tables being
// edited are the ones currently in use by the processor.
void
tlb_invalidate(pde_t *pgdir, uintptr_t la) {
    if (rcr3() == PADDR(pgdir)) {
        invlpg((void *)la);
    }
}
static inline void
page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
   	if (*ptep & PTE_P) {   //PTE_P代表页存在
        struct Page *page = pte2page(*ptep); //获得物理地址
        if (page_ref_dec(page) == 0) { //无引用,可以清除
            free_page(page);
        }
        *ptep = 0; //清除二级页表
        tlb_invalidate(pgdir, la)
    }
}

请在实验

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值