操作系统实验—ucore Lab2

ucore Lab2

  • 一、内容

本次实验包含三个部分。首先了解如何发现系统中的物理内存;然后了解如何建立对物理内 存的初步管理,即了解连续物理内存管理;最后了解页表相关的操作,即如何建立页表来实 现虚拟内存到物理内存之间的映射,对段页式内存管理机制有一个比较全面的了解。

  • 二、目的

1、理解基于段页式内存地址的转换机制
2、理解页表的建立和使用方法
3、理解物理内存的管理方法

  • 三、实验思想以及实验过程和结果分析

    练习0:填写已有的实验
    实验二依赖于实验已所写的内容,因此需要将实验一中的内容在实验二相对应的地方进行填写。
    手动复制粘贴太慢而且容易出错,因此利用Meld Diff工具进行查看比对以及内容迁移
    在这里插入图片描述
    选择需要比对的文件
    在这里插入图片描述
    不同之处会进行显示,按下箭头便可以将不同之处进行复制粘贴。重复以上过程,将所有文件都需要的内容准备好之后进行实验。

准备知识
First-fit(首次适应算法):首次适应算法从空闲分区表的第一个表目起查找该表,把最先能够满足要求的空闲区分配给作业,这种方法目的在于减少查找时间。为适应这种算法,空闲分区表(空闲区链)中的空闲分区要按地址由低到高进行排序。

在机器加载完操作系统时,操作系统开始接管整个机器的运转,包括硬件资源,因此此时操作系统要对内存进行有组织的管理,以便后面内存访问有序的进行。
因此在这时首先要弄清楚可调用内存的空间大小以及管理的组织形式。实验中,操作系统是以页为单位管理物理内存的,用一个结构Page来描述一页内存,因为一页需要一个Page与之对应,因此我们在设计Page时应该使其尽可能地小,以便减少内存开销

Page:

/* *
 * struct Page - Page descriptor structures. Each Page describes one
 * physical page. In kern/mm/pmm.h, you can find lots of useful functions
 * that convert Page to other data types, such as phyical address.
 * */
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
};

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

flags: 表示此物理页的状态标记,其中bit 0表示此页是否被保留,保留的页将不能进行动态的分配和释放。bit 1表示此页是否是free的,如果为1,则是free的,如果为0则表示被分配出去了,不能二次分配

property: 用来记录某连续内存空闲块的大小(即地址 连续的空闲页的个数),只有连续空闲空间的第一页才会用到这个成员变量,其页的这个值都为0

**page_link:**是便于把多个连续内存空闲块链接在一起的双向链表指针,只链接连续空闲空间的最小的一页(即头一页)

free_area_t:

/* free_area_t - maintains a doubly linked list to record free (unused) pages */
typedef struct {
    list_entry_t free_list;         // the list header
    unsigned int nr_free;           // # of free pages in this free list
} free_area_t;

**list_entry:**双向链表指针,指向空闲物理也,对空闲页进行管理
**nr_free:**记录当前空闲页的个数

有了这两个数据结构,ucore就可以管理起来整个以页为单位的物理内存空间。接下来要解决的问题是空闲空间的大小以及起始位置。
我们首先根据bootloader给出的内存布局信息找出最大的物理内存地址 maxpa(定义在page_init函数中的局部变量),由于x86的起始物理内存地址为0,所以可以得知需要管理的物理页个数为

npage = maxpa / PGSIZE

这样,我们就可以预估出管理页级物理内存空间所需的Page结构的内存空间所需的内存大小为: sizeof(struct Page) * npage 由于bootloader加载ucore的结束地址(用全局指针变量end记录)以上的空间没有被使用,所 以我们可以把end按页大小为边界取整后,作为管理页级物理内存空间所需的Page结构的内存空间,记为:

pages = (struct Page *)ROUNDUP((void *)end, PGSIZE);

以页为单位管理物理内存 151 pages = (struct Page *)ROUNDUP((void *)end, PGSIZE); 为了简化起见,从地址0到地址pages+ sizeof(struct Page) * npage)结束的物理内存空间设定 为已占用物理内存空间(起始0~640KB的空间是空闲的),地址pages+ sizeof(struct Page) * npage)以上的空间为空闲物理内存空间,这时的空闲空间起始地址为

uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) *
npage);

练习1:实现 first-fit 连续物理内存分配算法
在实现first fit 内存分配算法的回收函数时,要考虑地址连续的空闲块之间的合并操作,我们需要完成default_init,default_init_memmap,default_alloc_pages, default_free_pages。现在从一个个函数来解释:

./ default_pmm.c/default_init():

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

首先将前面进行了宏定义:

free_area_t free_area;

#define free_list (free_area.free_list)
#define nr_free (free_area.nr_free)

即将free_area.free_list(全局的空闲链表入口)定义为free_list,将free_area.nr_free(全局空闲页数量)定义为nr_free。
在default_init中,先将free_list进行init初始化,然后将空闲页数量置为0(因为还没开始计算空闲页的数量,所以是0)。

./ default_pmm.c/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));
}

