前言
通过C语言实现先进先出FIFO、最佳置换OPI和最近最久未使用LRU页面置换算法的实现方法,加深对虚拟内存页面置换概念的理解。
算法流程
代码实现
废话不多说,下面直接上代码。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdbool.h>
#include <corecrt_malloc.h>
#define MaxNumber 100
// 函数原型
void FIFO(int PageOrder[], int PageNum, int BlockNum);
void OPT(int PageOrder[], int PageNum, int BlockNum);
void LRU(int PageOrder[], int PageNum, int BlockNum);
int main() {
int PageOrder[MaxNumber]; // 页面序列
int PageNum, BlockNum; // 页面个数,内存中的块数
// 输入
printf("请输入页面个数:");
scanf("%d", &PageNum);
printf("请输入页面序列:");
for (int i = 0; i < PageNum; i++) {
scanf("%d", &PageOrder[i]);
}
printf("请输入内存中的块数:");
scanf("%d", &BlockNum);
// 模拟FIFO页面置换
printf("\nFIFO页面置换算法:\n");
FIFO(PageOrder, PageNum, BlockNum);
// 模拟OPT页面置换
printf("\nOPT页面置换算法:\n");
OPT(PageOrder, PageNum, BlockNum);
// 模拟LRU页面置换
printf("\nLRU页面置换算法:\n");
LRU(PageOrder, PageNum, BlockNum);
return 0;
}
// 函数:模拟FIFO页面置换
void FIFO(int PageOrder[], int PageNum, int BlockNum) {
int* memory = (int*)malloc(BlockNum * sizeof(int));
int pageFaults = 0;
for (int i = 0; i < BlockNum; i++) {
memory[i] = -1; // 将内存块初始化为-1(表示空)
}
int index = 0; // FIFO置换的索引
for (int i = 0; i < PageNum; i++) {
bool found = false;
for (int j = 0; j < BlockNum; j++) {
if (memory[j] == PageOrder[i]) {
found = true;
break;
}
}
if (!found) {
pageFaults++;
memory[index] = PageOrder[i];
index = (index + 1) % BlockNum; // 循环递增索引
printf("页面 %d 发生缺页。内存内容:", PageOrder[i]);
for (int k = 0; k < BlockNum; k++) {
printf("%d ", memory[k]);
}
printf("\n");
}
}
double pageFaultRate = (double)pageFaults / PageNum * 100;
printf("缺页次数:%d\n缺页率:%.2f%%\n", pageFaults, pageFaultRate);
}
// 函数:模拟OPT页面置换
void OPT(int PageOrder[], int PageNum, int BlockNum)
{
int* memory = (int*)malloc(BlockNum * sizeof(int));
int pageFaults = 0;
for (int i = 0; i < BlockNum; i++) {
memory[i] = -1; // 将内存块初始化为-1(表示空)
}
for (int i = 0; i < PageNum; i++) {
bool found = false;
for (int j = 0; j < BlockNum; j++) {
if (memory[j] == PageOrder[i]) {
found = true;
break;
}
}
if (!found) {
pageFaults++;
// 找出未来最长时间不被访问的页面
int farthest = -1;
int replaceIndex;
for (int j = 0; j < BlockNum; j++) {
int farthestIndex = -1;
for (int k = i + 1; k < PageNum; k++) {
if (memory[j] == PageOrder[k]) {
farthestIndex = k;
break;
}
}
if (farthestIndex == -1) {
replaceIndex = j;
break;
}
if (farthestIndex > farthest) {
farthest = farthestIndex;
replaceIndex = j;
}
}
memory[replaceIndex] = PageOrder[i];
printf("页面 %d 发生缺页。内存内容:", PageOrder[i]);
for (int k = 0; k < BlockNum; k++) {
printf("%d ", memory[k]);
}
printf("\n");
}
}
double pageFaultRate = (double)pageFaults / PageNum * 100;
printf("缺页次数:%d\n缺页率:%.2f%%\n", pageFaults, pageFaultRate);
}
// 函数:模拟LRU页面置换
void LRU(int PageOrder[], int PageNum, int BlockNum)
{
int* memory = (int*)malloc(BlockNum * sizeof(int));
int pageFaults = 0;
int* counter = (int*)malloc(BlockNum * sizeof(int));
for (int i = 0; i < BlockNum; i++) {
memory[i] = -1; // 将内存块初始化为-1(表示空)
counter[i] = 0; // 初始化计数器为0
}
int time = 0;
for (int i = 0; i < PageNum; i++) {
bool found = false;
for (int j = 0; j < BlockNum; j++) {
if (memory[j] == PageOrder[i]) {
found = true;
counter[j] = time++; // 更新页面的访问时间
break;
}
}
if (!found) {
pageFaults++;
int replaceIndex = 0;
int minCounter = counter[0];
for (int j = 1; j < BlockNum; j++) {
if (counter[j] < minCounter) {
minCounter = counter[j];
replaceIndex = j;
}
}
memory[replaceIndex] = PageOrder[i];
counter[replaceIndex] = time++;
printf("页面 %d 发生缺页。内存内容:", PageOrder[i]);
for (int k = 0; k < BlockNum; k++) {
printf("%d ", memory[k]);
}
printf("\n");
}
}
double pageFaultRate = (double)pageFaults / PageNum * 100;
printf("缺页次数:%d\n缺页率:%.2f%%\n", pageFaults, pageFaultRate);
}
常见问题
在某些情况下,如果数组的大小需要在运行时动态决定,可以使用指针和动态内存分配(如 `malloc` 或 `new`)来实现。
但是,一旦分配了数组的大小,就不能再改变它。这是因为数组的大小必须是编译时已知的,以便正确地寻址和访问数组元素。
通过以下形式定义时,会出现“表达式必须含有常量值”的问题。
int memory[BlockNum];
此时我们改为动态分配内存。
int* memory = (int*)malloc(BlockNum * sizeof(int))
关于为什么使用动态内存分配就可以?
使用动态内存分配可以解决静态数组的一些局限性,主要体现在以下几个方面:
1.灵活性:动态内存分配允许你在程序运行时根据需要分配内存,而不是在编译时。这意味着你可以根据用户的输入或其他运行时条件来决定分配的内存大小,从而使程序更加灵活。
2.效率:如果你事先知道数据的大小,使用动态内存分配可以避免为不必要的空间分配内存,从而节省内存资源。对于例如数组或链表这样的数据结构,动态分配可以确保它们只使用实际需要的空间。
3.避免内存浪费:静态数组的大小是固定的,如果分配的内存超过了实际需要,那么这部分内存就浪费了。动态内存分配可以根据实际需要分配和释放内存,从而避免这种浪费。
4.跨函数使用:静态数组的生命周期通常局限于函数调用栈中,这意味着一旦函数调用结束,静态数组占用的内存就会被释放。动态内存分配可以在程序的不同部分使用,跨多个函数调用保持数据。
5.避免数组越界:静态数组的大小在声明时就已确定,如果尝试访问超出这个大小的索引,会导致数组越界,从而产生未定义行为。动态内存分配可以动态扩展数组的大小,以防止越界。
内存管理:动态内存分配需要使用特定的函数(如C中的`malloc`和`free`,C++中的`new`和`delete`)来请求和释放内存。这使得内存管理更加明确,有助于避免内存泄漏和野指针的问题。
总之,动态内存分配提供了一种在程序运行时分配和释放内存的方法,这比编译时固定的静态内存分配更加灵活、高效,并且有助于避免内存浪费和管理问题。