操作系统 ucore lab2实验报告

ucore lab2(物理内存管理)

一、实验目的

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

二、实验内容

本次实验包含三个部分。首先了解如何发现系统中的物理内存;然后了解如何建立对物理内存的初步管理,即了解连续物理内存管理;最后了解页表相关的操作,即如何建立页表来实现虚拟内存到物理内存之间的映射,对段页式内存管理机制有一个比较全面的了解。本实验里面实现的内存管理还是非常基本的,并没有涉及到对实际机器的优化,比如针对 cache 的优化等。如果大家有余力,尝试完成扩展练习。

三、实验步骤及流程

3.0 练习0:填写已有实验

3.0.1实验要求

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

3.0.2实验操作

根据要求将以下部分代码合并入LAB2中。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

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

3.1.1实验要求

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

3.1.2关键数据结构及知识点

(1)首次匹配
要求空闲分区链以地址递增的次序链接。在分配内存时,从链首开始顺序查找,直至找到一个大小能满足要求的分区为止;然后再按照作业的大小,从该分取中划出一块内存空间分配给请求者,余下的空闲分区仍留在空闲链中。若从链首直到链尾都不能找到一个能满足要求的分区,则此次内存分配失败,返回。该算法倾向于优先利用内存中低地址部分的空闲分区,从而保留了高址部分的大空闲区。这给为以后到达的大作业分配大的内存空间创造了条件,其缺点是低址部分不断被划分,会留下许多难以利用的、很小的空闲分区,而每次查找又都是从低址部分开始,这无疑会增加查找可用空闲分区时的开销。
(2)default_init()
这个初始化很简单,直接调用库函数list_init初始化掉free_area_t(管理所有连续的空闲内存空间块的数据结构free_area_t)的双向链表和空闲块数。
在这里插入图片描述
(3)default_init_memmap()
default_init_memmap函数主要实现的是一个根据现有的内存情况构建空闲块列表的初始状态的功能。用来初始化空闲页链表的,初始化每一个空闲页,然后计算空闲页的总数。
调用过程是:kern_init --> pmm_init–>page_init–>init_memmap。涉及到的这些函数作用如下:
(1)kern_init(kern/init/init.c)
这个函数是进入ucore操作系统之后,第一个执行的函数,对于内核进行初始化。在其中,调用了初始化物理内存的函数pmm_init。
(2)pmm_init(kern/mm/pmm.c)
这个函数主要是完成对于整个物理内存的初始化,页初始化只是其中的一部分,调用位置偏前,函数之后的部分可以不管,直接进入page_init函数。
(3)page_init(kern/mm/pmm.c)
page_init函数主要是完成了一个整体物理地址的初始化过程,包括设置标记位,探测物理内存布局等操作。但是,其中最关键的部分,也是和实验相关的页初始化,交给了init_memmap函数处理。
在这里插入图片描述
实现此函数的主要过程为:传入物理页基地址,和物理页的个数(个数必须大于 0),然后对每一块物理页进行设置:先判断是否为保留页,如果不是,则进行下一步。将标志位清0,连续空页个数清0,然后将标志位设置为1,将引用此物理页的虚拟页的个数清0。然后再加入空闲链表。最后计算空闲页的个数,修改物理基地址页的property的个数为n。
(4)default_alloc_pages()
遍历空闲页块的链表,找到第一块大小大于n的块,分配出来,从空闲页链表中移除,若有多余的,再加入空闲页链表中。准确的说,这个函数是用来分配空闲页的。首先判断空闲页的大小是否大于所需的页块大小。如果小于空闲页的大小。则遍历整个空闲链表。如果找到合适的空闲页,则重新设置标志位。然后从空闲链表中删除此页。如果当前空闲页的大小大于所需大小。则分割页块。如果合适则不进行操作,最后计算剩余空闲页个数并返回分配的页块地址。代码如下:
在这里插入图片描述
(5)default_free_pages()
这个函数的作用是释放已经使用完的页,把他们合并到freelist中。在freelist中查找合适的位置以供插入。改变被释放页的标志位,以及头部的计数器尝试在freelist中向高地址或低地址合并。
在这里插入图片描述

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

3.2.1实验要求

通过设置页表和对应的页表项,可建立虚拟内存地址和物理内存地址的对应关系。其中的get_pte函数是设置页表项环节中的一个重要步骤。此函数找到一个虚地址对应的二级页表项的内核虚地址,如果此二级页表项不存在,则分配一个包含此项的二级页表。本练习需要补全get_pte函数 in kern/mm/pmm.c,实现其功能。请仔细查看和理解get_pte函数中的注释。
请在实验报告中简要说明你的设计实现过程。请回答如下问题:
请描述页目录项(Page Directory Entry)和页表项(Page Table Entry)中每个组成部分的含义以及对ucore而言的潜在用处。
如果ucore执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?