其中pmm_manager是一个物理内存管理器,作为整个物理内存的管理者
定义如下:
在这里插入图片描述
首先传入一个页的基址,然后对于base+n的页面,判断p是否被保留,如果是保留页面则会直接报错。然后继续,将flags与property置为0
将base的property(数量)置为n,此时这个base作为这n个页的开始地址。将free页的数量+n。将base链接到空闲链表中去。

./ default_pmm.c/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);   // convert list entry to page
        if (p->property >= n) {   //find one page whose 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));
	    list_add_after(&(page->page_link), &(p->page_link));
    }
	list_del(&(page->page_link));
        nr_free -= n;
        ClearPageProperty(page);
    }
    return page;
}

default_alloc_pages()是为了分配page而设计的函数,参数为n,表示需要分配页的大小。
实现:

1、判断n是否大于0,大于0的合法,并且要保证free页要大于n
2、第一个while循环,从空闲链表开始查找,找到第一块函数符合条件的空闲空间返回
3、这时候我们需要将多余的空闲空间重新分割出来(避免空间浪费),即property>n时,将大于n的部分重新加入到空闲列表中。具体做法时先将base变为page+n的地方(此时对应的Page为p),然后将此时的property-n,将此时的p加入空闲链表中,再将page从空闲链表中删除。

最后返回即可

./ default_pmm.c/default_free_pages ():

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));
    le=list_next(&free_list);

    while(le!=&free_list)       //make sure we are right
    {
      p=le2page(le,page_link);
      if(base+base->property<=p)
	{
	  assert(base+base->property!=p);
	  break;
	    }
	le=list_next(le);

     }

   list_add_before(le, &(base->page_link));
}

static size_t
default_nr_free_pages(void) {
    return nr_free;
}

参数:
**base:**将要释放的空间基址
**n:**页的数量
实现过程:

1、第一个for循环,将这n页标志位flags置为0.然后将base基址的property置为n,表示这里已经存在着有n页的空闲空间
2、将新的到的空闲空间加入到空闲列表中,这里要注意的是,如果这段空间的临近空间也存在空闲空间,那就要将他们合并。下面的代码完成的就是这件事

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

3、最后将空闲空间按照地址递增的方式加入链表中,即最后一个while循环。 这样就可以将空间进行释放并正确插入空闲链表中了

练习2:实现寻找虚拟地址对应的页表项
现在我们试着去填充pmm.c中的get_pte函数。
在这里插入图片描述
参数:
**pgdir:**一级页表的地址
**la:**线性地址
**create:**是否分配一页物理页
功能:对于传入的一个线性地址,返回该线性地址所对应的二级页表条目的入口地址

实现:
/pmm.c/get_pte()

pte_t *
get_pte(pde_t *pgdir, uintptr_t la, bool create) {
 pde_t *pdep = &pgdir[PDX(la)];   //指向一级页表一个条目
    if (!(*pdep & PTE_P)) {
        struct Page *page;
        if (!create || (page = alloc_page()) == NULL) {    //分配一页物理内存作为PT
            return NULL;
        }
        set_page_ref(page, 1);
        uintptr_t pa = page2pa(page);    //获得PT页物理地址
        memset(KADDR(pa), 0, PGSIZE);  //清空页内容
        *pdep = pa | PTE_U | PTE_W | PTE_P; //将一级页表中的内容填写为PT物理地址
    }
    return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];  //指向二级页表一个条目
 }

1、首先,通过pgdir以及PDX(la)获得一级页表条目的入口(PDX是la的前10位,作为一级页表的条目)pdep
2、之后判断这个地址是否指向一块二级页表,如果没有就通过alloc_page函数分配一块物理内存作为二级页表。
3、获取这块物理内存的地址,清空它的内容并将它的地址填入对应的以及页表条目中
4、最后通过PTX(la)获取线性地址的中间10位(二级页表条目),然后返回。函数功能实现

引用的函数解释:

PDX(la): la对应虚拟地址的页面目录条目索引
PTE_P: 该虚拟页是否被表示
set_page_ref(page,1): 页面被引用一次
alloc_page(): 分配一个页面
KRDDR(pa): 获取一个物理地址并返回相应的内核虚拟地址
*memset(void s, char c, size_t n): 设置由s指向的长度为n个字节的内存

 线性地址结构:

在这里插入图片描述

一级页表与二级页表结构:
在这里插入图片描述

练习3:释放某虚地址所在的页并取消对应二级页表项的映射
./pmm.c/ page_remove_pte()
在这里插入图片描述
功能:对于传入的一个二级页表条目pte,取消其与物理地址的映射关系
参数:
**pgdir:**一级页表入口地址
**la:**线性地址
**ptep:**二级页表条目
实现:

/pmm.c/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) {   //减少page->ref 为0时释放空间
            free_page(page);   //释放空间
        }
        *ptep = 0;  //对应条目内容为0
        tlb_invalidate(pgdir, la);  //使TLB项无效,但仅当正在编辑的页表是处理器当前使用的页表时才无效
    }
}

1、先获取二级页表条目中存储的物理地址(*pte)
2、使用page_ref_dec使得ref-1,当ref为0时,此页应该被释放(free_page)
3、再将二级页表条目中的存储的内容清空(取消映射关系)
4、使TLB无效,因为此时PT已经改变

使用make grade查看正确性
在这里插入图片描述
结果正确

  • 2
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值