一 实验目的
(1)理解虚拟内存管理的原理和技术。
(2)理解不同页面替换算法的工作原理和性能特征。
(3)掌握并实现请求页式存储管理中常见的页面替换算法。
二 实验内容
2.1 任务一
本实验要求编程模拟请求页式存储管理系统中的进程运行情况。一个进程被分配了若干个物理块,运行时按照给定的页面访问序列进行操作。在发生缺页中断时,将使用以下页面替换算法进行页面置换:
1.先进先出算法(FIFO)
2.最近最少使用算法(LRU)
3.CLOCK 算法(即第二次机会算法)
假设该进程被分配了 4 个内存物理块,按照顺序访问 10 个页面,具体访问序列为:3、6、2、1、3、3、7、6、1、4。要求编写 C 程序,分别实现上述三种页面调度算法,程序输入包括内存物理块数量、页面访问序列;输出包括当前驻留在内存区的页面,缺页次数,页面置换算法的命中率(即页面命中次数/访问页面数)。
2.2 任务二
除提供的示例外,可以自行修改物理块分配个数以及访问序列,检验实现的页面置换算法的有效性和稳定性。
三 实验过程及结果
(1)编程模拟请求页式存储管理系统中的进程运行情况。一个进程被分配了若干个物理块,运行时按照给定的页面访问序列进行操作。在发生缺页中断时,将使用三种不同的页面替换算法进行页面置换。
【实验步骤】:
1. 在用户主目录下创建一个demo目录,存放本次实验的代码文件,进入demo目录
2. 在demo目录下创建test.c文件,编写代码
#include <stdio.h>
#include <stdlib.h>
// 页面结构
typedef struct {
int page_number;
int referenced; // 用于LRU算法
} Page;
// 页面调度算法类型
typedef enum {
FIFO,
LRU,
CLOCK
} PageReplacementAlgorithm;
// FIFO算法
void fifo(Page pages[], int page_sequence[], int page_count, int max_frames);
// LRU算法
void lru(Page pages[], int page_sequence[], int page_count, int max_frames);
// CLOCK算法
void clock_algorithm(Page pages[], int page_sequence[], int page_count, int max_frames);
// 打印当前内存状态
void print_memory_state(Page pages[], int frame_count);
int main() {
int max_frames;
printf("Enter the number of physical memory blocks: ");
scanf("%d", &max_frames);
int page_count;
printf("Enter the number of pages in the sequence: ");
scanf("%d", &page_count);
int *page_sequence = (int *)malloc(page_count * sizeof(int));
printf("Enter the page sequence: ");
for (int i = 0; i < page_count; i++) {
scanf("%d", &page_sequence[i]);
}
Page *pages = (Page *)malloc(max_frames * sizeof(Page));
printf("\nFIFO Algorithm:\n");
fifo(pages, page_sequence, page_count, max_frames);
printf("\nLRU Algorithm:\n");
lru(pages, page_sequence, page_count, max_frames);
printf("\nCLOCK Algorithm:\n");
clock_algorithm(pages, page_sequence, page_count, max_frames);
free(page_sequence);
free(pages);
return 0;
}
void fifo(Page pages[], int page_sequence[], int page_count, int max_frames) {
int frame_count = 0;
int page_faults = 0;
int next_frame_index = 0;
for (int i = 0; i < page_count; i++) {
int page_number = page_sequence[i];
// 检查页面是否已经在内存中
int page_found = 0;
for (int j = 0; j < frame_count; j++) {
if (pages[j].page_number == page_number) {
page_found = 1;
break;
}
}
// 页面不在内存中,发生缺页中断
if (!page_found) {
// 检查内存是否已满
if (frame_count < max_frames) {
// 内存未满,直接加入
pages[next_frame_index].page_number = page_number;
next_frame_index = (next_frame_index + 1) % max_frames;
frame_count++;
} else {
// 内存已满,使用FIFO算法置换页面
pages[next_frame_index].page_number = page_number;
next_frame_index = (next_frame_index + 1) % max_frames;
}
page_faults++;
}
// 打印当前内存状态
print_memory_state(pages, frame_count);
}
// 打印结果
printf("Page Faults: %d\n", page_faults);
printf("Page Hit Ratio: %.2f%%\n", (1 - (float)page_faults / page_count) * 100);
}
void lru(Page pages[], int page_sequence[], int page_count, int max_frames) {
int frame_count = 0;
int page_faults = 0;
int next_frame_index = 0;
for (int i = 0; i < page_count; i++) {
int page_number = page_sequence[i];
// 检查页面是否已经在内存中
int page_found = 0;
for (int j = 0; j < frame_count; j++) {
if (pages[j].page_number == page_number) {
page_found = 1;
// 更新页面的referenced字段
pages[j].referenced = i;
break;
}
}
// 页面不在内存中,发生缺页中断
if (!page_found) {
// 检查内存是否已满
if (frame_count < max_frames) {
// 内存未满,直接加入
pages[next_frame_index].page_number = page_number;
pages[next_frame_index].referenced = i;
next_frame_index = (next_frame_index + 1) % max_frames;
frame_count++;
} else {
// 内存已满,使用LRU算法置换页面
int min_referenced = pages[0].referenced;
int min_index = 0;
for (int j = 1; j < frame_count; j++) {
if (pages[j].referenced < min_referenced) {
min_referenced = pages[j].referenced;
min_index = j;
}
}
pages[min_index].page_number = page_number;
pages[min_index].referenced = i;
}
page_faults++;
}
// 打印当前内存状态
print_memory_state(pages, frame_count);
}
// 打印结果
printf("Page Faults: %d\n", page_faults);
printf("Page Hit Ratio: %.2f%%\n", (1 - (float)page_faults / page_count) * 100);
}
// CLOCK算法
void clock_algorithm(Page pages[], int page_sequence[], int page_count, int max_frames) {
int frame_count = 0;
int page_faults = 0;
int hand = 0; // 时钟算法的指针
for (int i = 0; i < page_count; i++) {
int page_number = page_sequence[i];
// 检查页面是否已经在内存中
int page_found = 0;
for (int j = 0; j < frame_count; j++) {
if (pages[j].page_number == page_number) {
page_found = 1;
break;
}
}
// 页面不在内存中,发生缺页中断
if (!page_found) {
// 检查内存是否已满
if (frame_count < max_frames) {
// 内存未满,直接加入
pages[frame_count].page_number = page_number;
pages[frame_count].referenced = 1; // 新加入的页面设置referenced为1
frame_count++;
} else {
// 内存已满,使用CLOCK算法置换页面
while (1) {
if (pages[hand].referenced == 0) {
// 找到第一个未被引用的页面
pages[hand].page_number = page_number;
pages[hand].referenced = 1; // 替换的页面设置referenced为1
break;
} else {
// 将页面的referenced字段清零
pages[hand].referenced = 0;
}
hand = (hand + 1) % max_frames;
}
}
page_faults++;
} else {
// 页面已经在内存中,设置页面的referenced字段为1
for (int j = 0; j < frame_count; j++) {
if (pages[j].page_number == page_number) {
pages[j].referenced = 1;
break;
}
}
}
// 打印当前内存状态
print_memory_state(pages, frame_count);
}
// 打印结果
printf("Page Faults: %d\n", page_faults);
printf("Page Hit Ratio: %.2f%%\n", (1 - (float)page_faults / page_count) * 100);
}
void print_memory_state(Page pages[], int frame_count) {
printf("Memory State: ");
for (int i = 0; i < frame_count; i++) {
printf("%d ", pages[i].page_number);
}
printf("\n");
}
【代码分析】:这份C代码旨在模拟三种页面置换算法:FIFO(先进先出)、LRU(最近最少使用)和CLOCK(时钟)。首先,通过定义 `Page` 结构来表示页面,其中包括页面编号和用于LRU算法的 `referenced` 字段。
每个算法都有一个对应的函数:
1)FIFO算法 (`fifo` 函数):
- 该函数接受四个参数:`Page pages[]` 用于表示内存中的页面,`page_sequence[]` 表示页面访问序列,`page_count` 表示页面访问序列的长度,`max_frames` 表示内存物理块的数量。
- 模拟了每一步的内存状态,FIFO算法按照先进先出的原则置换页面。在给定的页面访问序列中,每当发生缺页,都会将最早进入内存的页面替换出去。最后,输出总的缺页次数和页面命中率。
2)LRU算法 (`lru` 函数):
- 该函数同样接受四个参数:`Page pages[]` 表示内存中的页面,`page_sequence[]` 表示页面访问序列,`page_count` 表示页面访问序列的长度,`max_frames` 表示内存物理块的数量。
- 模拟了每一步的内存状态,LRU算法根据最近最少使用的原则进行页面置换。每次发生缺页时,都会替换最久未被使用的页面。最后,输出总的缺页次数和页面命中率。
3)CLOCK算法 (`clock_algorithm` 函数):
- 该函数同样接受四个参数:`Page pages[]` 表示内存中的页面,`page_sequence[]` 表示页面访问序列,`page_count` 表示页面访问序列的长度,`max_frames` 表示内存物理块的数量。
-模拟了每一步的内存状态,CLOCK算法是一种时钟算法,根据页面的访问位进行页面置换。在发生缺页时,逐个检查页面的访问位,选择第一个访问位为0的页面进行替换。最后,输出总的缺页次数和页面命中率。
用户可以通过输入指定内存物理块数量和页面访问序列。整个程序的逻辑结构清晰,每个算法的实现都以独立的函数形式存在。
3. 编译test.c文件,运行a.out文件,输入内存物理块数量、访问页数和页面访问序列,输出三种页面调度算法的当前驻留在内存区的页面,缺页次数以及命中率。
[结果分析]:三种页面调度算法的运行结果如下:
FIFO 最终内存页面:7 4 2 1 缺页次数:6次 命中率:40.00%
LRU 最终内存页面:4 7 6 1 缺页次数:7次 命中率:30.00%
CLOCK 最终内存页面:7 6 4 1 缺页次数:6次 命中率:40.00%
综合考虑,对于相同的输入序列,不同的页面置换算法展现出了各自独特的性能特征。每个算法都具有一些优势和局限,而其选择通常依赖于具体的应用场景和性能要求。
在这个具体的实例中,FIFO和CLOCK算法表现相似,这是因为它们都属于相对简单的置换策略。FIFO按照最早进入内存的页面进行替换,而CLOCK算法则通过维护一个类似时钟的访问位循环指针,以判断页面是否被访问。这些算法的简单性使其在某些场景下表现良好,但在某些复杂的访问模式下可能会产生不理想的结果。
相比之下,LRU算法则考虑了页面的使用频率,选择最近最少被使用的页面进行替换。然而,由于需要追踪每个页面的使用历史,LRU算法的实现相对复杂,可能会导致较高的计算成本。在这个例子中,LRU算法的性能稍逊,可能是由于其较为复杂的维护机制导致了额外的开销。
因此,在选择页面置换算法时,需要综合考虑应用场景的特点、系统资源的约束以及对性能的具体需求。不同的算法可能在不同的情境下表现更为优越。
(2)自行修改物理块分配个数以及访问序列,检验实现的页面置换算法的有效性和稳定性。
四 实验总结
(1) FIFO算法分析
优点:简单易实现。FIFO算法是一种直观、简单的页面置换策略,容易理解和实现。
缺点:FIFO容易出现"Belady现象",即增加物理块数量反而可能导致缺页率增加。这使得FIFO在某些情况下不够灵活适应不同访问模式。
(2)LRU算法分析
优点:LRU算法考虑了页面的使用频率,更加智能地选择置换页面,适应不同访问模式。
缺点:实现复杂。LRU的实现相对复杂,需要维护每个页面的使用历史,可能导致较高的计算开销。
(3)CLOCK算法分析
优点:介于简单和智能之间。CLOCK算法介于FIFO和LRU之间,考虑了页面的使用情况,但相对实现较为简单。
缺点:对某些模式不敏感。CLOCK算法可能对某些访问模式不敏感,因为它主要关注页面的访问位而非实际的使用频率。
(4)自定义测试
验证适应性:通过自定义测试,观察了算法对不同内存块数量和访问序列的适应性。
性能不一定提升:在某些情况下,增加物理块数量并不总是导致性能提升。这表明算法性能与具体场景和访问模式有关。
(5)总体评价
选择算法需权衡:页面置换算法的选择需要权衡算法的复杂性和性能表现,具体应用场景会影响算法的优劣。
实验提供参考:实验结果为理解和选择适当的页面置换算法提供了实际的参考,对比了不同算法在具体场景下的表现。