3.2.2关键数据结构及知识点

(1)三种地址
内存地址分成三种:逻辑地址(也称虚地址)、线性地址和物理地址。逻辑地址即是程序指令中使用的地址。物理地址是实际访问内存的地址。逻辑地址通过段式管理的地址映射可以得到线性地址,线性地址通过页式管理的地址映射得到物理地址。get _pte函数是给出了线性地址,即linear address。
线性地址(Linear Address)是逻辑地址到物理地址变换之间的中间层。程式代码会产生逻辑地址,或说是段中的偏移地址,加上相应段的基地址就生成了一个线性地址。如果启用了分页机制,那么线性地址能再经变换以产生一个物理地址。
(2)页式管理将线性地址分成三部分
(图中的 Linear Address 的 Directory 部分、 Table 部分和 Offset 部分)。ucore 的页式管理通过一个二级的页表实现。一级页表存放在高10位中,二级页表存放于中间10位中,最后的12位表示偏移量。
在这里插入图片描述

3.2.3代码实现

pde_t全称为 page directory entry,也就是一级页表的表项。pte_t全称为 page table entry,表示二级页表的表项。uintptr_t表示为线性地址,由于段式管理只做直接映射,所以它也是逻辑地址。pgdir给出页表起始地址。通过查找这个页表,我们可以得到一级页表项(二级页表的入口地址)。
如果在查找二级页表项时,发现对应的二级页表不存在,则需要根据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:表示物理内存页存在。
当建立从一级页表到二级页表的映射时,需要注意设置控制位。这里应该设置同时设置 上PTE_U、PTE_W和PTE_P(定义可在mm/mmu.h)。如果原来就有二级页表,或者新建立了页表,则只需返回对应项的地址即可。具体代码如下:
在这里插入图片描述

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

3.3.1实验要求

当释放一个包含某虚地址的物理内存页时,需要让对应此物理内存页的管理数据结构Page做相关的清除处理,使得此物理内存页成为空闲;另外还需把表示虚地址与物理地址对应关系的二级页表项清除。请仔细查看和理解page_remove_pte函数中的注释。为此,需要补全在 kern/mm/pmm.c中的page_remove_pte函数。
请在实验报告中简要说明你的设计实现过程。请回答如下问题:
数据结构Page的全局变量(其实是一个数组)的每一项与页表中的页目录项和页表项有无对应关系?如果有,其对应关系是啥?
如果希望虚拟地址与物理地址相等,则需要如何修改lab2,完成此事? 鼓励通过编程来具体完成这个问题

3.3.2代码实现

传入参数和寻找二级页表的时候差不多,传入的是一个一级、二级地址,和一个线性地址,用于确认虚拟地址(线性地址)所在页的位置。
首先,需要确认这个页是存在的,使用*ptep和上一题中定义的标记位PTE_P(0x001,代表页存在)。如果这个页存在,那么使用pte2page获取其物理地址。。
调用函数page_ref_dec查看当前这个页被引用的次数,如果只被上一级(二级页表)引用了一次,那么减一以后就是0,页和对应的二级页表都可以直接被释放(将二级页表置0是取消映射)。如果还有更多的页表应用了它,那就不能释放掉这个页,但是取消对应二级页表项的映射,也就是把映射的入口(传入的二级页表)释放为0。
然后调用函数tlb_invalidate使TLB条目无效,但仅当正在编辑的页表是处理器当前正在使用的页表时。具体作用是去除那些已经被清除关系的二级页表,更新pgdir,也就是一级页表。
在这里插入图片描述

四、思考题

