前言
提示:本次实验在Linux(Ubuntu)中运行,程序中读取的文件需放在与c文件同一个文件夹中,或者自行在代码中修改路径。
一、实验目的
通过模拟实现请求页式存储管理的几种基本页面置换算法,了解虚拟存储技术的特点,掌握虚拟存储请求页式存储管理中几种基本页面置换算法的基本思想和实现过程,并比较它们的效率。
二、实验内容和要求
(1)访问页面号以数字为模拟,输入数据从文本文件中读出,数字间以空格为间隔,假设有50个页面数据。
(2)编写程序分别使用下述四个算法计算访问命中率:最佳淘汰算法(OPT),先进先出的算法(FIFO),最近最久未使用算法(LRU),最不经常使用算法(LFU)。
(3)输出访问顺序和命中率结果。
三、实验原理
页面置换算法
当CPU接收到缺页中断信号,中断处理程序先保存现场,分析中断原因,转入缺页中断处理程序。该程序通过查找页表,得到该页所在外存的物理块号。如果此时内存未满,能容纳新页,则启动磁盘I/O将所缺之页调入内存,然后修改页表。如果内存已满,则须按某种置换算法从内存中选出一页准备换出,是否重新写盘由页表的修改位决定,然后将缺页调入,修改页表。利用修改后的页表,去形成所要访问数据的物理地址,再去访问内存数据。整个页面的调入过程对用户是透明的。
四、实验程序
c语言代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
typedef int bool;
#define true 1
#define false 0
int pageNum; //页面数
int memory_blockNum; //内存块数
int *Page; //页面访问列表
int **show; //用于展示整个页面置换过程
int *cur; //记录当前各内存块情况
int missNum; //缺页次数
int count; //记录内存中的页面数量
int temp; //记录内存块填满时Page遍历的位置
//判断当前访问的页面是否在内存中
bool InMemory(int *cur, int curPage) {
for(int i = 0; i < memory_blockNum; i++) {
if(cur[i] == curPage) return true;
}
return false;
}
//初始化show数组
void init() {
//初始化show数组大小:
show = (int**)malloc((memory_blockNum + 2) * sizeof(int*));
for(int i = 0; i < memory_blockNum + 2; i++) {
show[i] = (int*)malloc(pageNum * sizeof(int));
}
for(int i = 0; i < pageNum; i++) {
show[0][i] = Page[i];
}
//show数组其余元素先初始化为-1
for(int i = 1; i < memory_blockNum + 2; i++) {
for(int j = 0; j < pageNum; j++) {
show[i][j] = -1;
}
}
missNum = 0; //缺页次数
cur = (int*)malloc(memory_blockNum * sizeof(int));
for(int i = 0; i < memory_blockNum; i++) {
cur[i] = -1; //初始化为-1
}
count = 0; //记录内存中的页面数量
temp = 0; //记录内存块填满时Page遍历的位置
while(count != memory_blockNum) { //还有空闲内存块时
if(InMemory(cur, Page[temp]) == false) {
cur[count++] = Page[temp];
missNum ++;
show[memory_blockNum + 1][temp] = 1; //设置缺页标记
}
//更新show数组
for(int k = 0; k < memory_blockNum; k++) {
show[k + 1][temp] = cur[k];
}
temp++;
}
}
//重载init()函数,因为LFU的初始化中涉及Hash数组的更新
//初始化show数组
void init2(int *Hash) {
//初始化show数组大小:
show = (int**)malloc((memory_blockNum + 2) * sizeof(int*));
for(int i = 0; i < memory_blockNum + 2; i++) {
show[i] = (int*)malloc(pageNum * sizeof(int));
}
for(int i = 0; i < pageNum; i++) {
show[0][i] = Page[i];
}
//show数组其余元素先初始化为-1
for(int i = 1; i < memory_blockNum + 2; i++) {
for(int j = 0; j < pageNum; j++) {
show[i][j] = -1;
}
}
missNum = 0; //缺页次数
cur = (int*)malloc(memory_blockNum * sizeof(int));
for(int i = 0; i < memory_blockNum; i++) {
cur[i] = -1; //初始化为-1
}
count = 0; //记录内存中的页面数量
temp = 0; //记录内存块填满时Page遍历的位置
while(count != memory_blockNum) { //还有空闲内存块时
if(InMemory(cur, Page[temp]) == false) {
cur[count++] = Page[temp];
missNum ++;
show[memory_blockNum + 1][temp] = 1; //设置缺页标记
}
//更新show数组
for(int k = 0; k < memory_blockNum; k++) {
show[k + 1][temp] = cur[k];
}
//更新Hash
Hash[Page[temp]]++;
temp++;
}
}
//打印show数组
void display(int **show) {
for(int i = 0; i < memory_blockNum + 1; i++) {
if(i == 0) {
printf("访问页面:");
}else {
printf("内存块%d: ", i - 1);
}
for(int j = 0; j < pageNum; j++) {
if(show[i][j] == -1)
printf(" ");
else
printf("%d ",show[i][j]);
}
printf("\n");
if(i == 0) {
for(int j = 0; j < (pageNum - 1) * 3 + 11; j++) {
printf("-");
}
printf("\n");
}
}
printf("是否缺页 ");
for(int j = 0; j < pageNum; j++) {
if(show[memory_blockNum + 1][j] == -1)
printf(" ");
else
printf("√ ");
}
printf("\n");
for(int j = 0; j < (pageNum - 1) * 3 + 11; j++) {
printf("-");
}
printf("\n");
}
//OPT算法,找到应被置换的页面,返回其所占的内存块号
//在之后要访问的页面列表中找到最长时间内不再被访问的页面
int OPT_find(int *cur, int *Page, int i) {
//拷贝一份cur数组
int *temp = (int*)malloc(memory_blockNum * sizeof(int));
for(int j = 0; j < memory_blockNum; j++) {
temp[j] = cur[j];
}
int count = 0;
while(count < memory_blockNum - 1 && i < pageNum) {
for(int j = 0; j < memory_blockNum; j++) {
if(temp[j] == Page[i]) {
temp[j] = -1; //表示该页面接下来将被访问
count++;
}
}
i++;
}
//此时temp中没被置为-1的就是最长时间内不再被访问的页面
for(int j = 0; j < memory_blockNum; j++) {
if(temp[j] != -1)
return j; //返回该页面占用的内存块号
}
return memory_blockNum - 1;
}
void OPT(int *Page) {
printf("\n最佳置换算法(OPT):\n");
init();
for(int i = temp; i < pageNum; i++) {
if(InMemory(cur, Page[i]) == false) {
missNum++;
cur[OPT_find(cur, Page, i)] = Page[i];
show[memory_blockNum + 1][i] = 1; //设置缺页标记
}
//更新show数组
for(int k = 0; k < memory_blockNum; k++) {
show[k + 1][i] = cur[k];
}
}
display(show);
double hit_rate = 1 - (double)missNum / (double)pageNum;
printf("最佳置换算法(OPT)命中率:%f\n", hit_rate);
}
int FIFO_find(int *cur, int *Page, int head) {
for(int i = 0; i < memory_blockNum; i++) {
if(cur[i] == Page[head]) {
return i;
}
}
return memory_blockNum - 1;
}
void FIFO(int *Page) {
printf("\n先进先出置换算法(FIFO):\n");
init();
int head = 0;
int rear = 0;
rear += memory_blockNum - 1;
int rear_pos = -1;
int hitNum = 1; //记录连续命中的次数
for(int i = temp; i < pageNum; i++) {
if(InMemory(cur, Page[i]) == false) {
missNum++;
cur[FIFO_find(cur, Page, head)] = Page[i];
show[memory_blockNum + 1][i] = 1; //设置缺页标记
if(head == rear_pos) {
head += (rear - (head + memory_blockNum - 1) + 1);
}else {
head++;
}
if(hitNum != 1) {
rear_pos = rear;
rear += hitNum;
}else {
rear++;
}
hitNum = 1;
}else {
hitNum++;
}
//更新show数组
for(int k = 0; k < memory_blockNum; k++) {
show[k + 1][i] = cur[k];
}
}
display(show);
double hit_rate = 1 - (double)missNum / (double)pageNum;
printf("先进先出置换算法(FIFO)命中率:%f\n", hit_rate);
}
//LRU算法,找到应被置换的页面,返回其所占的内存块号
//在之前访问过的页面列表中找到最近最久未使用的页面
int LRU_find(int *cur, int *Page, int i) {
//拷贝一份cur数组
int *temp = (int*)malloc(memory_blockNum * sizeof(int));
for(int j = 0; j < memory_blockNum; j++) {
temp[j] = cur[j];
}
int count = 0;
while(count < memory_blockNum - 1) {
for(int j = 0; j < memory_blockNum; j++) {
if(temp[j] == Page[i]) {
temp[j] = -1; //表示该页面接下来将被访问
count++;
}
}
i--;
}
//此时temp中没被置为-1的就是最长时间内不再被访问的页面
for(int j = 0; j < memory_blockNum; j++) {
if(temp[j] != -1)
return j; //返回该页面占用的内存块号
}
return memory_blockNum - 1;
}
void LRU(int *Page) {
printf("\n最近最久未使用置换算法(LRU):\n");
init();
for(int i = temp; i < pageNum; i++) {
if(InMemory(cur, Page[i]) == false) {
missNum++;
cur[LRU_find(cur, Page, i)] = Page[i];
show[memory_blockNum + 1][i] = 1; //设置缺页标记
}
//更新show数组
for(int k = 0; k < memory_blockNum; k++) {
show[k + 1][i] = cur[k];
}
}
display(show);
double hit_rate = 1 - (double)missNum / (double)pageNum;
printf("最近最久未使用置换算法(LRU)命中率:%f\n", hit_rate);
}
//最不经常使用算法(LFU)
int LFU_find(int *cur, int **cur2, int *Hash) {
/*
printf("cur2:");
for(int i = 0; i < memory_blockNum; i++) {
printf("%d(%d) ", cur2[0][i], cur2[1][i]);
}printf("\n");*/
int* cur_sort = (int*)malloc(2 * sizeof(int));
for(int i = 0; i < memory_blockNum; i++) {
for(int j = 0; j < memory_blockNum; j++) {
if(cur2[1][j] == i + 1) {
cur_sort[i] = cur2[0][j];
break;
}
}
}
/*
printf("cur_sort:");
for(int i = 0; i < memory_blockNum; i++) {
printf("%d ", cur_sort[i]);
}printf("\n");*/
int minIndex = 0; //记录排序后的cur_sort中访问次数最少的页面的下标
for(int i = 1; i < memory_blockNum; i++) {
if(Hash[cur_sort[i]] < Hash[cur_sort[minIndex]])
minIndex = i;
}
//printf("minIndex = %d\n", minIndex);
for(int i = 0; i < memory_blockNum; i++) {
if(cur[i] == cur_sort[minIndex]) {
free(cur_sort);
return i;
}
}
free(cur_sort);
return memory_blockNum - 1;
}
int cur2_find(int* cur2_temp) {
for(int i = 1; i <= memory_blockNum; i++) {
int c = 0;
for(int j = 0; j < memory_blockNum; j++) {
if(i == cur2_temp[j]) {
break;
}else {
c++;
}
if(c == memory_blockNum - 1) {
return i;
}
}
}
return 0;
}
void LFU(int *Page) {
//建立哈希表,记录当前时刻每个页面访问的次数
int *Hash = (int*)malloc(pageNum * sizeof(int));
for(int i = 0; i < pageNum; i++) {
Hash[i] = 0;
}
printf("\n最不经常使用置换算法(LFU):\n");
init2(Hash);
/*for(int i = 0; i < memory_blockNum; i++) {
Hash[cur[i]]++;
}*/
/*
printf("初始Hash: ");
for(int i = 0; i < pageNum; i++) {
printf("%d ", Hash[i]);
}printf("\n");
*/
//cur2用于存放当前内存页面号及优先数,优先数越低越先被置换
int** cur2 = (int**)malloc(2 * sizeof(int*));
for(int i = 0; i < 2; i++) {
cur2[i] = (int*)malloc(memory_blockNum * sizeof(int));
}
for(int i = 0; i < memory_blockNum; i++) {
cur2[0][i] = cur[i];
cur2[1][i] = i + 1;
}
for(int i = temp; i < pageNum; i++) {
if(InMemory(cur, Page[i]) == false) {
missNum++;
int flag = LFU_find(cur, cur2, Hash);
cur[flag] = Page[i];
//更新cur2
if(cur2[1][flag] == memory_blockNum) {
cur2[0][flag] = Page[i];
}else {
cur2[0][flag] = Page[i];
for(int k = 0; k < memory_blockNum; k++) {
cur2[1][k]--;
}
cur2[1][flag] = memory_blockNum;
for(int k = 0; k < memory_blockNum; k++) {
if(cur2[1][k] == 0) {
cur2[1][k] = cur2_find(cur2[1]);
}
}
}
show[memory_blockNum + 1][i] = 1; //设置缺页标记
}
//更新show数组
for(int k = 0; k < memory_blockNum; k++) {
show[k + 1][i] = cur[k];
}
//更新Hash
Hash[Page[i]]++;
}
display(show);
double hit_rate = 1 - (double)missNum / (double)pageNum;
printf("最不经常使用置换算法(LFU)命中率:%f\n", hit_rate);
free(Hash);
free(cur2);
}
int main(int argc, char* argv[])
{
FILE *fp = fopen(argv[1], "r");
if(fp == NULL) {
printf("文件读取失败!\n");
return 0;
}
fscanf(fp, "%d", &pageNum);
fscanf(fp, "%d", &memory_blockNum);
Page = (int*)malloc(pageNum * sizeof(int));
for(int i = 0; i < pageNum; i++) {
fscanf(fp, "%d", &Page[i]);
}
printf("pageNum = %d\n", pageNum);
printf("memory_blockNum = %d\n", memory_blockNum);
printf("Page = { ");
for(int i = 0; i < pageNum; i++) {
printf("%d ", Page[i]);
}printf("}\n");
OPT(Page); printf("\n");
LRU(Page); printf("\n");
FIFO(Page); printf("\n");
LFU(Page);
fclose(fp); //关闭文件
return 0;
}
五、验证数据和运行结果
这里我列举了两组数据,两组数据均为50个页面数据,而内存块分别为3和4,共4个测试用例。
第一个测试用例:
第二个测试用例:
第三个测试用例:
第四个测试用例:
六、思考和分析
程序实现(思路):
1)最佳置换算法(OPT):
先进行初始化操作,在内存块还有剩余时循环执行:“判断当前页面是否在内存中,若不在则将该页面存入当前内存页面cur数组中,且缺页次数missNum加一”,循环结束后得到此时遍历到的页面下标temp和缺页次数。初始化结束后,从temp开始遍历页面数组,若页面不在内存中(不在cur数组),则missNum加一,并调用OPT算法对应的查找函数,从当前页面往后遍历,在之后要访问的页面列表中找到最长时间不再被访问的页面,并返回它在内存数组cur中的下标,然后将其置换成当前页面。
2)先进先出置换算法(FIFO):
先进行与OPT算法一样的初始化操作,用类似队列的思想记录当前内存中的页面,遍历页面数组时,若页面不在内存中(不在cur数组),则missNum加一,淘汰队列中的首元素进行置换,然后当前页面放入队列尾部,更新队头元素。
3)最近最久未使用置换算法(LRU):
与OPT算法思路类似,初始化结束后,遍历页面数组时,若页面不在内存中,就在之前访问过的页面列表中找到最近最久没有使用的页面,并返回它在内存数组cur中的下标,然后将其置换成当前页面。
4)最不经常使用算法(LFU):
建立一个哈希数组Hash记录当前各页面的访问次数,然后进行初始化操作,在初始化操作中加上对Hash的更新操作,在建立一个行数为2,列数为内存块数的二维数组cur2,第一行与cur数组一样记录当前内存中的页面,第二行记录这些页面的优先级数(1~内存块数),若内存中的页面对应最小的Hash值不止一个,则优先级低的页面优先被置换。
总结(对于实际情况):
- 最佳置换算法(OPT):每次选择淘汰的页面将是以后永不使用,或者在最长时间内不再被访问的页面,可以保证最低的缺页率。最佳置换算法可以保证最低的缺页率,但实际上,只有在进程执行的过程中才能知道接下来会访问到的是哪个页面。操作系统无法提前预判页面访问序列。因此,最佳置换算法是无法实现的。
- 先进先出算法(FIFO):每次优先淘汰最先进入内存的页面,只有FIFO算法会产生Belady 异常。另外,FIFO算法虽然实现简单,但是该算法与进程实际运行时的规律不适应,因为先进入的页面也有可能最经常被访问。因此,算法性能差。
- 最近最久未使用算法(LRU):每次优先淘汰最近最久未使用的页面,在实际中,该算法的实现需要专门的硬件支持,虽然算法性能好,但实现困难,开销较大。
- 最不经常使用算法(LFU):算法根据数据的历史访问频率来淘汰数据,其核心思想是“如果数据过去被访问多次,那么将来被访问的频率也更高”。一般情况下,LFU效率要优于LRU,且能够避免周期性或者偶发性的操作导致缓存命中率下降的问题。但LFU需要记录数据的历史访问记录,一旦数据访问模式改变,LFU需要更长时间来适用新的访问模式,即:LFU存在历史数据影响将来数据的“缓存污染”效用。