操作系统实验报告 lab3
目录:
本次实验主要完成ucore内核对虚拟内存的管理工作。首先完成初始化虚拟内存管理机制,即需要设置好哪些页需要放在物理内存中,哪些页不需要放在物理内存中,而是可被换出到硬盘上,并涉及完善建立页表映射、页错误异常处理操作等函数实现。最后测试编写的代码有没有达到预期的效果。整体的实验不难,但需要掌握一些数据结构之间的关系,这样对理解虚拟内存管理有很大的帮助。
练习0 填写已有实验
将实验二代码补全至实验三
在这里了仍然采用meld工具直接进行比较,截图如下
图1 文件夹对比
总共需要修改的文件有三个
default_pmm.c
pmm.c
trap.c
将代码直接从lab2
复制至lab3
,修改完之后进入下面的实验
练习1 给未被映射的地址映射上物理页
完成 do_pgfault函数,给未被映射的地址映射上物理页。设置访问权限的时候要参考所在页面的VMA权限,同时需要注意映射物理页时需要操作内存控制结构指定的页表,而不是内核页表
1 问题分析:
当启动分页机制以后,如果一条指令或数据的虚拟地址所对应的物理页不在内存中,或者访问权限不够,那么就会产生页错误异常。其具体原因有以下三点:
- 页表项全为0——虚拟地址与物理地址未建立映射关系或已被撤销。
- 物理页面不在内存中——需要进行换页机制。
- 访问权限不够——应当报错。
当出现上面情况之一,那么就会产生页面page fault(#PF)异常。产生异常的线性地址存储在
CR2中,并且将是page fault的产生类型保存在 error code 中
2 结构体分析
vma_struct
每个进程只有一个mm_struct
结构,在每个进程的task_struct
结构体中,有一个指向该进程的结构。可以说,mm_struct
结构是对整个用户空间的描述。
struct vma_struct {
// the set of vma using the same PDT
struct mm_struct *vm_mm;
uintptr_t vm_start; // start addr of vma
uintptr_t vm_end; // end addr of vma
uint32_t vm_flags; // flags of vma
//linear list link which sorted by start addr of vma
list_entry_t list_link;
};
1.vm_start和vm_end描述的是一个合理的地址空间范围(即严格确保 vm_start < vm_end的关系);
2.list_link是一个双向链表,按照从小到大的顺序把一系列用vma_struct表示的虚拟内存空间链接起来,并且还要求这些链起来的vma_struct应该是不相交的,即vma之间的地址空间无交集;
3.vm_flags表示了这个虚拟内存空间的属性,目前的属性包括
#define VM_READ 0x00000001 //只读
#define VM_WRITE 0x00000002 //可读写
#define VM_EXEC 0x00000004 //可执行
4.vm_mm是一个指针,指向一个比vma_struct更高的抽象层次的数据结构mm_struct
mm_struct
较高层次的结构vm_area_struct
描述了虚拟地址空间的一个区间(简称虚拟区)。
struct mm_struct {
// linear list link which sorted by start addr of vma
list_entry_t mmap_list;
// current accessed vma, used for speed purpose
struct vma_struct *mmap_cache;
pde_t *pgdir; // the PDT of these vma
int map_count; // the count of these vma
void *sm_priv; // the private data for swap manager
};
1.mmap_list是双向链表头,链接了所有属于同一页目录表的虚拟内存空间
2.mmap_cache是指向当前正在使用的虚拟内存空间
3.pgdir所指向的就是 mm_struct数据结构所维护的页表
4.map_count记录mmap_list里面链接的vma_struct的个数
5.sm_priv指向用来链接记录页访问情况的链表头
3 管理结构关系
在进程的task_struct
结构中包含一个指向 mm_struct
结构的指针,mm_strcut
用来描述一个进程的虚拟地址空间。
进程的 mm_struct
则包含装入的可执行映像信息以及进程的页目录指针pgd
。该结构还包含有指向 vm_area_struct
结构的几个指针.
每个vm_area_struct
代表进程的一个虚拟地址区间。vm_area_struct
结构含有指向vm_operations_struct
结构的一个指针,vm_operations_struct
描述了在这个区间的操作。vm_operations
结构中包含的是函数指针;其中,open
、close
分别用于虚拟区间的打开、关闭,而nopage
用于当虚存页面不在物理内存而引起的“缺页异常”时所应该调用的函数,当 Linux 处理这一缺页异常时(请页机制),就可以为新的虚拟内存区分配实际的物理内存。
图2 管理结构图
图3 关系图
实现代码
1.目标页面不存在(页表项全为0,即该线性地址与物理地址尚未建立映射或者已经撤销);
2.相应的物理页面不在内存中(页表项非空,但Present标志位=0,比如在swap分区或磁盘文件上)
3.访问权限不符合(此时页表项P标志=1,比如企图写只读页面).
int do_pgfault(struct mm_struct *mm,uint32_t error_code,uintptr addr)
{
pte_t *ptep=NULL;//页目录
if ((ptep = get_pte(mm->pgdir, addr, 1)) == NULL) { //查找页目录 如果不存在则失败
cprintf("get_pte in do_pgfault failed\n");
goto failed;
}
if (*ptep == 0) { //如果物理地址不存在,那么分配一个物理页 并且与虚拟内存建立对应关系
if (pgdir_alloc_page(mm->pgdir, addr, perm) == NULL) {
cprintf("pgdir_alloc_page in do_pgfault failed\n");
goto failed;
}
}
else { //页表项非空,可以尝试换入页面
if(swap_init_ok) {
struct Page *page=NULL;//新建内存页指针
if ((ret = swap_in(mm, addr, &page)) != 0) {//根据mm结构和addr地址,尝试将硬盘中的内容换入至page中
cprintf("swap_in in do_pgfault failed\n");
goto failed;
}
page_insert(mm->pgdir, page, addr, perm);//建立虚拟地址和物理地址之间的对应关系
swap_map_swappable(mm, addr, page, 1);//将此页面设置为可交换的
page->pra_vaddr = addr;
}
else {
cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
goto failed;
}
}
ret = 0
failed:
return ret;
}
练习2 补充完成基于FIFO()的页面替换算法
完成vmm.c中的do_pgfault函数,并且在实现FIFO算法的swap_fifo.c中完成map_swappable和swap_out_vistim函数。通过对swap的测试。
问题分析
根据练习1,当页错误异常发生时,有可能是因为页面保存在swap区或者磁盘文件上造成的,练习2需要利用页面替换算法解决这个问题。
函数分析
map_swappable()
主要解决的问题是将最近被用到的页面添加到算法所维护的次序队列。
链表是由list_entry_t
指针串起来的
mm_struct
结构体的sm_priv
元素是交换管理的私有数据 是一串链表 满足FIFO
先进先出
_fifo_swap_out_victim()
函数是用来查询哪个页面需要被换出,它的主要作用是用来查询哪个页面需要被换出。
实现代码
_fifo_map_swappable()
static int _fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) {
list_entry_t *head=(list_entry_t*) mm->sm_priv;
list_entry_t *entry=&(page->pra_page_link);
assert(entry != NULL && head != NULL);
list_add(head, entry); //将最近用到的页面添加到次序队尾
return 0;
}
_fifo_swap_out_victim()
static int
_fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) {
list_entry_t *head=(list_entry_t*) mm->sm_priv;
assert(head != NULL);
assert(in_tick==0);
list_entry_t *le = head->prev; //用le指示需要被换出的页,最先进入队列的元素head的另一端
assert(head!=le);
struct Page *p = le2page(le, pra_page_link);//le2page宏可以根据链表元素获得对应的Page指针p 这里是物理页属性
list_del(le); //将进来最早的页面从队列中删除
assert(p !=NULL);
*ptr_page = p; //将这一页的地址存储在ptr_page中
return 0;
}
实验截图
图4 实验效果图
check_vma_struct() succeeded!
page fault at 0x00000100: K/W [no page found].
check_pgfault() succeeded!
check_vmm() succeeded.
图5 实验效果图
check_swap() succeeded!
实验运行成功!
总结
1.整个实验下来感觉自己收获很多,通过上网查找资料,自己深刻学习了虚拟内存管理的机制,通过实验更是加深了印象。
2.实验中遇到了很多的问题,比如进程虚拟内存的管理涉及到的一些结构体的关系,由于种类比较多很容易混乱,通过列图梳理了他们之间的关系。
3.操作系统这门课程比较基础,涉及到的内容比较多。有时学起来会比较吃力,但这门课程非常重要,是以后专业课的基石,希望在以后自己留给更多的时间在做实验上,从而更加深入的理解、掌握操作系统