清华操作系统实验ucore_lab2

lab2

实验目的

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

练习0:

需要更改的文件为kdebug.c和trap.c,具体更改的代码如下:

kdebug.c:

uint32_t t_ebp = read_ebp();
	uint32_t t_eip = read_eip();
	int i,j;
	for(i = 0;i< STACKFRAME_DEPTH && t_ebp!=0;i++)
	{
		cprintf("ebp=%08x,eip=%08x,args:",t_ebp,t_eip);
		uint32_t *args = (uint32_t *)t_ebp +2;
		for(j = 0;j<4;j++)
		{
			cprintf("0x%08x",args[j]);
		}
		cprintf("\n");
		print_debuginfo(t_eip-1);
		t_eip = ((uint32_t *)t_ebp)[1];
		t_ebp = ((uint32_t *)t_ebp)[0];
	}

trap.c:

void trap(struct trapframe *tf) {
    ticks ++; //每一次时钟信号会使变量ticks加1
        if (ticks==TICK_NUM) {//TICK_NUM已经被预定义成了100,每到100便调用print_ticks()函数打印
            ticks=0;
            print_ticks();
        } ticks ++; //每一次时钟信号会使变量ticks加1
        if (ticks==TICK_NUM) {//TICK_NUM已经被预定义成了100,每到100便调用print_ticks()函数打印
            ticks=0;
            print_ticks();
        }
}
void idt_init(void) {
extern uintptr_t __vectors[];
     int i;
     //初始化idt
     for(i=0;i<256;i++)
     {
         SETGATE(idt[i],0,GD_KTEXT,__vectors[i],DPL_KERNEL);
     }
     SETGATE(idt[T_SWITCH_TOK],0,GD_KTEXT,__vectors[T_SWITCH_TOK],DPL_USER);
     SETGATE(idt[T_SWITCH_TOU],0,GD_KTEXT,__vectors[T_SWITCH_TOU],DPL_KERNEL);
     lidt(&idt_pd);
}

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

1-1、实现思路

​ 物理内存页管理器顺着双向链表进行搜索空闲内存区域,直到找到一个足够大的空闲区域。如果空闲区域的大小和申请分配的大小正好一样,则把这个空闲区域分配出去,成功返回;否则将该空闲区分为两部分,一部分区域与申请分配的大小相等,把它分配出去,剩下的一部分区域形成新的空闲区。其释放内存的设计思路很简单,只需把这块区域重新放回双向链表中即可。 在实现之前,我们要先了解几个需要用到数据结构。

1-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
};
  • ref表示这样页被页表的引用记数,就是映射此物理页的虚拟页个数。一旦某页表中有一个页表项设置了虚拟页到这个Page管理的物理页的映射关系,就会把Page的ref加一。反之,若是解除,那就减一。
  • flags表示此物理页的状态标记,有两个标志位,第一个表示是否被保留,如果被保留了则设为1(比如内核代码占用的空间)。第二个表示此页是否是free的。如果设置为1,表示这页是free的,可以被分配;如果设置为0,表示这页已经被分配出去了,不能被再二次分配。
  • property用来记录某连续内存空闲块的大小,这里需要注意的是用到此成员变量的这个Page一定是连续内存块的开始地址(第一页的地址)。
  • page_link是便于把多个连续内存空闲块链接在一起的双向链表指针,连续内存空闲块利用这个页的成员变量page_link来链接比它地址小和大的其他连续内存空闲块

1-3、双向链表

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则记录当前空闲页的个数

1-4、代码实现

ucore使用物理内存管理类pmm_manager来绑定一个函数,我们需要在该类中包含需要实现的函数的名字。

default_init函数:

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

​ 直接调用库函数list_init初始化掉free_area_t(管理所有连续的空闲内存空间块的数据结构free_area_t)的双向链表和空闲块数,不需要修改。

default_init_memmap函数:

​ **函数功能:**这个函数实现的是一个根据现有的内存情况构建空闲块列表的初始状态的功能。用来初始化空闲页链表的,初始化每一个空闲页,然后计算空闲页的总数。

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

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

*Page default_alloc_pages函数:

​ 函数功能是寻找第一个能够满足需求的空闲块(First-fit)。先顺序查找,直到查找完或找到第一个满足条件的空闲块,当找到后,就将该块截断取出。因此可以明显的看出原代码的问题,当块被找到之后,只取出了第一页,而其他页并没有被取出,且没有被设为保留页,所以按照这个思路进行改动。

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

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

1-5、是否还有进一步的改进空间

​ 我认为还有提升空间。在free操作中,寻找需要free的base地址的时候,依靠的是遍历,通过改进算法,可以直接将base地址传入,无需遍历,直接找到位置开始操作,减少时间开销。

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

2-1、编程实现

​ 3-1、数据结构Page的全局变量(其实是一个数组)的每一项与页表中的页目录项和页表项有无对应关系?如果有,其对应关系是啥?

有关系,数据结构page是最低级的页表,目录项是一级页表,存储的内容是页表项的起始地址(二级页表),而页表项是二级页表,存储的是每个页表的开始地址,这些内容之间的关系时通过线性地址高低位不同功能的寻址体现的。

3-2、如果希望虚拟地址与物理地址相等,则需要如何修改lab2,完成此事? 鼓励通过编程来具体完成这个问题

​ 物理地址和虚拟地址之间存在一定偏移offset,通常,这个offset,就是KERNBASE是由操作系统决定的,而ucore中,定义在(kern/mm/memlayout.h)。KERNBASE为虚拟地址空间中的内核基址,即偏移量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b7js0FZN-1639279573174)(E:/Typore%E5%9B%BE%E7%89%87/image-20211125225445106.png)]

由图可知lab2设置的偏移量为0xC,将其改为0即可。

2-2、请描述页目录项(Pag Director Entry)和页表(Page Table Entry)中每个组成部分的含义和以及对ucore而言的潜在用处。

页目录项:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MxZfy99U-1639279573175)(E:/Typore%E5%9B%BE%E7%89%87/image-20211125185323213.png)]

从低到高,分别是:

  • P (Present) 位:表示该页保存在物理内存中。
  • R (Read/Write) 位:表示该页可读可写。
  • U (User) 位:表示该页可以被任何权限用户访问。
  • W (Write Through) 位:表示 CPU 可以直写回内存。
  • D (Cache Disable) 位:表示不需要被 CPU 缓存。
  • A (Access) 位:表示该页被写过。
  • S (Size) 位:表示一个页 4MB 。
  • 9-11 位保留给 OS 使用。
  • 12-31 位指明 PTE 基质地址。

页表项:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VBcWfbUw-1639279573175)(E:/Typore%E5%9B%BE%E7%89%87/image-20211125185406067.png)]

从低到高,分别是:

  • 0-3 位同 PDE。
  • C (Cache Disable) 位:同 PDE D 位。
  • A (Access) 位:同 PDE 。
  • D (Dirty) 位:表示该页被写过。
  • G (Global) 位:表示在 CR3 寄存器更新时无需刷新 TLB 中关于该页的地址。
  • 9-11 位保留给 OS 使用。
  • 12-31 位指明物理页基址。

对ucore而言的潜在用处:

​ 假如在系统里面,物理内存和虚拟内存是一一对应的,那么在进程空间里面就会存在很多的页表,同时也会占据很多的空间,那么,为了解决这个问题就出现了多级页表。

2-3、如果ucore执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?

​ 会进行换页操作。首先 CPU 将产生页访问异常的线性地址放到 cr2 寄存器中,然后就是和普通的中断一样,保护现场,将寄存器的值压入栈中,设置错误代码 error_code,触发 Page Fault 异常,然后压入 error_code 中断服务例程,将外存的数据换到内存中来,最后退出中断,回到进入中断前的状态。

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

3-1、编程实现

