uCoreOs lab3 实验报告

实验目的

  • 了解虚拟内存的Page Fault异常处理实现
  • 了解页替换算法在操作系统中的实现

实验内容

练习介绍

​ 本次实验是在实验二的基础上,借助于页表机制和实验一中涉及的中断异常处理机制,完成Page Fault异常处理和FIFO页替换算法的实现,结合磁盘提供的缓存空间,从而能够支持虚存管理,提供一个比实际物理内存空间“更大”的虚拟内存空间给系统使用。这个实验与实际操作系统中的实现比较起来要简单,不过需要了解实验一和实验二的具体实现。实际操作系统系统中的虚拟内存管理设计与实现是相当复杂的,涉及到与进程管理系统、文件系统等的交叉访问。如果大家有余力,可以尝试完成扩展练习,实现extended clock页替换算法。

虚存管理介绍

  • 内存地址虚拟化能有效隔离不同进程的内存访问空间:在有了分页机制后,程序员或CPU“看到”的地址已经不是实际的物理地址了,这已经有一层虚拟化,我们可简称为内存地址虚拟化。有了内存地址虚拟化,我们就可以通过设置页表项来限定软件运行时的访问空间,确保软件运行不越界,完成内存访问保护的功能。

  • 内存地址虚拟化能提供更大的内存“空间”:

    • 按需分页:通过内存地址虚拟化,可以使得软件在没有访问某虚拟内存地址时不分配具体的物理内存,而只有在实际访问某虚拟内存地址时,操作系统再动态地分配物理内存,建立虚拟内存到物理内存的页映射关系,这种技术称为按需分页(demand paging) 。
    • 页换入换出:把不经常访问的数据所占的内存空间临时写到硬盘上,这样可以腾出更多的空闲内存空间给经常访问的数据;当CPU访问到不经常访问的数据时,再把这些数据从硬盘读入到内存中,这种技术称为页换入换出(page swap in/out) 。这种内存管理技术给了程序员更大的内存“空间”,从而可以让更多的程序在内存中并发运行。

虚存管理总体框架

​ 1.完成初始化虚拟内存管理机制:IDE 硬盘读写,缺页异常处理

​ 2.设置虚拟页空间和物理页帧空间,表述不在物理内存中的“合法”虚拟页

​ 3.完善建立页表映射、页访问异常处理操作等函数实现

​ 4.执行访存测试,查看建立的页表项是否能够正确完成虚实地址映射

​ 5.执行访存测试,查看是否正确描述了虚拟内存页在物理内存中还是在硬盘上

​ 6.执行访存测试,查看是否能够正确把虚拟内存页在物理内存和硬盘之间进行传递

​ 7.执行访存测试,查看是否正确实现了页面替换算法等

实验过程

练习0:填写已有实验

本实验依赖实验1/2。请把你做的实验1/2的代码填入本实验中代码中有“LAB1”,“LAB2”的注释相应部分。

用meld工具比较两个文件夹的差异

只需对kdebug.c、trap.c、default_pmm.c、pmm.c四个文件进行相应补充即可

练习1:给未被映射的地址映射上物理页

完成do_pgfault(mm/vmm.c)函数,给未被映射的地址映射上物理页。设置访问权限 的时候需要参考页面所在 VMA 的权限,同时需要注意映射物理页时需要操作内存控制 结构所指定的页表,而不是内核的页表。注意:在LAB3 EXERCISE 1处填写代码。执行

1
make qemu

后,如果通过check_pgfault函数的测试后,会有“check_pgfault() succeeded!”的输出,表示练习1基本正确。

请在实验报告中简要说明你的设计实现过程。请回答如下问题:

  • 请描述页目录项(Page Directory Entry)和页表项(Page Table Entry)中组成部分对ucore实现页替换算法的潜在用处。
  • 如果ucore的缺页服务例程在执行过程中访问内存,出现了页访问异常,请问硬件要做哪些事情?

实验过程

查看init.c函数

image-20200409204157428

可以看出虚拟内存初始化前便进行了物理内存初始化

