1.实验目的
通过对页面、页表、地址转换和页面置换过程的模拟,加深对请求调页系统的原理和实现过程的理解。
2.实验内容
(1)假设每个页面中可存放10条指令,分配给作业的内存块数为4。
(2)用c语言模拟一个作业的执行过程,该作业共有320条指令,即它的地址空间为32页,目前它的所有页都还未调入内存。在模拟过程中,如果所访问的指令已在内存,则显示其物理地址,并转下一条指令。如果所访问的指令还未装入内存,则发生缺页,此时需记录缺页的次数,并将相应页调入内存。如果4个内存块均已装入该作业,则需进行页面置换,最后显示其物理地址,并转下一条指令。在所有320指令执行完毕后,请计算并显示作业运行过程中发生的缺页率。
(3)置换算法:采用先进先出(FIFO)、最近最久未使用(LRU)和最佳置换(OPT)算法置换算法。
(4)通过随机数产生一个指令序列,共320条指令。
1)指令的地址按下述原则生成:
① 50%的指令是顺序执行的;
② 25%的指令是均匀分布在前地址部分;
③ 25%的指令是均匀分布在后地址部分;
具体的实施方法是:
① 在[0,319]的指令地址之间随机选取一起点m;
② 顺序执行一条指令,即执行序号为m+1的指令;
③ 在前地址[0,m-1]中随机选取一条指令并执行,该指令的序号为m1;
④ 顺序执行一条指令,其序号为m1+1的指令;
⑤ 在后地址[m1+2,319]中随机选取一条指令并执行,该指令的序号为m2;
⑥ 顺序执行一条指令,其序号为m2+1的指令;
重复上述步骤①~⑥,直到执行320次指令。
2)将指令序列变换为页地址流
设页面大小为1K, 用户虚存容量为32K。在用户虚存中,按每K存放10条指令排列虚存地址,即320条指令在虚存中的存放方式为:
第0条~第9条指令为第0页(对应虚存地址为[0,9]);
第10条~第19条指令为第1页(对应虚存地址为[10,19]);
……
……
第310条~第319条指令为第31页(对应虚存地址为[310,319])。
按以上方式,用户指令可组成32页。
:
上图为在华为云上OPT运行的结果,修改主函数中的注释即可选择不同的置换算法
以下为C语言源代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define INSTR_NUM 320 // 指令条数
#define MEMORY_BLOCK_NUM 4 // 物理内存被划分的块数
#define INSTR_NUM_PER_PAGE 10 // 每个页面的指令条数
typedef struct Block {
int page_id;
int next; // 在最佳替换算法中OPT使用,FIFO和LRU 均用队列来辅助实现
} Block;
int _index = 0;
int instr[INSTR_NUM]; // 随机产生的指令执行序列数组
int lack_num; // 缺页次数,不同算法中将该值重置为0
Block memory_block[MEMORY_BLOCK_NUM]; // 内存物理块
// 返回某范围的随机数
int getRandom(int start, int end) {
int t = rand() % (end - start + 1) + start;
int j;
for (j = 0; j < _index; j++) {
if (instr[j] == t) {
break;
}
}
if (_index == j) {
instr[_index++] = t;
}
int k;
for (k = 0; k < _index; k++) {
if (instr[k] == t + 1) {
break;
}
}
if (_index == k && t + 1 < INSTR_NUM) {
instr[_index++] = t + 1;
}
return t;
}
// 初始化,给指令执行数组赋值
void init() {
// 得到指令执行顺序,存入数组
int t = getRandom(0, INSTR_NUM - 1);
while (_index < INSTR_NUM) {
if (t == 1 && _index == 2) {
instr[_index++] = t - 1;
}
if (t > 1) {
int forword = getRandom(0, t - 1);
}
if (t <= INSTR_NUM - 3) {
int last = getRandom(t + 2, INSTR_NUM - 1);
}
}
// 内存置空
for (int i = 0; i < MEMORY_BLOCK_NUM; i++) {
memory_block[i].page_id = -1;
}
}
// 当前页面是否已经调入页框
int isExistInMemory(int curr_page) {
// 不存在内存中,返回-1 ,存在 返回所在物理块位置
int is_exist = -1;
for (int i = 0; i < MEMORY_BLOCK_NUM; i++) {
if (memory_block[i].page_id == curr_page) {
is_exist = i;
break;
}
}
return is_exist;
}
// 是否有空闲页框
Block* findEmpty() {
Block* empty = NULL;
for (int i = 0; i < MEMORY_BLOCK_NUM; i++) {
if (memory_block[i].page_id == -1) {
empty = &memory_block[i];
break;
}
}
return empty;
}
// 在OPT中使用,找到应该被替换的页框
int findReplace() {
int pos = 0;
for (int i = 0; i < MEMORY_BLOCK_NUM; i++) {
if (memory_block[i].next > memory_block[pos].next)
pos = i; //找到应予置换页面,返回BLOCK中位置
}
return pos;
}
// 返回最佳被置换的页面
void findBeReplaceBlockByOPT(int index_) {
for (int i = 0; i < MEMORY_BLOCK_NUM; i++) {
for (int j = index_ + 1; j < 320; j++) {
if (memory_block[i].page_id != instr[j] / INSTR_NUM_PER_PAGE) {
memory_block[i].next = 1000; //最近此块并没有指令要被访问
}
else { //将来不会用,设置为一个很大数
memory_block[i].next = j; //最近此块内要被访问的指令
break;
}
}
}
}
// 显示当前页的物理块(页框)编号,模拟真实物理地址的计算
void show(int curr_page) {
// printf("物理地址分别是:\n");
for (int i = 0; i < MEMORY_BLOCK_NUM; i++) {
if (memory_block[i].page_id == curr_page) {
// printf("第%d页所在物理块编号:%d ", curr_page, i);
// printf("物理地址:%d ", i);
printf("%d ", i);
break;
}
}
}
// 最佳页面替换算法
void OPT() {
int pc;
int curr_page;
int is_exist;
int position;
float lack_num = 0; // Declare and initialize `lack_num` here.
Block* empty;
printf("OPT:\n\n");
for (int i = 0; i < INSTR_NUM; i++) {
pc = instr[i];
// 指令是否在内存块中,在则打印,不在则调入
curr_page = pc / INSTR_NUM_PER_PAGE; // 当前指令所在的页面编号
is_exist = isExistInMemory(curr_page); // 当前页面是否在内存中 -1 表示不存在
if (is_exist == -1) {
lack_num++;
// 内存是否还有剩余空间,有则直接调入,没有则根据OPT算法找到最合适的页面调出
empty = findEmpty();
if (empty != NULL) {
empty->page_id = curr_page;
}
else {
findBeReplaceBlockByOPT(i);
position = findReplace();
memory_block[position].page_id = curr_page;
}
}
show(curr_page);
}
printf("\n缺页率:%.2f %%\n", lack_num / 320.0 * 100);
}
// 最近最少使用页面替换算法
void LRU() {
int pc;
int curr_page;
int is_exist;
Block* empty;
lack_num = 0;
printf("LRU:\n\n");
for (int i = 0; i < INSTR_NUM; i++) {
int tempQueue[INSTR_NUM]; // 使用数组模拟队列
int queue_front = 0, queue_rear = -1;
pc = instr[i];
curr_page = pc / INSTR_NUM_PER_PAGE;
is_exist = isExistInMemory(curr_page);
if (is_exist == -1) {
lack_num++;
empty = findEmpty();
if (empty != NULL) {
empty->page_id = curr_page;
// 将新页面放入队列末尾
tempQueue[++queue_rear] = curr_page;
}
else {
int first = tempQueue[queue_front++];
// 替换内存中的页面
for (int j = 0; j < MEMORY_BLOCK_NUM; j++) {
if (memory_block[j].page_id == first) {
memory_block[j].page_id = curr_page;
break;
}
}
// 将新页面放入队列末尾
tempQueue[++queue_rear] = curr_page;
}
}
else {
// 存在则将页面移动到队列末尾
for (int j = queue_front; j <= queue_rear; j++) {
if (tempQueue[j] == curr_page) {
// 将当前页面移到队列末尾
for (int k = j; k < queue_rear; k++) {
tempQueue[k] = tempQueue[k + 1];
}
tempQueue[queue_rear] = curr_page;
break;
}
}
}
show(curr_page);
}
printf("\n缺页率:%.2f %%\n", lack_num / 320.0 * 100);
}
// 先入先出页面替换算法
void FIFO() {
int pc;
int curr_page;
int is_exist;
Block* empty;
lack_num = 0;
printf("FIFO:\n\n");
printf("Physical Address:\n");
for (int i = 0; i < INSTR_NUM; i++) {
pc = instr[i];
curr_page = pc / INSTR_NUM_PER_PAGE;
is_exist = isExistInMemory(curr_page);
if (is_exist == -1) {
lack_num++;
empty = findEmpty();
if (empty != NULL) {
empty->page_id = curr_page;
}
else {
int first = memory_block[0].page_id;
for (int j = 0; j < MEMORY_BLOCK_NUM - 1; j++) {
memory_block[j].page_id = memory_block[j + 1].page_id;
}
memory_block[MEMORY_BLOCK_NUM - 1].page_id = curr_page;
}
}
show(curr_page);
}
printf("\nPage Fault Rate: %.2f %%\n", lack_num / 320.0 * 100);
}
int main() {
// 以时间作为随机数种子
srand((unsigned)time(NULL));
init();
printf("\n随机产生的指令执行序号:\n");
for (int i = 0; i < INSTR_NUM; i++) {
printf("%d ", instr[i]);
}
printf("\n\n");
// 每次只能使用一种算法
OPT();
// FIFO();
// LRU();
return 0;
}