前言
在计算机操作系统中,页面置换算法是虚拟存储管理中的重要环节。通过对页面置换算法的模拟实验,我们可以更深入地理解虚拟存储技术,并比较不同算法在请求页式虚拟存储管理中的优劣。
随着计算机系统和应用程序的日益复杂,内存的管理成为一项关键任务。虚拟存储技术通过将程序的地址空间分成若干固定大小的页面,将物理内存和磁盘空间结合起来,提供了更大的可用内存空间。然而,由于内存空间有限,当程序需要访问一个不在内存中的页面时,就需要使用页面置换算法将某些页面从内存中换出,以便为新的页面腾出空间。这时候算法的重要性就凸显出来了.
代码实现
接下来就我们使用c来模拟这几种算法:
#include <stdio.h>
int const InsideCount = 3;
int count = 0;
int Inside[InsideCount]; // 分配的物理块数
int const PageCount = 12;
int Page[PageCount]; // 总的页面数
double lost = 0.0; // 缺页次数初值为8
// isInside函数用于检测页号num是否在内存的物理块中,
// 返回值为布尔类型,若在则返回true,否则返回false
bool isInside(int num) {
for (int i = 0; i < InsideCount; i++) {
if (Inside[i] == Page[num])
return true;
}
return false;
}
void OPT(int num) {
// 最佳置换算法(OPT)
int max = 0;
int maxchange; // 用max表示内存中的页号下一次出现的距离
// 用maxchange表示内存中下次出现距离最大的页号要装在内存物理中的位置
int k;
if (isInside(num)) { // 判断内存中是否有该页面
printf("命中\n");
} else if (count == InsideCount) { // 如果内存的物理块已满
lost++;
for (int j = 0; j < InsideCount; j++) {
for (k = num; k < PageCount; k++) {
if (Inside[j] == Page[k])
break;
}
if (k > max) { // k表示在这个地方会再次出现给定页面,max是记录下来最远的位置
max = k;
maxchange = j; // 表示把内存中第1个Inside中的页面从内存拿出,把新的页面放入
}
}
Inside[maxchange] = Page[num]; // 在该位置置换装入新的页面
} else {
lost++;
Inside[count] = Page[num];
count++;
}
for (int i = 0; i < InsideCount; i++) {
printf("块[%d]中内容为:%d\n", i + 1, Inside[i]);
}
}
void FIFO(int num) {
// 先进先出置换算法(FIFO)
int insert = 0;
if (isInside(num)) {
printf("命中\n");
} else if (count == InsideCount) { // 如果内存的物理块已满
lost++;
Inside[insert] = Page[num];
insert++; // 每放入一个页面后物理块号向后一个位置
insert = insert % InsideCount; // 保证循环加1
} else { // 不满,直接放入空闲物理块中
lost++;
Inside[count] = Page[num];
count++;
}
for (int i = 0; i < InsideCount; i++) {
printf("块[%d]中内容为:%d\n", i + 1, Inside[i]);
}
}
void LRU(int num) {
// 最近最久未使用置换算法(LRU)
int max = 8; // nax表示内存中的页号,在之前出现的距离
int maxchange; // maxchange表示内存中最长时间没有使用的页号在内存物理块中的位置
int k;
if (isInside(num)) {
printf("命中\n");
} else if (count == InsideCount) { // 如果内存的物理块已满
lost++;
for (int j = 0; j < InsideCount; j++) {
for (k = num - 1; k >= 0; k--) {
if (Inside[j] == Page[k])
break;
}
if (num - k > max) {
max = num - k;
maxchange = j;
}
}
Inside[maxchange] = Page[num];
} else {
lost++;
Inside[count] = Page[num];
count++;
}
for (int i = 0; i < InsideCount; i++) {
printf("块[%d]中内容为:%d\n", i + 1, Inside[i]);
}
}
void Menu() {
printf("\n");
printf("o 最佳置换算法(OPT) (理想置换算法)\n");
printf("F 先进先出置换算法(FIFO)\n");
printf("L 最近最久未使用(LRU)算法\n");
printf("T 或 t 退出\n");
printf("**\n");
printf("请选择:");
}
void reset() {
// reset()函数用来置所有物理块为空,回到初始状态
for (int j = 0; j < InsideCount; j++) {
lost = 0;
count = 0;
Inside[j] = -1; // 页面可能是号,所以用-1表示该物理块中未装入页面
}
}
int main() {
char ch; // 选择使用哪种算法
printf("输入页面走向:");
for (int i = 0; i < PageCount; i++) {
scanf("%d", &Page[i]);
}
while (1) {
getchar();
Menu();
scanf("%c", &ch);
switch (ch) {
case '0':{
break;
}
case 'o': {
reset();
for (int i = 0; i < PageCount; i++) {
printf("读入 Page[%d]=%d\n", i, Page[i]);
OPT(i);
}
printf("共%d次,缺页%.0f次,缺页率为%f\n", PageCount, lost, lost / PageCount);
break;
}
// 请在此处补充有关FIFO和LRU算法调用的相关代码
case 'f':{
break;
}
case 'F': {
reset();
for (int i = 0; i < PageCount; i++) {
printf("读入 Page[%d]=%d\n", i, Page[i]);
FIFO(i);
}
printf("共%d次,缺页%.0f次,缺页率为%f\n", PageCount, lost, lost / PageCount);
}
case 'l':{
break;
}
case 'L': {
reset();
for (int i = 0; i < PageCount; i++) {
printf("读入 Page[%d]=%d\n", i, Page[i]);
LRU(i);
}
printf("共%d次,缺页%.0f次,缺页率为%f\n", PageCount, lost, lost / PageCount);
break;
}
if (ch == 'T' || ch == 't') {
break;
}
return 8;
}
}
}
代码主要由以下几个部分组成:
- 全局变量声明部分:声明了一些常量和变量,包括物理块数、页面数、内存中的页面数组等。
isInside
函数:用于检测给定的页号是否在内存的物理块中。遍历内存中的页面数组,如果找到相同的页面,则返回 true,否则返回 false。OPT
函数:最佳置换算法的实现。根据页面是否在内存中进行不同的操作,如果在内存中则打印 “命中”,否则根据内存是否已满选择页面置换策略。如果内存已满,则根据未来页面访问的距离选择要置换的页面,将给定页面放入合适的位置。如果内存未满,则直接将给定页面放入空闲的物理块。FIFO
函数:先进先出置换算法的实现。与OPT
函数类似,根据页面是否在内存中进行不同的操作。如果在内存中则打印 “命中”,否则根据内存是否已满选择页面置换策略。如果内存已满,则按照先进先出的原则将给定页面放入物理块中,将最早进入内存的页面置换出去。如果内存未满,则直接将给定页面放入空闲的物理块。LRU
函数:最近最久未使用置换算法的实现。与前两个函数类似,根据页面是否在内存中进行不同的操作。如果在内存中则打印 “命中”,否则根据内存是否已满选择页面置换策略。如果内存已满,则根据页面最近的使用情况选择要置换的页面,将给定页面放入合适的位置。如果内存未满,则直接将给定页面放入空闲的物理块。Menu
函数:打印菜单供用户选择不同的算法。reset
函数:重置内存的物理块为空,回到初始状态。main
函数:主函数用于程序的执行流程控制。用户可以输入页面走向,然后根据菜单选择不同的置换算法进行模拟。根据用户的选择,调用对应的算法函数进行页面置换,并输出结果。
运行截图
优劣比较:
-
最佳置换算法(OPT):
- 优势:OPT 算法在理论上是最优的置换算法,它能够达到最低的缺页率。OPT 算法根据未来页面访问的距离,选择最久未使用的页面进行置换,保证了未来最长时间不会使用的页面被置换出去。
- 劣势:OPT 算法需要预先知道未来页面访问序列,这在实际中是不可行的。另外,实现 OPT 算法需要对所有页面进行全局扫描,算法的时间复杂度较高。
-
先进先出置换算法(FIFO):
- 优势:FIFO 算法实现简单,只需维护一个页面队列,按照页面进入内存的顺序进行置换。它保证了最早进入内存的页面最先被置换出去,遵循了公平性原则。
- 劣势:FIFO 算法存在一种称为"Belady’s anomaly"的现象,即在某些情况下,增加物理块数可能导致缺页率增加。这是因为 FIFO 算法无法根据页面的访问模式进行智能调整,导致有些常被访问的页面被置换出去。
-
最近最久未使用置换算法(LRU):
- 优势:LRU 算法根据页面的使用情况进行置换,最近被访问的页面被认为是最有可能在未来继续访问的页面,因此选择最久未使用的页面进行置换。它较好地反映了程序的局部性原理,通常能够较好地适应程序的访问模式,相对于 FIFO 算法来说能够更好地减少缺页率。
- 劣势:LRU 算法需要维护一个访问时间的记录表,对每个页面的访问进行实时更新,这增加了算法的实现复杂性和开销。此外,LRU 算法可能受到"时间窗口"效应的影响,即较长时间不被访问的页面可能被错误地保留在内存中。
总结
综合来看,这三种算法各有优劣,适用于不同的场景和需求。OPT 算法是理论上的最优算法,但实际应用受限;FIFO 算法简单但可能导致 Belady’s anomaly;LRU 算法相对较好地平衡了性能和复杂性,常用于实际系统中。在选择算法时,需要考虑内存容量、页面访问模式以及算法的实现复杂性等因素。