查看vmm.h相关结构体内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct mm_struct { // 描述一个进程的虚拟地址空间 每个进程的 pcb 中 会有一个指针指向本结构体
list_entry_t mmap_list; // 链接同一页目录表的虚拟内存空间 的 双向链表的 头节点
struct vma_struct *mmap_cache; // 当前正在使用的虚拟内存空间
pde_t *pgdir; // mm_struct 所维护的页表地址(拿来找 PTE)
int map_count; // 虚拟内存块的数目
void *sm_priv; // 记录访问情况链表头地址(用于置换算法)
};
struct vma_struct { // 虚拟内存空间
struct mm_struct *vm_mm; // 虚拟内存空间属于的进程
uintptr_t vm_start; // 连续地址的虚拟内存空间的起始位置和结束位置
uintptr_t vm_end;
uint32_t vm_flags; // 虚拟内存空间的属性 (读/写/执行)
list_entry_t list_link; // 双向链表 从小到大将虚拟内存空间链接起来
};

//vm_flags:该虚拟内存空间的属性,目前的属性包括可读、可写、可执行;
#define VM_READ 0x00000001
#define VM_WRITE 0x00000002
#define VM_EXEC 0x00000004
//可以看出来三个权限是在uint32的后三位上。

mm_vma

其中mm_struct 描述了整个进程的虚拟地址空间,而 vma_struct 描述了进程中的一小部分虚拟内存空间

查看提示

image-20200409230159885

根据提示填入代码

1
2
3
4
5
6
ptep = get_pte(mm->pgdir, addr, 1); // 根据引发缺页异常的地址 去找到 地址所对应的 PTE 如果找不到 则创建一页表
if (*ptep == 0) { // PTE 所指向的 物理页表地址 若不存在 则分配一物理页并将逻辑地址和物理地址作映射 (就是让 PTE 指向 物理页帧)
if (pgdir_alloc_page(mm->pgdir, addr, perm) == NULL) {
goto failed;
}
}

make qemu

image-20200409235404911

显示成功,但check_swap()错误,出现了缺页错误

回答问题

本练习问题和lab2的练习2完全一致

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

PDE 详解

从低到高,分别是:

  • 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 基质地址。

PTE 详解

从低到高,分别是:

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

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

使能页机制

image-20200328093548814

Cr0系列的控制寄存器:

寄存器名称 描述
CR0 包含处理器标志控制位,如PE,PG,WP等
CR1 保留
CR2 专门用于保存缺页异常时的线性地址
CR3 保存进程页目录地址
CR4 扩展功能(如判断物理地址扩展模式等),Pentium系列(包括486的后期版本)处理器中才实现

如果出现了页访问异常,那么硬件将引发页访问异常的地址将被保存在cr2寄存器中,设置错误代码,然后触发Page Fault异常。

下图为页访问异常的各种情况:

image-20200410020310883

页访问异常(Page Fault)

  • 在程序的执行过程中由于某种原因(页框不存在/写只读页等) 而使 CPU 无法最终访问到相应的物理内存单元,即无法完成从虚拟地址到物理地址映射时,CPU 会产生一次页访问异常,从而需要进行相应的页访问异常的中断服务例程。这个页访问异常处理的时机被操作系统充分利用来完成虚存管理,即实现“按需调页”/“页换入换出”处理的执行时机。当相关处理完成后,页访问异常服务例程会返回到产生异常的指令处重新执行,使得应用软件可以继续正常运行下去。
  • 具体而言,当启动分页机制以后,如果一条指令或数据的虚拟地址所对应的物理页框不在内存中或者访问的类型有错误(比如写一个只读页或用户态程序访问内核态的数据等) ,就会发生页访问异常。产生页访问异常的原因主要有:
    • 目标页帧不存在(页表项全为0,即该线性地址与物理地址尚未建立映射或者已经撤销)
    • 相应的物理页帧不在内存中(页表项非空,但Present标志位=0,比如在swap分区或磁盘文件上)
    • 不满足访问权限(此时页表项P标志=1,但低权限的程序试图访问高权限的地址空间,或者有程序试图写只读页面)
  • CPU会把产生异常的线性地址存储在CR2中,并且把表示页访问异常类型的值(简称页访问异常错误码,errorCode)保存在中断栈中。页访问异常错误码有32位。位0为1表示对应物理页不存在;位1为1表示写异常(比如写了只读页);位2为1表示访问权限异常(比如用户态程序访问内核空间的数据)
  • CR2是页故障线性地址寄存器,保存最后一次出现页故障的全32位线性地址。CR2用于发生页异常时报告出错信息。当发生页异常时,处理器把引起页异常的线性地址保存在CR2中。操作系统中对应的中断服务例程可以检查CR2的内容,从而查出线性地址空间中的哪个页引起本次异常。

