ucore操作系统lab2实验报告

练习一、first-fit连续内存分配

文件:default_pmm.c

(一)思路:

首先我们需要用一个数据结构来描述每个物理页(也称页帧),这里用了双向链表结构来表示每个页。链表头用 free_area_t结构来表示,包含了一个 list_entry 结构的双向链表指针和记录当前空闲页的个数的无符号整型变量 nr_free

typedef struct {

list_entry_t free_list;  // the list header

unsigned int nr_free;  // # of free pages in this free list

} free_area_t;

接下来需要了解管理物理页的Page数据结构,这个数据结构也是实现连续物理内存分配算法的关键数据结构,可通过此数据结构来完成空闲块的链接和信息存储,而基于这个数据结构的管理物理页数组起始地址就是全局变量pages

struct Page {

    int ref;                        // 映射此物理页的虚拟页个数

    uint32_t flags;                 // 物理页属性(空或不空

unsigned int property;          // 连续空页有多少(只在地址最低页有值,其余为0)

list_entry_t page_link;  // 双向链接各个Page结构的page_link双向链表(用于释放)

};

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

(二)任务:

修改default_init_memmap()default_alloc_pages()default_free_pages()函数。  

(三)实现

1、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;
        SetPageProperty(p);
		//清零此页的引用计数 
        set_page_ref(p, 0);
        //将空闲页插入到链表
		list_add_before(&free_list, &(p->page_link)); 
    }
    base->property = n;
    //计算空闲页总数 
    nr_free += n;
}

2、default_alloc_pages()

此函数是用于为进程分配空闲页。 其分配的步骤如下: 

 寻找足够大的空闲块 ,如果找到了,重新设置标志位

②从空闲链表中删除此页 

③判断空闲块大小是否合适 ,如果不合适,分割页块 ,如果合适则不进行操作 

 计算剩余空闲页个数 

 返回分配的页块地址 

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

2、default_free_pages()

这个函数的作用是释放已经使用完的页,把他们合并到free_list中。 具体步骤如下:  

①在free_list中查找合适的位置以供插入 

②改变被释放页的标志位,以及头部的计数器 

③尝试在free_list中向高地址或低地址合并

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;
            list_del(&(p->page_link));
}
//向低地址合并
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;
}

练习二、查找虚拟地址对应页表项

(一)、思路:

pde_t全称为 page directory entry,也就是一级页表的表项(注意:pgdir实际不是表 项,而是一级页表本身。实际上应该新定义一个类型pgd_t来表示一级页表本身)。pte t全 称为 page table entry,表示二级页表的表项。uintptr t表示为线性地址,由于段式管理只做直接映射,所以它也是逻辑地址。

pgdir给出页表起始地址。通过查找这个页表,我们需要给出二级页表中对应项的地址。 虽然目前我们只有boot_pgdir一个页表,但是引入进程的概念之后每个进程都会有自己的页 表。

有可能根本就没有对应的二级页表的情况,所以二级页表不必要一开始就分配,而是等到需要的时候再添加对应的二级页表。如果在查找二级页表项时,发现对应的二级页表不存在,则需要根据create参数的值来处理是否创建新的二级页表。如果create参数为0,则get_pte返回NULL;如果create参数不为0,则get_pte需要申请一个新的物理页(通过alloc_page来实现,可在mm/pmm.h中找到它的定义),再在一级页表中添加页目录项指向表示二级页表的新物理页。注意,新申请的页必须全部设定为零,因为这个页所代表的虚拟地址都没有被映射。

当建立从一级页表到二级页表的映射时,需要注意设置控制位。这里应该设置同时设置 上PTE_UPTE_WPTE_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)];   // (1) find page directory entry
    //若获取不成功则执行下面的语句
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)];          
}

练习三、释放虚拟地址所在页,并取消对应二级页表映射

(一)、思路:

判断此页被引用的次数,如果仅仅被引用一次,则这个页也可以被释放。否则,只能释放页表入口。

(二)、实现:

static inline voidpage_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
/* 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
     */    	
<span style="white-space:pre">	</span>//判断页表是否存在
	if (*ptep & PTE_P){         
<span style="white-space:pre">		</span>struct Page* page = pte2page(*ptep);
	//判断此页是否被多次引用         
<span style="white-space:pre">	</span>if (page_ref_dec(page)==0){
		free_page(page);        
<span style="white-space:pre">	</span>}        
<span style="white-space:pre">	</span>*ptep = 0;
	//释放pte        
<span style="white-space:pre">	</span>tlb_invalidate(pgdir, la);    <span style="font-family: 宋体; font-size: 10.5pt; letter-spacing: 0pt; line-height: 19.5pt; text-indent: 0pt;">}}</span>

运行结果如下图:











展开阅读全文

没有更多推荐了,返回首页