ucore lab2

练习0:填写已有实验

本实验依赖实验1。请把你做的实验1的代码填入本实验中代码中有“LAB1”的注释相应部分。提示:可采用diff和patch工具进行半自动的合并(merge),也可用一些图形化的比较/merge工具来手动合并,比如meld,eclipse中的diff/merge工具,understand中的diff/merge工具等。

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

算法原理

要求空闲分区链以地址递增的次序链接。在分配内存时,从链首开始顺序查找,直至找到一个大小能满足要求的分区为止;然后再按照作业的大小,从该分取中划出一块内存空间分配给请求者,余下的空闲分区仍留在空闲链中。若从链首直到链尾都不能找到一个能满足要求的分区,则此次内存分配失败,返回。该算法倾向于优先利用内存中低地址部分的空闲分区,从而保留了高址部分的大空闲区。这给为以后到达的大作业分配大的内存空间创造了条件,其缺点是低址部分不断被划分,会留下许多难以利用的、很小的空闲分区,而每次查找又都是从低址部分开始,这无疑会增加查找可用空闲分区时的开销。

改写 default_init_memmap()

其中用到的结构体定义
struct list_entry {
    struct list_entry *prev, *next; //父节点,子节点
};

typedef struct list_entry list_entry_t; //重命名
typedef struct {
    list_entry_t free_list; //链表头部        
    unsigned int nr_free;   //空闲块的个数    
} free_area_t; //链表头部结构

struct Page {
    int ref;       //映射此物理页的虚拟页的个数       
    uint32_t flags; //物理页属性    
    unsigned int property;  //连续空页有多少(只在地址最低页有值)
    list_entry_t page_link; // 双向链接各个Page结构的page_link
};
其中用到的函数和宏定义
free_area_t free_area;
#define free_list (free_area.free_list)
#define nr_free (free_area.nr_free)
#define PG_reserved                     0 
#define PG_property                     1
#define SetPageProperty(page)       \
                      set_bit(PG_property, &((page)->flags)) //设置为保留页
#define PageReserved(page)          \
                     test_bit(PG_reserved, &((page)->flags)) //检查是否为保留页
static inline void
list_add_before(list_entry_t *listelm, list_entry_t *elm) { //使用头插法将空闲页插入链表中
    __list_add(elm, listelm->prev, listelm);
}

static inline void
__list_add(list_entry_t *elm, list_entry_t *prev, list_entry_t *next) {
    prev->next = next->prev = elm;
    elm->next = next;
    elm->prev = prev;
}

static inline void
set_page_ref(struct Page *page, int val) {
    page->ref = val;
}
实现default_init_memmap()
static void
default_init_memmap(struct Page *base, size_t n) {
    assert(n > 0);   //判断n是否大于0
    struct Page *p = base;
    for (; p != base + n; p ++) { //初始化n块物理页
        assert(PageReserved(p)); //检查此页是否为保留页
        p->flags = p->property= 0; //标志位清0
        SetPageProperty(p);       //设置标志位为1
        set_page_ref(p, 0);  //清除引用此页的虚拟页的个数
       //加入空闲链表
        list_add_before(&free_list, &(p->page_link)); 
    }
    nr_free += n;  //计算空闲页总数
    base->property = n; //修改base的连续空页值为n
}
总结

​ 传入物理页基地址,和物理页的个数(个数必须大于0),然后对每一块物理页进行设置:先判断是否为保留页,如果不是,则进行下一步。将标志位清0,连续空页个数清0,然后将标志位设置为1,将引用此物理页的虚拟页的个数清0。然后再加入空闲链表。最后计算空闲页的个数,修改物理基地址页的property的个数为n。

改写 default_alloc_pages()

程序用到的函数定义
static inline list_entry_t *
list_next(list_entry_t *listelm) { //返回下一个节点
    return listelm->next;
}
static inline void
list_del(list_entry_t *listelm) { //删除当前节点
    __list_del(listelm->prev, listelm->next);
}
static inline void
__list_del(list_entry_t *prev, list_entry_t *next) {
    prev->next = next;
    next->prev = prev;
}
宏定义
#define offsetof(type, member)               \
    ((size_t)(&((type *)0)->member))
#define to_struct(ptr, type, member)         \
    ((type *)((char *)(ptr) - offsetof(type, member)))
#define le2page(le, member)                 \
    to_struct((le), struct Page, member)
#define ClearPageReserved(page)              \     
     clear_bit(PG_reserved, &((page)->flags))
实现default_alloc_pages()
static struct Page *
default_alloc_pages(size_t n) {
    assert(n > 0); //判断n是否大于0
    if (n > nr_free) { //需要分配页的个数小于空闲页的总数,直接返回
        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){ //找到合适的空闲页
        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); //将此页从free_list中清除
          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 inline list_entry_t *
list_prev(list_entry_t *listelm) {
    return listelm->prev;
}
实现 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); //获取链表对应的Page
      if(p>base){    
        break;
      }
    }
    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;      //设置连续大小为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 ;
} 
总结

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

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