练习2:补充完成基于FIFO的页面替换算法

完成vmm.c中的do_pgfault函数,并且在实现FIFO算法的swap_fifo.c中完成map_swappable和swap_out_victim函数。通过对swap的测试。注意:在LAB3 EXERCISE 2处填写代码。执行

1
make qemu

后,如果通过check_swap函数的测试后,会有“check_swap() succeeded!”的输出,表示练习2基本正确。

请在实验报告中简要说明你的设计实现过程。

请在实验报告中回答如下问题:

  • 如果要在ucore上实现"extended clock页替换算法"请给你的设计方案,现有的swap_manager框架是否足以支持在ucore中实现此算法?如果是,请给你的设计方案。如果不是,请给出你的新的扩展和基此扩展的设计方案。并需要回答如下问题
    • 需要被换出的页的特征是什么?
    • 在ucore中如何判断具有这样特征的页?
    • 何时进行换入和换出操作?

实验过程

查看vmm.c中lab2注释

image-20200410001251895

根据注释写代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
else { // 如果 PTE 存在 说明此时 P 位为 0 该页被换出到外存中 需要将其换入内存
if(swap_init_ok) { // 是否可以换入页面
struct Page *page = NULL;
ret = swap_in(mm, addr, &page); // 根据 PTE 找到 换出那页所在的硬盘地址 并将其从外存中换入
if (ret != 0) {
cprintf("swap_in in do_pgfault failed\n");
goto failed;
}
page_insert(mm->pgdir, page, addr, perm); // 建立虚拟地址和物理地址之间的对应关系(更新 PTE 因为 已经被换入到内存中了)
swap_map_swappable(mm, addr, page, 0); // 使这一页可以置换
page->pra_vaddr = addr; // 设置 这一页的虚拟地址
}
else {
cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep);
goto failed;
}
}

查看swap_fifo.c注释

image-20200410001111541

根据注释写代码

此时完成的是 FIFO 置换算法,因此每次换出的都应该是最先进来的 页

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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;
}

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; // 换出最先进来的页 (因为每次访问一个页 都是插入到头节点的后面 因此 头节点的前面就是最先访问的页)
+ struct Page* page = le2page(le, pra_page_link); // 和之前一样 通过 le 这个链表节点的地址 减去 pra_page_link 在 Page 结构体中的 Offset 得到 Page 的地址
+ list_del(le); // 删掉这个节点
+ *ptr_page = page; // 将这一页地址存到 ptr_page 中 给 调用本函数的函数使用
return 0;
}

重新make qemu,得到下面的结果

image-20200410010706159

make grade

image-20200410030434318

成功运行

观察FIFO的check函数的对应关系