Q1:你的first fit算法是否有进一步的改进空间?
我认为还有提升空间。比如在free操作中,寻找需要free的base地址的时候,依靠的是遍历,通过改进算法,可以直接将base地址传入,无需遍历,直接找到位置开始操作,减少时间开销。
Q2:请描述页目录项(Page Directory Entry)和页表项(Page Table Entry)中每个组成部分的含义以及对ucore而言的潜在用处。
页目录项是一级页表,存储了各二级页表的起始地址,页表是二级页表,存储了各个页的起始地址。一个虚拟地址(线性地址)通过页机制翻译得到最终的物理地址。页表的主要作用是:假如在系统里面,物理内存和虚拟内存是一一对应的,那么在进程空间里面就会存在很多的页表,同时也会占据很多的空间,那么,为了解决这个问题就出现了多级页表。
Q3:如果ucore执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?
1.硬件陷入内核,在内核堆栈中保存程序计数器。
2.启动一个汇编代码例程保存通用寄存器和其他易失的信息,以免被操作系统破坏。
3.当操作系统发现一个缺页中断时,尝试发现需要哪个虚拟页面。通常一个硬件寄存器包含了这一信息,如果没有的话,操作系统必须检索程序计数器,取出这条指令,用软件分析这条指令,看看它在缺页中断时正在做什么。
4.一旦知道了发生缺页中断的虚拟地址,操作系统检查这个地址是否有效,并检查存取与保护是否一致。如果不一致,向进程发出一个信号或杀掉该进程。如果地址有效且没有保护错误发生,系统则检查是否有空闲页框。如果没有空闲页框,执行页面置换算法寻找一个页面来淘汰。
5.如果选择的页框“脏”了,安排该页写回磁盘,并发生一次上下文切换,挂起产生缺页中断的进程,让其他进程运行直至磁盘传输结束。无论如何,该页框被标记为忙,以免因为其他原因而被其他进程占用。
6.一旦页框“干净”后(无论是立刻还是在写回磁盘后),操作系统查找所需页面在磁盘上的地址,通过磁盘操作将其装入。该页面被装入后,产生缺页中断的进程仍然被挂起,并且如果有其他可运行的用户进程,则选择另一个用户进程运行。
7.当磁盘中断发生时,表明该页已经被装入,页表已经更新可以反映它的位置,页框也被标记为正常状态。
8.恢复发生缺页中断指令以前的状态,程序计数器重新指向这条指令。
9.调度引发缺页中断的进程,操作系统返回调用它的汇编语言。
10.该例程恢复寄存器和其他状态信息。
Q4:数据结构Page的全局变量(其实是一个数组)的每一项与页表中的页目录项和页表项有无对应关系?如果有,其对应关系是啥?
有关系,数据结构page是最低级的页表,目录项是一级页表,存储的内容是页表项的起始地址(二级页表),而页表项是二级页表,存储的是每个页表的开始地址,这些内容之间的关系时通过线性地址高低位不同功能的寻址体现的。
Q5:如果希望虚拟地址与物理地址相等,则需要如何修改lab2,完成此事?
物理地址和虚拟地址之间存在一定偏移offset,通常,这个offset,就是KERNBASE是由操作系统决定的,而ucore中,对其也是有定义的,定义在(kern/mm/memlayout.h)。KERNBASE为虚拟地址空间中的内核基址,即偏移量。
在这里插入图片描述
在ucore中,虚拟地址和物理地址之间的偏移量为0xC0000000,将其改为0即可完成题目要求。

五、运行结果

运行结果如下:从图中我们可以看到ucore在显示其entry(入口地址)、etext(代码段截止处地址)、edata(数据段截止处地址)、和end(ucore截止处地址)的值后,探测出计算机系统中的物理内存的布局(e820map下的显示内容)。接下来ucore会以页为最小分配单位实现一个简单的内存分配管理,完成二级页表的建立,进入分页模式,执行各种我们设置的检查,最后显示ucore建立好的二级页表内容,并在分页模式下响应时钟中断。
在这里插入图片描述

六、实验重难点总结

6.1段页式管理基本概念
在下图的保护模式中,x86 体系结构将内存地址分成三种:逻辑地址(也称虚地址)、线性地址和物理地址。逻辑地址即是程序指令中使用的地址,物理地址是实际访问内存的地址。逻辑地址通过段式管理的地址映射可以得到线性地址,线性地址通过页式管理的地址映射得到物理地址。
在这里插入图片描述
6.2地址映射
①第一个阶段(开启保护模式,创建启动段表)是bootloader阶段,即从bootloader的start函数(在boot/bootasm.S中)到执行ucore kernel的kern_entry函数之前,其虚拟地址、线性地址以及物理地址之间的映射关系与lab1的一样。
②第二个阶段(创建初始页目录表,开启分页模式)从kern_entry函数开始,到pmm_init函数被执行之前。编译好的ucore自带了一个设置好的页目录表和相应的页表,将0~4M的线性地址一一映射到物理地址。了解了一一映射的二级页表结构后,接下来就要使能分页机制了。
③第三个阶段(完善段表和页表)从pmm_init函数被调用开始。pmm_init函数将页目录表项补充完成(从04M扩充到0KMEMSIZE)。然后,更新了段映射机制,使用了一个新的段表。这个新段表除了包括内核态的代码段和数据段描述符,还包括用户态的代码段和数据段描述符以及TSS(段)的描述符。
6.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,表示物理内存页存在。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值