​ **实现思路:**主要就是先判断该页被引用的次数,如果只被引用了一次,那么直接释放掉这页, 否则就删掉二级页表的该表项,即该页的入口。我们先将物理页的引用数目减一,如果变为零,那么释放页面;然后将页目录项清零,刷新页表,即可取消页表映射。

static inline void page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep)
{
 if (*ptep & PTE_P) {         //如果页表项存在               
        struct Page *page = pte2page(*ptep);    	//找到页表项
        page_ref_dec(page);                     
        if (page->ref == 0) {    //如果只有当前进程引用
            free_page(page);       //释放页             
        }
        *ptep = 0;           	//设置页目录项为0                  
        tlb_invalidate(pgdir, la);     //修改的页表是进程正在使用的页表,使其无效 
    }
    }

运行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gdd2cEXH-1639279573176)(E:/Typore%E5%9B%BE%E7%89%87/image-20211125234052813.png)]

3-2、数据结构Page的全局变量(其实是一个数组)的每一项与页表中的页目录项和页表项有无对应关系?如果有,其对应关系是啥?

​ 有关系,数据结构page是最低级的页表,目录项是一级页表,存储的内容是页表项的起始地址(二级页表),而页表项是二级页表,存储的是每个页表的开始地址,这些内容之间的关系时通过线性地址高低位不同功能的寻址体现的。

3-3、如果希望虚拟地址与物理地址相等,则需要如何修改lab2,完成此事? 鼓励通过编程来具体完成这个问题

​ 物理地址和虚拟地址之间存在一定偏移offset,通常,这个offset,就是KERNBASE,是由操作系统决定的,而ucore中,定义在(kern/mm/memlayout.h)。KERNBASE为虚拟地址空间中的内核基址,即偏移量。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-InKPyJDe-1639279573176)(E:/Typore%E5%9B%BE%E7%89%87/image-20211125225445106.png)]

由图可知lab2设置的偏移量为0xC,将其改为0即可。

4、实验重难点分析:

4.1段页式管理基本概念

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

4.2地址映射

1、第一个阶段(开启保护模式,创建启动段表)是bootloader阶段,即从bootloader的start函数(在boot/bootasm.S中)到执行ucore kernel的kern_entry函数之前,其虚拟地址、线性地址以及物理地址之间的映射关系与lab1的一样。
2、第二个阶段(创建初始页目录表,开启分页模式)从kern_entry函数开始,到pmm_init函数被执行之前。编译好的ucore自带了一个设置好的页目录表和相应的页表,将0~4M的线性地址一一映射到物理地址。了解了一一映射的二级页表结构后,接下来就要使能分页机制了。
3、第三个阶段(完善段表和页表)从pmm_init函数被调用开始。pmm_init函数将页目录表项补充完成(从04M扩充到0KMEMSIZE)。然后,更新了段映射机制,使用了一个新的段表。这个新段表除了包括内核态的代码段和数据段描述符,还包括用户态的代码段和数据段描述符以及TSS(段)的描述符。

4.3建立虚拟页和物理页帧的地址映射关系

Intel 80386采用了二级页表来建立线性地址与物理地址之间的映射关系。由于我们已经具有了一个物理内存页管理器default_pmm_manager,支持动态分配和释放内存页的功能,我们就可以用它来获得所需的空闲物理页。在二级页表结构中,页目录表占4KB空间,可通过alloc_page函数获得一个空闲物理页作为页目录表(Page Directory Table,PDT)。同理,ucore也通过这种类似方式获得一个页表(Page Table,PT)所需的4KB空间。
为把0~KERNSIZE的物理地址一一映射到页目录项和页表项的内容,其大致流程如下:
①指向页目录表的指针已存储在boot_pgdir变量中。
②映射0~4MB的首个页表已经填充好。
③调用boot_map_segment函数进一步建立一一映射关系,处理过程以页为单位进行设置。
设一个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,表示物理内存页存在。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值