image-20200410135030537

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
swap.c
static inline void
check_content_set(void)
{
*(unsigned char *)0x1000 = 0x0a;
assert(pgfault_num==1);
*(unsigned char *)0x1010 = 0x0a;
assert(pgfault_num==1);
*(unsigned char *)0x2000 = 0x0b;
assert(pgfault_num==2);
*(unsigned char *)0x2010 = 0x0b;
assert(pgfault_num==2);
*(unsigned char *)0x3000 = 0x0c;
assert(pgfault_num==3);
*(unsigned char *)0x3010 = 0x0c;
assert(pgfault_num==3);
*(unsigned char *)0x4000 = 0x0d;
assert(pgfault_num==4);
*(unsigned char *)0x4010 = 0x0d;
assert(pgfault_num==4);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
swap_fifo.c
static int
_fifo_check_swap(void) {
cprintf("write Virt Page c in fifo_check_swap\n");
*(unsigned char *)0x3000 = 0x0c;
assert(pgfault_num==4);
cprintf("write Virt Page a in fifo_check_swap\n");
*(unsigned char *)0x1000 = 0x0a;
assert(pgfault_num==4);
cprintf("write Virt Page d in fifo_check_swap\n");
*(unsigned char *)0x4000 = 0x0d;
assert(pgfault_num==4);
cprintf("write Virt Page b in fifo_check_swap\n");
*(unsigned char *)0x2000 = 0x0b;
assert(pgfault_num==4);
cprintf("write Virt Page e in fifo_check_swap\n");
*(unsigned char *)0x5000 = 0x0e;
assert(pgfault_num==5);
cprintf("write Virt Page b in fifo_check_swap\n");
*(unsigned char *)0x2000 = 0x0b;
assert(pgfault_num==5);
cprintf("write Virt Page a in fifo_check_swap\n");
*(unsigned char *)0x1000 = 0x0a;
assert(pgfault_num==6);
cprintf("write Virt Page b in fifo_check_swap\n");
*(unsigned char *)0x2000 = 0x0b;
assert(pgfault_num==7);
cprintf("write Virt Page c in fifo_check_swap\n");
*(unsigned char *)0x3000 = 0x0c;
assert(pgfault_num==8);
cprintf("write Virt Page d in fifo_check_swap\n");
*(unsigned char *)0x4000 = 0x0d;
assert(pgfault_num==9);
return 0;
}

回答问题

如果要在ucore上实现"extended clock页替换算法"请给你的设计方案,现有的swap_manager框架是否足以支持在ucore中实现此算法?如果是,请给你的设计方案。如果不是,请给出你的新的扩展和基此扩展的设计方案。并需要回答如下问题

  • 需要被换出的页的特征是什么?
  • 在ucore中如何判断具有这样特征的页?
  • 何时进行换入和换出操作?

支持。

image-20200410033815992

在mmu.h文件可以查看页表项标志位

image-20200410032433658

其中的PTE_A和PTE_D可用于实现extended clock页替换算法,可对swap_fifo.c做相应修改即可。

  • 需要被换出的页的特征是什么?

    页表项的修改位 D(Dirty Bit)为 0 的页 且访问位 A( Access Bit) 为 0 的页。

  • 在 ucore 中如何判断具有这样特征的页?

    检查页表项的访问位和修改位是不是1。当内存页被访问后,MMU 将在对应的页表项的 PTE_A 这一位设为1,当内存页被修改后,MMU 将在对应的页表项的 PTE_D 这一位设为1。

  • 何时进行换入和换出操作?

    当进程访问的物理页没有在内存中缓存而是保存在磁盘中(缺页且内容在磁盘中)时,需要进行换入操作; 当位于物理页中的内存由于物理页帧满被页面替换算法选择换出(alloc_page返回NULL)时,需要进行换出操作。

扩展练习 Challenge 1:实现识别dirty bit的 extended clock页替换算法

实验过程

实现基础:

image-20200410033815992

复制swap_fifo.c和swap_fifo.h到同原文件夹并重命名为swap_clock.c和swap_clock.h

对照_fifo_swap_out_victim函数结构

1
2
3
4
5
6
7
8
9
10
11
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; // 换出最先进来的页 (因为每次访问一个页 都是插入到头节点的后面 因此 头节点的前面就是最先访问的页)
struct Page* page = le2page(le, pra_page_link); // 和之前一样 通过 le 这个链表节点的地址 减去 pra_page_link 在 Page 结构体中的 Offset 得到 Page 的地址
list_del(le); // 删掉这个节点
*ptr_page = page; // 将这一页地址存到 ptr_page 中 给 调用本函数的函数使用
return 0;
}

依据算法写出代码

image-20200410040231378