段页式管理总体框架

这里写图片描述

在保护模式中,x86 体系结构将内存地址分成三种:逻辑地址(也称虚地址)、线性地址和物理地址。逻辑地址即是程序指令中使用的地址,物理地址是实际访问内存的地址。逻辑地址通过段式管理的地址映射可以得到线性地址,线性地址通过页式管理的地址映射得到物理地址。

相关定义

  • 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 表示可以读取对应地址的物理内存页内容

类型定义

这里涉及到三个类型pte_t、pde_t和uintptr_t。通过查看定义:

typedef unsigned int uint32_t;
typedef uint32_t uintptr_t;
typedef uintptr_t pte_t;
typedef uintptr_t pde_t;

可知它们其实都是unsigned int类型。在此做区分,是为了分清概念。

获取一级页表项地址

对于32位的线性地址,我们可以将它拆分成三部分

这里写图片描述

通过查看mmu.h文件,我们可以知道它的定义

// A linear address 'la' has a three-part structure as follows:
//
// +--------10------+-------10-------+---------12----------+
// | Page Directory |   Page Table   | Offset within Page  |
// |      Index     |     Index      |                     |
// +----------------+----------------+---------------------+
//  \--- PDX(la) --/ \--- PTX(la) --/ \---- PGOFF(la) ----/
//  \----------- PPN(la) -----------/
//

// page directory index
#define PDX(la) ((((uintptr_t)(la)) >> PDXSHIFT) & 0x3FF)
// page table index
#define PTX(la) ((((uintptr_t)(la)) >> PTXSHIFT) & 0x3FF)
// page number field of address
#define PPN(la) (((uintptr_t)(la)) >> PTXSHIFT)
// offset in page
#define PGOFF(la) (((uintptr_t)(la)) & 0xFFF)

所以,PDX(la)的作用就是得到一级页表项对应的入口地址。

获取一级页表项

 pde_t *pdep = &pgdir[PDX(la)];  //尝试获得页表

pde_t全称为 page directory entry,也就是一级页表的表项(注意:pgdir实际不是表项,而是一级页表本身。实际上应该新定义一个类型pgd_t来表示一级页表本身)。pte_t全称为 page table entry,表示二级页表的表项。uintptr_t表示为线性地址,由于段式管理只做直接映射,所以它也是逻辑地址。pgdir给出页表起始地址。通过查找这个页表,我们可以得到一级页表项(二级页表的入口地址)。

不存在二级页表项

 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);  //获取页的线性地址
        memset(KADDR(pa), 0, PGSIZE); //初始化
        *pdep = pa | PTE_U | PTE_W | PTE_P; //设置控制位
 }

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

然后设置控制位:

 *pdep = pa | PTE_U | PTE_W | PTE_P; //设置控制位

当建立从一级页表到二级页表的映射时,需要注意设置控制位。这里应该设置同时设置 上PTE_U、PTE_W和PTE_P(定义可在mm/mmu.h)。如果原来就有二级页表,或者新建立了页表,则只需返回对应项的地址即可。

设一个32bit线性地址la有一个对应的32bit物理地址pa,如果在以la的高10位为索引值的页目录项中的存在位(PTE_P)为0,表示缺少对应的页表空间,则可通过alloc_page获得一个空闲物理页给页表,页表起始物理地址是按4096字节对齐的,这样填写页目录项的内容为

页目录项内容 = (页表起始物理地址 &0x0FFF) | PTE_U | PTE_W | PTE_P

其中:

​ PTE_U:位3,表示用户态的软件可以读取对应地址的物理内存页内容

​ PTE_W:位2,表示物理内存页内容可写

​ PTE_P:位1,表示物理内存页存在

返回地址

return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)]; //返回页表的地址

如果原来就有二级页表,或者新建立了页表,则只需返回对应项的地址即可。

实现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) { 
                return NULL;
        }
        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)]; 
    //KADDR(PDE_ADDR(*pdep)):这部分是由页目录项地址得到关联的页表物理地址, 再转成虚拟地址
    //PTX(la):返回虚拟地址la的页表项索引
    //最后返回的是虚拟地址la对应的页表项入口地址
}

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

PTE_P

#define PTE_P           0x001                   // Present

tlb_invalidate函数

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

当修改的页表是进程正在使用的那些页表,使之无效。

page_ref_dec函数

static inline int
page_ref_dec(struct Page *page) {
    page->ref -= 1; //引用数减一
    return page->ref;
}

减少该页的引用次数,返回剩下引用次数。

实现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); 
        //修改的页表是进程正在使用的那些页表,使之无效
    }
}

总结

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

实验结果

这里写图片描述

  • 16
    点赞
  • 54
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

计算机的小粽子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值