请求页式存储管理系统实验
操作系统实验三——请求页式存储管理(含代码实现)
实验环境
C++ | g++ 8.1.0 |
---|---|
IDE | Visual Studio 2017 Enterprise (15.9.13) |
操作系统 | Windows 10 x64 中文专业版 (1903) |
实验目的
近年来,由于大规模集成电路(LSI)和超大规模集成电路(VLSI)技术的发展,使存储器的容量不断扩大,价格大幅度下降。但从使用角度看,存储器的容量和成本总受到一定的限制。所以,提高存储器的效率始终是操作系统研究的重要课题之一。虚拟存储技术是用来扩大内存容量的一种重要方法。学生应独立地用高级语言编写几个常用的存储分配算法,并设计一个存储管理的模拟程序,对各种算法进行分析比较,评测其性能优劣,从而加深对这些算法的了解。
为了比较真实地模拟存储管理,可预先生成一个大致符合实际情况的指令地址流。然后模拟这样一种指令序列的执行来计算和分析各种算法的访问命中率。
实验内容
-
编制和调试示例给出的请求页式存储管理程序,并使其投入运行。
-
增加1~2 种已学过的淘汰算法,计算它们的页面访问命中率。试用各种算法的命中率加以比较分析。
【示例】
1、题目本示例是采用页式分配存储管理方案,并通过分析计算不同页面淘汰算法情况下的访问命中率来比较各种算法的优劣。另外也考虑到改变页面大小和实际存储器容量对计算结果的影响,从而可为算则好的算法、合适的页面尺寸和实存容量提供依据。
本程序是按下述原则生成指令序列的:
(1)50%的指令是顺序执行的。
(2)25%的指令均匀散布在前地址部分。
(3)25%的指令均匀散布在后地址部分。
示例中选用最佳淘汰算法(OPT)和最近最少使用页面淘汰算法(LRU)计算页面命中率。
公式为:
假定虚存容量为32K,页面尺寸从1K 至8K,实存容量从4 页至32 页。
2、算法与框图
(1)最佳淘汰算法(OPT)。
这是一种理想的算法,可用来作为衡量其他算法优劣的依据,在实际系统中是难以实现的,因为它必须先知道指令的全部地址流。由于本示例中已预先生成了全部的指令地址流,故可计算出最佳命中率。该算法的准则是淘汰已满页表中不再访问或是最迟访问的的页。这就要求将页表中的页逐个与后继指令访问的所有页比较,如后继指令不在访问该页,则把此页淘汰,不然得找出后继指令中最迟访问的页面淘汰。可见最佳淘汰算法要花费较长的运算时间。
(2)最近最少使用页淘汰算法(LRU)。
这是一种经常使用的方法,有各种不同的实施方案,这里采用的是不断调整页表链的方法,即总是淘汰页表链链首的页,而把新访问的页插入链尾。如果当前调用页已在页表内,则把它再次调整到链尾。这样就能保证最近使用的页,总是处于靠近链尾部分,而不常使用的页就移到链首,逐个被淘汰,在页表较大时,调整页表链的代价也是不小的。
(3)主程序框图如下图 1示。
操作过程
1. 编制和调试示例给出的请求页式存储管理程序,并使其投入运行。【完整程序见附录】
- 指令流生成
本程序按下述原则生成指令序列的:
(1)前50%的指令是顺序执行的。
(2)后25%的指令随机散布在前地址部分。
(3)后25%的指令随机散布在后地址部分。
正整数的范围从0~32767,指令地址均在此区间,所以限制程序中的虚存尺寸为32K,且设置指令流长度为256。关键代码如下:
// 生成指令流
void init()
{
srand(time(0));
printf("vmsize = 32k\n");
printf("length = 256\n");
printf("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =\n");
printf("THE VIRTUAL ADDRESS STREAM AS FOLLOWS:\n");
printf("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =\n");
for (int i = 0; i < length; i++)
{
if (i == 0)
a[i] = rand() % 32767;
else if (i < length / 2)
a[i] = a[i - 1] + 1;
else if (i < length / 4 * 3)
a[i] = rand() % (32767 / 2);
else
a[i] = (32767 / 2) + rand() % (32767 / 2);
printf("a[%d]=%d ", i, a[i]);
if (i % 6 == 0)
printf("\n");
}
printf("\n");
printf("= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =\n");
}
- OPT算法实现
OPT算法为淘汰已满页表中不再访问或是最迟访问的的页。这就要求将页表中的页逐个与后继指令访问的所有页比较,如后继指令不在访问该页,则把此页淘汰,不然得找出后继指令中最迟访问的页面淘汰。本实验采取链表方式实现OPT算法,其算法框图和关键代码如下:
// 淘汰已满页表中不再访问或是最迟访问的的页
void OPT(int memcount, int pagesize)
{
/*
淘汰已满页表中不再访问或是最迟访问的的页。
这就要求将页表中的页逐个与后继指令访问的所有页比较,
如后继指令不在访问该页,则把此页淘汰,不然得找出后继指令中最迟访问的页面淘汰。
*/
node *table = new node; // 页表 table[i]=j,表示虚存的第j 页在实存的第i 页中。
node *tail = table;
int called; // 当前请求的页面号
bool pagefault; // 页面失效标志,如当前请求页called 已在页表内,则置pagefault=false,否则为true
int used = 0; // 当前被占用的实存页面数,可用来判断当前实存中是否有空闲页。
int misscount = 0; // 页面失效次数
for (int j = 0; j < length; j++) // 顺序执行指令流
{
called = page[j];
node *p = table;
node *q = p;
pagefault = false;
/* 如果当前调用页已在页表内,则页面有效 */
while (p->next != NULL)
{
if (p->next->target == called)
{
pagefault = true;
break;
}
p = p->next;
}
/* 如果当前调用页不在页表内,则在空间不足时执行淘汰算法 */
if (pagefault == false)
{
misscount++; // 页面访问时产生中断--->页面失效
// 无空闲物理块则淘汰已满页表中不再访问或是最迟访问的的页
if (used == memcount)
{
p = table;
q = p->next; // q记录当前最迟访问页面在链表中的位置
int index = 0; // index记录当前最迟访问页面的序号
while (p->next != NULL)
{
int i;
for (i = j + 1; i < length; i++)
{
if (p->next->target == page[i])
{
break;
}
}
if (i > index) // 该页面为当前最迟访问页面
{
index = i;
q = p;
}
if (i == length) // 该页面以后不会被访问了
{
q = p;
break;
}
p = p->next;
}
// q->next指向页表中不再访问或是最迟访问的的页
if (tail == q->next) // 特殊情况:要删除末页的话要修改tail的指向
tail = q;
// 删除淘汰的页面
p = q->next;
q->next = p->next;
delete p;
used--;
}
//插入新的页面
p = new node(called);
tail->next = p;
tail = p;
used++;
}
}
double rate = 100 - misscount * 100 / double(length);
printf("%3d %3d %3.3lf% \n", memcount, misscount, rate);
}
- LRU算法实现
这里采用的是不断调整页表链的方法,即总是淘汰页表链链首的页,而把新访问的页插入链尾。如果当前调用页已在页表内,则把它再次调整到链尾。算法框图和关键代码如下:
void LRU(int memcount, int pagesize)
{
/*
总是淘汰页表链链首的页,而把新访问的页插入链尾。
如果当前调用页已在页表内,则把它再次调整到链尾。
这样就能保证最近使用的页,总是处于靠近链尾部分,而不常使用的页就移到链首,逐个被淘汰
*/
node *table = new node; // 页表 table[i]=j,表示虚存的第j 页在实存的第i 页中。
node *tail = table;
int called; // 当前请求的页面号
bool pagefault; // 页面失效标志,如当前请求页called 已在页表内,则置pagefault=false,否则为true
int used = 0; // 当前被占用的实存页面数,可用来判断当前实存中是否有空闲页。
int misscount = 0; // 页面失效次数
for (int j = 0; j < length; j++) // 顺序执行指令流
{
called = page[j];
node *p = table;
node *q = p;
pagefault = false;
/* 如果当前调用页已在页表内,则把它再次调整到链尾 */
while (p->next != NULL)
{
if (p->next->target == called)
{
pagefault = true;
if (p->next != tail)
{
// 调整位置
q = p->next;
p->next = q->next;
q->next = NULL;
tail->next = q;
tail = q;
}
break;
}
p = p->next;
}
/* 如果当前调用页不在页表内,则把它添加到链尾 */
if (pagefault == false)
{
misscount++;
// 无空闲物理块则淘汰第一个页面
if (used == memcount)
{
p = table->next;
table->next = p->next;
delete p;
used--;
}
//插入新的页面
p = new node(called);
tail->next = p;
tail = p;
used++;
}
}
double rate = 100 - misscount * 100 / double(length);
printf("%3d %3d %3.3lf% \n", memcount, misscount, rate);
}
2. 增加1~2 种已学过的淘汰算法,计算它们的页面访问命中率。试用各种算法的命中率加以比较分析。【完整程序见附录】
- FIFO算法实现
这里增加了FIFO淘汰算法,即先调用到内存的页面先被淘汰出去。程序打印出不同页面大小和物理块数时三种算法的命中率。下面时该算法的程序框图和关键代码:
![image7](D:\C2\操作系统(2)\实验\请求页式存储管理\image7.png)// 先访问的页先淘汰
void FIFO(int memcount, int pagesize)
{
node *table = new node; // 页表 table[i]=j,表示虚存的第j 页在实存的第i 页中。
node *tail = table;
int called; // 当前请求的页面号
bool pagefault; // 页面失效标志,如当前请求页called 已在页表内,则置pagefault=false,否则为true
int used = 0; // 当前被占用的实存页面数,可用来判断当前实存中是否有空闲页。
int misscount = 0; // 页面失效次数
for (int j = 0; j < length; j++) // 顺序执行指令流
{
called = page[j];
node *p = table;
node *q = p;
pagefault = false;
/* 如果当前调用页已在页表内(页面有效),则保持其位置不动 */
while (p->next != NULL)
{
if (p->next->target == called)
{
pagefault = true;
break;
}
p = p->next;
}
/* 如果当前调用页不在页表内(页面失效),则执行淘汰算法,并把它添加到链尾 */
if (pagefault == false)
{
misscount++;
// 无空闲物理块则淘汰第一个页面
if (used == memcount)
{
if (tail == table->next) // 特殊情况:要删除末页的话要修改tail的指向
tail = table;
p = table->next;
table->next = p->next;
delete p;
used--;
}
//插入新的页面
p = new node(called);
tail->next = p;
tail = p;
used++;
}
}
double rate = 100 - misscount * 100 / double(length);
printf("%3d %3d %3.3lf% \n", memcount, misscount, rate);
}
void Test_alg()
{
int pagesize; // 页面大小
int alg;
setpage(pagesize);
printf("The algorithm is [OPT = 1 LRU = 2]:");
scanf("%d", &alg);
//memcount: 物理地址块数
printf("MEMORY MISS COUNT HIT RATE\n");
for (int memcount = 2; memcount <= 32 / pagesize; memcount = memcount + 2)
{
if (alg == 1)
{
OPT(memcount, pagesize);
}
else if (alg == 2)
{
LRU(memcount, pagesize);
}
else
{
break;
}
}
}
结果
1. 编制和调试示例给出的请求页式存储管理程序,并使其投入运行。
下面截取了73~156的指令地址流:
页面大小为1K时对应的虚拟页号:
MEMORY为内存块数,MISS COUNT为缺页次数,HIT RATE为命中率。
这里截取了使用OPT算法和LRU算法,页面大小为1K,不同物理块数情况下的命中率情况。
可以明显看出随着物理块数增大,OPT算法和LRU算法的命中率都明显增大,OPT在块数达到18以后保持最大值87.5%,LRU在块数达到28以后才达到最大值87.5%。在16块以下时LRU算法总是比OPT算法的命中率低,但在16以后都达到了87%,两者相差较小。
2. 增加1~2 种已学过的淘汰算法,计算它们的页面访问命中率。试用各种算法的命中率加以比较分析。
部分运行结果:
分析
从结果可以看出,相同页面大小不同物理块数时,LRU算法和FIFO算法命中率不相上下,这跟页面的分布情况有关。但是这两种算法都比OPT算法低,且随着物理块数增大而增大。在vmsize = 32k ,pagesize = 8k memcount = 2时三种算法都达到最大命中率HIT RATE: 98.438%,这是因为总共只有4个虚拟页面,在两个物理块的情况下页面交换次数极小,说明命中率与主存容量、页面大小、程序局部性和替换算法等因素都有很大的关系。