1
2
3
!(*ptep & PTE_A) && !(*ptep & PTE_D)  //没被访问过 也没被修改过
(*ptep & PTE_A) ^ (*ptep & PTE_D) //被访问过 但没被修改过 或 没被访问过 但被修改过
(*ptep & PTE_A) && (*ptep & PTE_D) //被访问过 且 被修改过

根据算法得出页置换过程

image-20200410174526396

将其应用于swap_out_victim的修改

注意:对于每次类似于*(unsigned char *)0x1000 = 0x0a;的赋值过程,对应页面的Dirty位和Access位均置1

swap_extended_clock.c修改代码,主要是对struct swap_manager,swap_out_victim函数,check_swap函数进行修改,并对各函数进行更名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#include <defs.h>
#include <x86.h>
#include <stdio.h>
#include <string.h>
#include <swap.h>
#include <swap_extended_clock.h>
#include <list.h>

/* [wikipedia]The simplest Page Replacement Algorithm(PRA) is a FIFO algorithm. The first-in, first-out
* page replacement algorithm is a low-overhead algorithm that requires little book-keeping on
* the part of the operating system. The idea is obvious from the name - the operating system
* keeps track of all the pages in memory in a queue, with the most recent arrival at the back,
* and the earliest arrival in front. When a page needs to be replaced, the page at the front
* of the queue (the oldest page) is selected. While FIFO is cheap and intuitive, it performs
* poorly in practical application. Thus, it is rarely used in its unmodified form. This
* algorithm experiences Belady's anomaly.
*
* Details of FIFO PRA
* (1) Prepare: In order to implement FIFO PRA, we should manage all swappable pages, so we can
* link these pages into pra_list_head according the time order. At first you should
* be familiar to the struct list in list.h. struct list is a simple doubly linked list
* implementation. You should know howto USE: list_init, list_add(list_add_after),
* list_add_before, list_del, list_next, list_prev. Another tricky method is to transform
* a general list struct to a special struct (such as struct page). You can find some MACRO:
* le2page (in memlayout.h), (in future labs: le2vma (in vmm.h), le2proc (in proc.h),etc.
*/

list_entry_t pra_list_head;
/*
* (2) _fifo_init_mm: init pra_list_head and let mm->sm_priv point to the addr of pra_list_head.
* Now, From the memory control struct mm_struct, we can access FIFO PRA
*/
static int
_extended_clock_init_mm(struct mm_struct *mm)
{
list_init(&pra_list_head);
mm->sm_priv = &pra_list_head;
//cprintf(" mm->sm_priv %x in fifo_init_mm\n",mm->sm_priv);
return 0;
}
/*
* (3)_fifo_map_swappable: According FIFO PRA, we should link the most recent arrival page at the back of pra_list_head qeueue
*/
static int
_extended_clock_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);
//record the page access situlation
/*LAB3 EXERCISE 2: YOUR CODE*/
//(1)link the most recent arrival page at the back of the pra_list_head qeueue.
list_add(head, entry);
//初始访问位和修改位均置0
struct Page *ptr = le2page(entry, pra_page_link);
pte_t *pte = get_pte(mm -> pgdir, ptr -> pra_vaddr, 0);
*pte &= ~PTE_D;
*pte &= ~PTE_A;
return 0;
}
/*
* (4)_fifo_swap_out_victim: According FIFO PRA, we should unlink the earliest arrival page in front of pra_list_head qeueue,
* then set the addr of addr of this page to ptr_page.
*/

static int
_extended_clock_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;
assert(head != le);

int i;
for (i = 0; i < 3; i ++) {

while (le != head) {
struct Page *page = le2page(le, pra_page_link);
pte_t *ptep = get_pte(mm->pgdir, page->pra_vaddr, 0);

if (!(*ptep & PTE_A) && !(*ptep & PTE_D)) {// (PTE_D, PTE_A) = (0, 0)
list_del(le);
*ptr_page = page;
return 0;
}
if ( (*ptep & PTE_A) ^ (*ptep & PTE_D)) {// (PTE_D, PTE_A) = (1, 0)||(0, 1)
*ptep &= ~PTE_D;
*ptep &= ~PTE_A;
}
if ( (*ptep & PTE_A) && (*ptep & PTE_D)) {// (PTE_D, PTE_A) = (1,1)
*ptep &= ~PTE_A;
}

le = le->prev;
}

le = le->prev;
}
}

