设计和实现:
假设模拟的虚拟内存的地址为16位,页面大小为1K ,模拟的物理内存有32K,通过页面访问序列随机发生器实现对上述算法的测试及性能比较。
基本知识:
请求分页虚拟内存管理是建立在基本分页基础上的,为了能支持虚拟存储器功能,而增加了请求调页功能和置换功能。
多数程序都显示出高度的局部性,也就是说,在一个时间段内,一组页面被反复引用。这组被反复引用的页面随着时间的推移,其成员也会发生变化。有时这种变化是剧烈的,有时这种变化则是渐进的。我们把这组页面的集合称为工作集
缺页中断次数/总的页面访问次数
(1)确定虚拟内存的尺寸N,工作集的起始位置p,工作集中包含的页数e,工作集移动率m(每处理m个页面访问则将起始位置p +1),以及一个范围在0和1之间的值t;
(2)生成m个取值范围在p和p + e间的随机数,并记录到页面访问序列串中;
(3)生成一个随机数r,0 ≤ r ≤ 1;
(4)如果r < t,则为p生成一个新值,否则p = (p + 1) mod N;
(5)如果想继续加大页面访问序列串的长度,请返回第2步,否则结束。
原理:
选择永不使用或是在最长时间内不再被访问(即距现在最长时间才会被访问)的页面淘汰出内存。是一种理想化算法,具有最好性能(对于固定分配页面方式,本法可保证获得最低的缺页率),但实际上却难于实现,故主要用于算法评价参照
全局数据结构设计:
int block = 3;
int access[32];//访问序列
int lost = 0; //没找到的页面数
int index = 0; //指示当前下标
int p; //工作集的起始位置
void generate();//模拟生成访问序列
void initMemory();//初始化存储空间,主要是设置分配空间的大小
bool inMemory (int n); //指定页号是否已经在内存中
//访问序列的长度始终为32,默认初始分配给每种算法的内存空间块数为3
数据结构设计:
void optimal (int n); //访问一个页面,执行一次最佳置换算法
void testOptimal(); //算法实现函数
//最佳适应算法
void optimal (int n)
{
int i = 0, j = 0;
if (inMemory (n))
{
printf ("页面已被调入\n");
}
else
if (index == block)
{
lost++;
int max = 0, pos, tag;
for (i = 0; i < block; i++)
{
tag = -1;
for (j = n + 1; j < 32; j++)
{
if (access[j] == memo[i])
{
tag = j;
break;
}
}
if (tag == -1)
{
max = 32;
pos = i;
break;
}
else
{
if (max < tag)
{
max = tag;
pos = i;
}
}
}
memo[pos] = access[n];
}
else
{
memo[index] = access[n];
index++;
}
}
void testOptimal()
{
initMemory();
int i = 0;
printf ("最佳置换算法:\n");
for (; i < 32; i++)
{
optimal (i);
printf ("%d %d %d\n", memo[0], memo[1], memo[2]);
}
printf("***************************************************\n");
printf ("最佳置换算法:\n");
printf("页面置换次数:%d ,缺页率:%2f \n", lost,lost / 32.0);
printf("***************************************************\n");
lost = 0;
free (memo);
index = 0;
}
原理:
选择最先进入内存即在内存驻留时间最久的页面换出到外存 进程已调入内存的页面按进入先后次序链接成一个队列,并设置替换指针以指向最老页面。
数据结构设计:
//队列结点元素的结构体
typedef struct node1
{
int num;
node1* next;
} Node1, *pNode1;
typedef struct queue
{
int n;
pNode1 front;
pNode1 rear;
} Queue, *pQueue;
void initQueue (pQueue q);//初始化队列
void addQueue (pQueue q, int num);//队列中加入新的页面结点
void removeMemory (pQueue q);/将页面移出内存
void destroy (pQueue q);//销毁队列
bool searchQ (pQueue q, int num);//查找页面是否已经调入内存
void FIFO (pQueue q, int num);//每访问一个页面,执行一次算法
void testFIFO();//先进先出置换算法实现函数
void FIFO (pQueue q, int num)
{
if (searchQ (q, num))
{
printf ("已装入内存\n");
}
else
{
if (q->n == size)
{
removeMemory (q);
addQueue (q, num);
lost++;
}
else
{
addQueue (q, num);
}
}
}
原理:
以“最近的过去”作为“最近的将来”的近似,选择最近一段时间最长时间未被访问的页面淘汰出内存 。
数据结构设计:
void LRU (int n);//每访问一个新的页面,执行一次LRU算法
void testLRU(); //LRU算法实现函数
LRU算法
void LRU (int n)
{
int i, j;
if (inMemory (n))
{
printf ("已经装入内存\n");
}
else
if (index == block)
{
int max = n, pos = -1, tag;
for (i = 0; i < block; i++)
{
for (j = n - 1; j >= 0; j--)
{
if (access[j] == memo[i])
{
tag = j;
break;
}
}
if (tag < max)
{
max = tag;
pos = i;
if (max == 0)
{
break;
}
}
}
memo[pos] = access[n];
lost++;
}
else
{
memo[index] = access[n];
index++;
}
}
void testLRU()
{
int i;
initMemory();
printf ("最近最久未使用算法\n");
for (i = 0; i < 32; i++)
{
LRU (i);
printf ("%d %d %d\n", memo[0], memo[1], memo[2]);
}
printf("***************************************************\n");
printf ("最近最久未使用算法:\n");
printf("置换页面次数为: %d, 缺页率: %2f \n", lost, lost / 32.0);
printf("***************************************************\n");
lost = 0;
index = 0;
}
原理:
改进型Clock置换算法的主要思想是,在每次页面替换时,总是尽可能地先替换掉既未被访问又未被修改的页面。
① 从查寻指针当前位置起扫描内存分页循环队列,选择A=0且M=0的第一个页面淘汰;若未找到,转②
② 开始第二轮扫描,选择A=0且M=1的第一个页面淘汰,同时将经过的所有页面访问位置0;若不能找到,转①
数据结构设计:
struct LNode //改进型Clock置换算法用到的数据结构
{
int data;
int flag;//访问位
int modify;//修改位
};
LNode* nodes;
void updated_Clock (int n);//改进型clock算法实现函数
void test_Clock(); //每访问一个新的页面,执行一次算法
void updated_Clock (int n)
{
if (isInNodes (n))
{
printf ("已经装入内存\n");
}
else
if (index == block)
{
lost++;
int i = 0, tag = -1;
while (true)
{
if ( (i / block) % 2 == 0)
{
if (nodes[i % block].flag == 0 && nodes[i % block].modify == 0)
{
tag = i % block;
break;
}
}
if ( (i / block) % 2 == 1)
{
if (nodes[i % block].flag == 0 && nodes[i % block].modify == 1)
{
tag = i % block;
break;
}
else
{
nodes[i % block].flag = 0;
}
}
i++;
}
nodes[tag].data = access[n];
nodes[tag].flag = 1;
if (rand() % 10 < 4)
{
nodes[tag].modify = 1;
}
else
{
nodes[tag].modify = 0;
}
}
else
{
nodes[index].data = access[n];
nodes[index].flag = 1;
if (rand() % 10 < 4)
{
nodes[index].modify = 1;
}
else
{
nodes[index].modify = 0;
}
index++;
}
}
void testClock()
{
int i = 0, j = 0;
printf ("改进型Clock置换算法\n");
nodes = (LNode*) malloc (block * sizeof (LNode));
for (i = 0; i < block; i++)
{
nodes[i].data = -1;
nodes[i].flag = -1;
nodes[i].modify = -1;
}
for (i = 0; i < 32; i++)
{
updated_Clock (i);
for (j = 0; j < block; j++)
{
printf ("%d ", nodes[j].data);
}
printf ("\n");
}
printf("***************************************************\n");
printf ("改进型Clock置换算法: \n");
printf("置换页面次数为: %d , 缺页率: %2f \n",lost ,lost / 32.0);
printf("***************************************************\n");
lost = 0;
index = 0;
}
原理:
设立空闲页面链表和已修改页面链表 采用可变分配和基于先进先出的局部置换策略,并规定被淘汰页先不做物理移动,而是依据是否修改分别挂到空闲页面链表或已修改页面链表的末尾 空闲页面链表同时用于物理块分配 当已修改页面链表达到一定长度如Z个页面时,一起将所有已修改页面写回磁盘,故可显著减少磁盘I/O操作次数
数据结构设计:
//链表结点元素的结构体如下
struct LNode2
{
int data;//页号
int flag;//访问位
int modify;//修改位
LNode2* next;
};
struct Link
{
int num;//当前链表上的结点数
LNode2* next;
};
bool inNodes (int n); //页面是否已经在链表中
void add (int data, int type);//页面添加到已修改页面链表和空闲链表上
void trans1(); //将空闲链表上的所有页面送出内存
void trans2(); //将已修改页面链表上所有的链表送出内存
void PBA (int n); //PBA算法实现函数
void initialPBA();//PBA算法初始化函数
void initialPBA()
{
int i = 0, j = 0;
generate();
printf ("页面缓冲置换算法PBA\n");
link1.num = 0;
link1.next = NULL;
link2.num = 0;
link2.next = NULL;
nodes2 = (LNode2*) malloc (size * sizeof (LNode2));
for (i = 0; i < size; i++)
{
nodes2[i].data = -1;
nodes2[i].flag = 0;
nodes2[i].modify = 0;
nodes2[i].next = NULL;
}
for (i = 0; i < 32; i++)
{
PBA (i);
for (j = 0; j < size; j++)
{
printf ("%d ", nodes2[j].data);
}
printf ("\n");
}
printf("***************************************************\n");
printf("页面缓冲置换算法PBA:\n");
printf ("置换页面次数为:%d , 缺页率:%f \n", lost, lost / 32.0);
printf("***************************************************\n");
lost = 0;
index = 0;
}
要求:
测试不同的页面访问序列及不同的虚拟内存尺寸,并从缺页率、算法开销等方面对各个算法进行比较。同时请给出在给定页面访问序列的情况下,发生页面置换次数的平均值。
五种算法计算结果截图分别如下:
经过三次实验,得到结果列表如下:
置换算法 |
最佳置换算法 |
先进先出置换算法 |
最近最久未使用算法 |
改进型clock置换算法 |
页面缓冲置换算法 |
|
测试序列1 |
缺页数 |
12 |
15 |
18 |
10 |
14 |
缺页率 |
0.375 |
0.46875 |
0.5625 |
0.3375 |
0.46875 |
|
测试序列2 |
缺页数 |
15 |
20 |
22 |
20 |
12 |
缺页率 |
0.468750 |
0.625 |
0.6875 |
0.625 |
0.375 |
|
测试序列3 |
缺页数 |
13 |
14 |
9 |
16 |
14 |
缺页率 |
0.40625 |
0.345 |
0.23875 |
0.5 |
0.4375 |
|
平均 |
缺页数 |
11.67 |
15 |
16 |
14.3 |
13.3 |
|
缺页率 |
0.3647 |
0.46875 |
0.5 |
0.4468 |
0.415625 |
最佳置换算法>页面缓冲置换算法>改进型clock置换算法>最近最久未使用算法>=先进先出置换算法。
https://github.com/Zhaoyu620/os-experiment4/tree/master