static int
_extended_clock_check_swap(void) {
cprintf("write Virt Page c in extended_clock_check_swap\n");
*(unsigned char *)0x3000 = 0x0c;
assert(pgfault_num==4);
cprintf("write Virt Page a in extended_clock_check_swap\n");
*(unsigned char *)0x1000 = 0x0a;
assert(pgfault_num==4);
cprintf("write Virt Page d in extended_clock_check_swap\n");
*(unsigned char *)0x4000 = 0x0d;
assert(pgfault_num==4);
cprintf("write Virt Page b in extended_clock_check_swap\n");
*(unsigned char *)0x2000 = 0x0b;
assert(pgfault_num==4);
cprintf("write Virt Page e in extended_clock_check_swap\n");
*(unsigned char *)0x5000 = 0x0e;
assert(pgfault_num==5);
cprintf("write Virt Page b in extended_clock_check_swap\n");
*(unsigned char *)0x2000 = 0x0b;
assert(pgfault_num==5);
cprintf("write Virt Page a in extended_clock_check_swap\n");
*(unsigned char *)0x1000 = 0x0a;
assert(pgfault_num==6);
cprintf("write Virt Page b in extended_clock_check_swap\n");
*(unsigned char *)0x2000 = 0x0b;
assert(pgfault_num==6);
cprintf("write Virt Page c in extended_clock_check_swap\n");
*(unsigned char *)0x3000 = 0x0c;
assert(pgfault_num==7);
cprintf("write Virt Page d in extended_clock_check_swap\n");
*(unsigned char *)0x4000 = 0x0d;
assert(pgfault_num==8);
return 0;
}

static int
_extended_clock_init(void)
{
return 0;
}

static int
_extended_clock_set_unswappable(struct mm_struct *mm, uintptr_t addr)
{
return 0;
}

static int
_extended_clock_tick_event(struct mm_struct *mm)
{ return 0; }


struct swap_manager swap_manager_extended_clock =
{
.name = "extended clock swap manager",
.init = &_extended_clock_init,
.init_mm = &_extended_clock_init_mm,
.tick_event = &_extended_clock_tick_event,
.map_swappable = &_extended_clock_map_swappable,
.set_unswappable = &_extended_clock_set_unswappable,
.swap_out_victim = &_extended_clock_swap_out_victim,
.check_swap = &_extended_clock_check_swap,
};

swap_clock.h

1
2
3
4
5
6
7
#ifndef __KERN_MM_SWAP_FIFO_H__
#define __KERN_MM_SWAP_FIFO_H__

#include <swap.h>
extern struct swap_manager swap_manager_extended_clock;

#endif

为了使用extended clock算法,对swap.c进行修改

1
2
3
4
5
6
7
8
9
10
11
- #include <swap_fifo.h>
+ #include <swap_extended_clock.h>
...
int
swap_init(void)
{
...
- sm = &swap_manager_fifo;
+ sm = &swap_manager_clock;
...
}

重新 make qemu

image-20200410145255073

成功运行

swap_check()函数通过

另外,此实验的页面访问权限为内核态,对于lab1的challenge 2,如果按3切换到用户态后会出现缺页中断,因为用户态不能访问只能内核态可以访问的页表,即无法访问内存。用户态没有权限访问虚拟地址对应的物理页面;该虚拟地址也不在合法的虚拟地址空间里,由于只设置了很小的合法虚拟地址空间。在内核态下,由于这部分虚拟地址对应的是操作系统内核代码,在启动虚拟内存管理之前就,已经装入了内存,建立好页的映射关系,是不会发生缺页中断的,不会被换出的。

实验总结

​ 本次实验是关于虚拟内存管理,主要有Page Fault异常处理页替换算法的相关实验,了解了相关操作。本次实验题目量较少,需要查找分析代码里宏定义和函数定义来完成对已有代码的理解和要完成代码的使用。通过本次实验我深入理解了虚拟内存管理机制,对课程内容有了更深的理解。