操作系统实验(5)—— 页面淘汰算法模拟实现与比较
利用标准 C 语言,编程设计与实现最佳淘汰算法、先进先出淘汰算法、最近最久未使用淘汰算法、简单 Clock 淘汰算法及改进型 Clock 淘汰算法,并随机发生页面访问序列开展有关算法的测试及性能比较。......
页面淘汰算法模拟实现与比较
1. 实验目的
理解并掌握主要页面淘汰算法的设计和实现要旨。
2. 实验内容
利用标准 C 语言,编程设计与实现最佳淘汰算法、先进先出淘汰算法、最近最久未使用淘汰算法、简单 Clock 淘汰算法及改进型 Clock 淘汰算法,并随机发生页面访问序列开展有关算法的测试及性能比较。
3. 实验要求
本实验课题功能设计要求如下:
- 编程设计实现最佳淘汰算法OPT、先进先出淘汰算法FIFO、最近最久未使用淘汰算法LRU、简单Clock 淘汰算法及改进型 Clock 淘汰算法;
- 编程设计实现页面访问序列的随机发生机制,包括各页面读写访问方式的设定以满足改进型 Clock 淘汰算法的要求;
- 在执行进程和访问各页面过程中,每访问一个(或一次)页面应显示输出当时的进程页表内容(包括页号、物理块号、状态位、读/写访问方式等字段)及本次页面访问操作情况(譬如页面已在内存或触发缺页中断);
- 基于相同的条件,包括系统均采用固定分配局部置换策略、相同的进程逻辑地址空间大小(暨逻辑页面数,设进程逻辑地址空间的页面总数为 N,则其页号取值区间为[0, N))、分配给进程同样多的物理块(设进程分配获得 S 个物理块,则相应物理块号分别标记为 PF0、PF1、……、PFS-1)、相同的页面访问序列(整数序列,整数取值区间为[0, N))、均预装入前三个页面,进行有关算法的测试;
- 变换上述条件实施多次测试,统计分析和比较有关算法的性能(譬如缺页率、淘汰页查找时间开销)。
关于页面访问序列随机发生机制的设计思想:
- 初始化进程逻辑地址空间页面总数 N、各逻辑页面的读写访问方式(是否支持写访问,即 R、RW)、工作集起始页号 s(s∈[0, N))、工作集中包含的页数 w,工作集移动速率 v(每处理 v 个页面访问,就将工作集起始页号递增即 s+1)以及一个取值区间为[0, 1]的值 t;
- 生成取值区间为[s, min(s+w, N-1)]的 v 个随机数并添加保存到页面访问序列中,同 时为每次页面访问分别生成一个取值区间为[0, 1]的随机数,若该随机数值大于 0.7 且对应所访问页面支持写访问则设定以写方式访问相应页面,否则以读方式访问对应页面;
- 生成取值区间为[0, 1]的一个随机数 r,并比较 r 与 t 的大小;
- 若 r < t,则为 s 生成一个新值(s∈[0, N)),否则 s = (s + 1) mod N;
- 如果想继续加大页面访问序列的长度,返回第 2 步,否则结束。
4. 实验过程
4.0 整体设计
4.0.1 数据结构
- 页表结构设计
假设虚拟内存的地址是16位,页面大小为1K,则进程的大小为 2 16 = 64 K B 2^{16}=64KB 216=64KB
分页个数 = 进程的大小 页面的大小 = 逻辑地址表示的大小 页面的大小 = 2 16 B 1 K B = 2 6 页 分页个数=\frac{进程的大小}{页面的大小}=\frac{逻辑地址表示的大小}{页面的大小}=\frac{2^{16B}}{1KB}=2^{6}页 分页个数=页面的大小进程的大小=页面的大小逻辑地址表示的大小=1KB216B=26页
因此页号P占用前6位,页内偏移量W=16-6=10位。每一个页表项占用2B,因此页表存储空间需要 2 6 ∗ 2 B = 128 B 2^6*2B=128B 26∗2B=128B
页面结构以及页表项设计如下:
struct PageInfo //页面信息结构
{
int pages[MAX]; // 模拟的最大访问页面数
int isVisited; // 标志位,0表示无页面访问数据
int page_missing_num; // 缺页中断次数
int allocated_page_num; // 分配的页框数
int visit_list_length; // 访问页面序列长度
} pInfo;
struct MemInfo // 页表项信息
{
int time; //记录页框中数据的访问次数
int isVisit;//访问位
int isModify;//修改位
int pages;//页号
}mInfo;
- 关键全局变量
MemInfo pageList[MAX]; // 分配的页框
int page_loss_num = 0; // 缺页次数
int current_page; //页面访问指针
int replace_page; //页面替换指针
int is_loss_page; //缺页标志:1->缺页,0->命中
4.0.2 主要函数
Init
初始化
void Init()
{
int i, pn;
is_loss_page = 0; //缺页标志,0为不缺页,1为缺页
pInfo.page_missing_num = 0; // 缺页次数
pInfo.isVisited = 0; // 标志位,0表示无页面访问数据
printf("请输入分配的页框数:"); // 自定义分配的页框数
scanf("%d", &pn);
pInfo.allocated_page_num = pn;
for (i = 0; i < MAX; i++) // 清空页面序列
{
pInfo.pages[i] = -1;
}
}
getRandomList
随机生成访问序列
算法流程如下:
- 确定虚拟内存的尺寸N,工作集的起始位置p,工作集中包含的页数e,工作集移动率m(每处理m个页面访问则将起始位置p +1),
- 以及一个范围在0和1之间的值t;
- 生成m个取值范围在p和p + e间的随机数,并记录到页面访问序列串中;
- 生成一个随机数r,0 ≤ r ≤ 1;如果r < t,则为p生成一个新值,否则p = (p + 1) mod N;
- 如果想继续加大页面访问序列串的长度,请返回第2步,否则结束。
具体实现如下:
void getRandomList(int m, int e, int s)
{
int pl;
Init(); //初始化
printf("请输入访问序列的长度:");
scanf("%d", &pl); //随机生成访问序列的长度
pInfo.visit_list_length = pl;
srand((unsigned)time(NULL)); // 随机种子
int p = rand() % MAX; //在0——MAX范围内随机初始化工作集的起始位置p
int i, j=0;
double t= rand() % 10 / 10.0; // 一个范围在0和1之间的值t
for (i = 0; i < s; i++) // 重复s次
{
if (j > pInfo.visit_list_length) // 不能超过访问序列的长度
break;
for (j = i * m; j < (i + 1) * m; j++)//生成m个取值范围在p~p + e间的随机数
{
pInfo.pages[j]= (p + (rand() % e)) % MAX; //并记录到页面访问序列串中;
}
double r = (rand() % 10) / 10.0;// 生成一个范围在0-1之间的随机数r
if (r < t) // 如果r < t,则为p生成一个新值
p = rand() % MAX;
else
{
p = (p + 1) % MAX; //否则向后循环移位
}
}
}
showState
跟踪显示状态信息
算法流程如下:
- 如果
current_page
=0, 则说明当前页面指针处于起始位置,则首先遍历pInfo.pages[i]
数组,打印页面的访问序列。其具体格式为每行显示10个等访问的页面。 - 打印当前要访问的页号
- 打印当前页框内已经有的页面号
- 通过
is_loss_page
判断是否发生中断。如果发生中断,则用缺页数/已访问的页面数计算得到缺页率并显示给用户。
具体实现如下:
void showState(void)
{
int i, n;
if (current_page == 0)
{
printf("\n~~~~~~~~~~~~~~~~~页面访问序列~~~~~~~~~~~~~~~~~\n");
for (i = 0; i < pInfo.visit_list_length; i++)
{
printf("%4d", pInfo.pages[i]);
if ((i + 1) % 10 == 0) printf("\n"); //每行显示10个
}
printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
}
printf("访问%3d -->内存空间[", pInfo.pages[current_page]); // 访问当前页面信息
for (n = 0; n < pInfo.allocated_page_num; n++) // 页框信息
{
if (pageList[n].pages >= 0)
printf("%3d", pageList[n].pages);
else
printf(" ");
}
printf(" ]");
if (is_loss_page == 1) //缺页标志,0为不缺页,1为缺页
{
printf(" --> 缺页中断 --> ");
if (current_page == 0)
{
printf("缺页率 = %3.1f", (float)(pInfo.page_missing_num) * 100.00 );
}
else{
printf("缺页率 = %3.1f", (float)(pInfo.page_missing_num) * 100.00 / current_page); }
}
printf("\n");
}
isExist
查找页面是否已经在内存中
算法流程如下:
- 遍历所有在内存中的页面,同时使得访问次数加1
- 然后再次遍历内存,寻找匹配页号的页表项
- 如果找到该页表项,则置
is_loss_page
缺页标志=0(不缺页),同时访问次数time
也置为0,然后通过随机生成的0或者1,来设置该访问页面是否被修改。并返回1。 - 如果没有找到该页面,则缺页中断的次数+1,同时返回0。
int isExist(int page)
{
int n;
for (n = 0; n < pInfo.allocated_page_num; n++)
pageList[n].time++; // 访问次数加1
for (n = 0; n < pInfo.allocated_page_num; n++)
{
if (pageList[n].pages == page)
{
is_loss_page = 0; //is_loss_page缺页标志=0(不缺页)
pageList[n].time = 0; //置访问次数为0
pageList[n].isVisit = 1; // 访问标志
if (randBool()) {
pageList[n].isModify = 1; // 修改标志
}
return 1;
}
}
is_loss_page = 1; //页面不存在,缺页
return 0;
}
其中调用了randBool()
函数,随机的生成0或者1,其具体实现如下
bool randBool()
{
if ((rand() % 2 + 1) == 1)
return true;
else
return false;
}
4.1 最佳淘汰算法(OPT)
4.1.1 原理介绍
算法规则:选择永不使用或是在最长时间内不再被访问(即距现在最长时间才会被访问)的页面淘汰出内存。
假定系统为某进程分配了三个物理块,并考虑有以下页面访问序列:
[7, 0, 1, 2, 0, 3, 0, 4, 2, 3, 0, 3, 2, 1, 2, 0, 1, 7, 0, 1]
进程运行时,先将7, 0, 1三个页面依次装入内存。进程要访问页面2时,产生缺页中断,根据最佳置换算法,选择第18次访问才需调入的页面7予以淘汰。然后,访问页面0时,因为已在内存中所以不必产生缺页中断。访问页面3时又会根据最佳置换算法将页面1淘汰……依此类推。
页面 | 7 | 0 | 1 | 2 | 0 | 3 | 0 | 4 | 2 | 3 | 0 | 3 | 2 | 1 | 2 | 0 | 1 | 7 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
页面1 | 7 | 7 | 7 | 2 | 2 | 2 | 2 | 2 | 7 | |||||||||||
页面2 | 0 | 0 | 0 | 0 | 4 | 0 | 0 | 0 | ||||||||||||
页面3 | 1 | 1 | 3 | 3 | 3 | 1 | 1 | |||||||||||||
缺页 | √ | √ | √ | √ | √ | √ | √ | √ |
4.1.2 代码实现
算法流程图如下:
OPT
算法具体实现如下:
void OPT()
{
int n, full, status;
replace_page = 0; // 页面替换指针初始化为0
page_loss_num = 0; // 缺页数初始化为0
full = 0; // 已经占用的页框数
is_loss_page = 0; //缺页标志,0为不缺页,1为缺页
for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
pageList[n].pages = -1;
for (current_page = 0; current_page < pInfo.visit_list_length; current_page++) // 遍历所有的访问页面
{
status = isExist(pInfo.pages[current_page]); //查找页面是否在内存
if (full < pInfo.allocated_page_num) // 当已经占用的页框数< 分配的页框数时,则不可能发生缺页
{
if (status == 0) // 页不存在则装入页面
{
pageList[replace_page].pages = pInfo.pages[current_page]; // 当前位置放入新的页面
replace_page = (replace_page + 1) % pInfo.allocated_page_num; //循环后移一位
full++; // 已经装入的页面数+1
}
}
else // 页框已经满,缺页置换
{
if (status == 0) // 页不存在则置换页面
{
int min,max = 0 ; //很大的数
for (int m = 0; m < pInfo.allocated_page_num ; m++) // 遍历页框中以占用的页号
{
min = 1000;
for (int n = current_page; n < pInfo.visit_list_length; n++)// 向后查找访问序列
{
if (pInfo.pages[n] == pageList[m].pages) // 找到在当前内存出现过的页面
{
min = n;
break;
}
}
if (max < min)
{
max = min;
replace_page = m; // 距离当前页面最远的被置换
}
}
pageList[replace_page].pages = pInfo.pages[current_page]; // 置换动作
replace_page = (replace_page + 1) % pInfo.allocated_page_num;
pInfo.page_missing_num++; // 置换次数加1
}
}
Sleep(10);
showState(); // 显示当前状态
} // 置换算法循环结束
_getch();
return;
}
4.1.3 测试结果
- 初始化设置
- m=8,e=8,s=10; 页框数=3,访问序列长度=20;
- 选择算法1:最佳淘汰算法OPT
- 页面置换过程
- 当发生缺页中断,需要置换时,所选择的被淘汰页面将是以后永不使用的,或者是在最长时间内不再被访问的页面。验证了程序的正确性。
- 置换率=42.1% 运行时长=0.327s
4.2 先进先出淘汰算法(FIFO)
4.2.1 原理介绍
算法规则:在发生页面替换时,被替换的对象应该是最早进入内存的。
仍然采用上述的例子,此时页面置换过程如下表所示
页面 | 7 | 0 | 1 | 2 | 0 | 3 | 0 | 4 | 2 | 3 | 0 | 3 | 2 | 1 | 2 | 0 | 1 | 7 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
页面1 | 7 | 7 | 7 | 2 | 2 | 2 | 4 | 4 | 4 | 0 | 0 | 0 | 7 | 7 | 7 | |||||
页面2 | 0 | 0 | 0 | 3 | 3 | 3 | 2 | 2 | 2 | 1 | 1 | 1 | 0 | 0 | ||||||
页面3 | 1 | 1 | 1 | 0 | 0 | 0 | 3 | 3 | 3 | 2 | 2 | 2 | 1 | |||||||
缺页 | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ |
4.2.2 代码实现
-
FIFO算法流程图如下所示:
-
FIFO
页面置换算法具体实现如下:
void FIFO(void)
{
int n, full, status;
replace_page = 0; // 页面替换指针初始化为0
page_loss_num = 0; // 缺页数初始化为0
full = 0; // 是否装满是所有的页框
for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
pageList[n].pages = -1;
is_loss_page = 0; //缺页标志,0为不缺页,1为缺页
for (current_page = 0; current_page < pInfo.visit_list_length; current_page++) // 执行算法
{
status = isExist(pInfo.pages[current_page]); //查找页面是否在内存
if (full < pInfo.allocated_page_num) // 开始时不计算缺页
{
if (status == 0) // 页不存在则装入页面
{
pageList[replace_page].pages = pInfo.pages[current_page];
replace_page = (replace_page + 1) % pInfo.allocated_page_num;
full++;
}
}
else // 正常缺页置换
{
if (status == 0) // 页不存在则置换页面
{
pageList[replace_page].pages = pInfo.pages[current_page];
replace_page = (replace_page + 1) % pInfo.allocated_page_num;
pInfo.page_missing_num++; // 缺页次数加1
}
}
Sleep(10);
showState(); // 显示当前状态
} // 置换算法循环结束
_getch();
return;
}
4.2.3 测试结果
-
初始化设置
-
m=8,e=8,s=10; 页框数=3,访问序列长度=20;
-
选择算法2:先进先出淘汰算法
-
-
置换过程
- 从如下测试结果可以看出,当需要淘汰一个页面时,总是选择驻留主存时间最长的页面进行淘汰,即先进入主存的页面先淘汰。证明了程序的正确性
- 置换率=63.2 运行时长=0.327
4.3 最近最久未使用淘汰算法(LRU)
4.3.1 原理介绍
算法规则:在发生页面替换时,被替换的页面应该满足,在之前的访问队列中,该对象截止目前未被访问的时间最长。
仍然采用上述的例子,此时页面置换过程如下表所示
页面 | 7 | 0 | 1 | 2 | 0 | 3 | 0 | 4 | 2 | 3 | 0 | 3 | 2 | 1 | 2 | 0 | 1 | 7 | 0 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
页面1 | 7 | 7 | 7 | 2 | 2 | 4 | 4 | 4 | 0 | 1 | 1 | 1 | ||||||||
页面2 | 0 | 0 | 0 | 0 | 0 | 0 | 3 | 3 | 3 | 0 | 0 | |||||||||
页面3 | 1 | 1 | 3 | 3 | 2 | 2 | 2 | 2 | 2 | 7 | ||||||||||
缺页 | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ | √ |
4.3.2 代码实现
- LRU算法流程图如下:
LRU
页面置换算法具体实现如下:
void LRU(void)
{
int n, full, status, max;
replace_page = 0; // 页面替换指针
page_loss_num = 0; // 缺页次数初始化为0
full = 0; // 是否装满所有的页框
for (n = 0; n < pInfo.allocated_page_num; n++)
{
pageList[n].pages = -1; // 清除页框信息
pageList[n].time = 0; // 清除页框历史
}
is_loss_page = 0; //缺页标志,0为不缺页,1为缺页
for (current_page = 0; current_page < pInfo.visit_list_length; current_page++) // 执行算法
{
status = isExist(pInfo.pages[current_page]); //查找页面是否在内存
if (full < pInfo.allocated_page_num) // 开始时不计算缺页
{
if (status == 0) // 页不存在则装入页面
{
pageList[replace_page].pages = pInfo.pages[current_page]; //把要调入的页面放入一个空的页框里
replace_page = (replace_page + 1) % pInfo.allocated_page_num;
full++;
}
}
else //正常缺页置换
{
if (status == 0)//页不存在则置换页面
{
max = 0;
for (n = 1; n < pInfo.allocated_page_num; n++)
{
if (pageList[n].time > pageList[max].time)
{
max = n;
}
}
replace_page = max;
pageList[replace_page].pages = pInfo.pages[current_page];
pageList[replace_page].time = 0;
pInfo.page_missing_num++; // 缺页次数加1
}
}
Sleep(10);
showState(); // 显示当前状态
} // 置换算法循环结束
_getch();
return;
}
4.3.3 测试结果
-
初始化设置
-
m=8,e=8,s=10; 页框数=3,访问序列长度=20;
-
选择算法3:先进先出淘汰算法
-
-
页面置换过程
- 如下图可以看出,当需要淘汰一个页面时,总是选择在最近一段时间内最久不用的页面予以淘汰。证明了程序的正确性。
- 置换率=57.9 运行时长=0.33
4.4 简单Clock 淘汰算法(NRU)
4.4.1 原理介绍
算法规则:简单的CLOCK算法实现方法:为每个页面设置一个访问位,再将内存中的页面都通过链接指针链接成一个循环队列。当某页被访问时,其访问位置为1。当需要淘汰一个页面时,只需检查页的访问位。如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面,若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面,因此简单的CLOCK算法选择一个淘汰页面最多会经过两轮扫描)
4.4.2 代码实现
- 简单CLOCk算法流程图如下:
- 算法具体实现如下:
CLOCK_pro
是CLOCK算法的入口参数,其中会传入参数choose。当choose =1时采用改进的clock算法,当choose =0时采用基本的clock算法。
void CLOCK_pro(int choose)
{
int n, full, status;
int num = -1;
replace_page = 0; // 页面替换指针初始化为0
page_loss_num = 0; // 缺页数初始化为0
full = 0; // 是否装满是所有的页框
for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
{
pageList[n].pages = -1;
pageList[n].isModify = 0;
pageList[n].isVisit = 0;
pageList[n].time = 0;
}
is_loss_page = 0; //缺页标志,0为不缺页,1为缺页
for (current_page = 0; current_page < pInfo.visit_list_length; current_page++) // 执行算法
{
status = isExist(pInfo.pages[current_page]); //查找页面是否在内存
if (full < pInfo.allocated_page_num) // 开始时不计算缺页
{
if (status == 0) // 页不存在则装入页面
{
pageList[replace_page].pages = pInfo.pages[current_page];
replace_page = (replace_page + 1) % pInfo.allocated_page_num;
pageList[n].isVisit = 1;
full++;
}
}
else // 正常缺页置换
{
if (status == 0) // 页不存在则置换页面
{
if(choose==1)
replace_page = replace_page_pro(++num); // 当choose =1时采用改进的clock算法
else if(choose==0)
replace_page = replace_page_clock(++num); // 当choose =0时采用基本的clock算法
pageList[replace_page].pages = pInfo.pages[current_page];
pageList[replace_page].isVisit = 1;
pInfo.page_missing_num++; // 缺页次数加1
}
}
Sleep(10);
showState(); // 显示当前状态
} // 置换算法循环结束
_getch();
return;
}
然后replace_page_clock
实现了基本CLOCK算法的置换策略,具体代码如下:
int replace_page_clock(int num) {
int j;
// 将所有可能被置换的页面排成一个循环队列,用(访问位,修改位)的形式表示各页面状态
// 1.当需要淘汰一个页面时,只需检查页的访问位。如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 )
return (j+num) % pInfo.allocated_page_num;
pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
}
//2. 若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 )
return (j + num) % pInfo.allocated_page_num;
}
}
4.4.3 测试结果
-
初始化设置
-
m=8,e=8,s=10; 页框数=3,访问序列长度=20;
-
选择算法4:基本的CLOCK算法
-
-
页面置换过程
- 如下图程序所示,正进行页面置换的过程中,总是优先淘汰未访问的页面。 证明了程序的正确性。
- 置换率=42.1 运行时长=0.323s
4.5 改进型 Clock 淘汰算法
4.5.1 原理介绍
算法规则:简单的时钟置换算法仅考虑到一个页面最近是否被访问过。事实上,如果被淘汰的页面没有被修改过,就不需要执行I/o操作写回外存。只有被淘汰的页面被修改过时,才需要写回外存。
因此,除了考虑一个页面最近有没有被访问过之外,操作系统还应考虑页面有没有被修改过。在其他条件都相同时,应优先淘汰没有修改过的页面,避免I/o操作。这就是改进型的时钟置换算法的思想。修改位=o,表示页面没有被修改过;修改位=1,表示页面被修改过。为方便讨论,用(访问位,修改位)的形式表示各页面状态。如(1,1)表示一个页面近期被访问过,且被修改过。
将所有可能被置换的页面排成一个循环队列
- 从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
- 若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0
- 若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
- 若第三轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。由于第二轮已将所有帧的访问位设为0,因此经过第三轮、第四轮扫描一定会有一个帧被选中,因此改进型CLOCK置换算法选择一个淘汰页面最多会进行四轮扫描
4.5.2 代码实现
- 改进的CLOCk算法流程图如下
- 代码具体实现如下
int replace_page_pro(int num) {
int j;
// 将所有可能被置换的页面排成一个循环队列,用(访问位,修改位)的形式表示各页面状态
// 1.从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 0)
return (j+num) % pInfo.allocated_page_num;
}
//2. 若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 1)
return (j + num) % pInfo.allocated_page_num;
pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
}
// 3.若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 0)
return (j+num) % pInfo.allocated_page_num;
}
//4. 若第三轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 1)
return (j + num) % pInfo.allocated_page_num;
// pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
}
}
4.5.3 测试结果
-
初始化设置
-
m=8,e=8,s=10; 页框数=3,访问序列长度=20;
-
选择算法5:改进的CLOCK算法
-
页面置换过程
- 如下图程序所示,正进行页面置换的过程中,总是在每次页面替换时,总是尽可能地先替换掉既未被访问又未被修改的页面。证明了程序的正确性。
- 置换率=42.1 运行时长=0.334s
4.6 算法性能对比
由于上述的生成访问随机序列较小,且只考虑了页框大小为3的情况。因此,需要改变页框数和增大访问序列来对比算法的命中率,并对不同的实验情况的置换率和系统开销求得平均值,统计如下表所示:
算法名称 \ 性能 | 置换率 | 系统开销 |
---|---|---|
OPT | 25.4% | 431ms |
FIFO | 43.1% | 336ms |
LRU | 42.7% | 362ms |
CLOCK | 35.2% | 384ms |
CLOCK_pro | 32.6% | 398ms |
-
置换率比较
最佳置换算法 < 改进型clock置换算法 <clock算法<最近最久未使用算法 <先进先出置换算法
-
系统开销比较
先进先出置换算法 < 最近最久未使用算法 < clock算法<改进型clock置换算法 < 最佳置换算法
5. 心得体会
通过这次实验我更加熟悉了操作系统页面置换算法的流程,并通过编写代码实现了5种置换算法。通过对比其置换率和系统开销,分析出了不同算法的优缺点和特性。同时也提升了我的编程能力,受益匪浅。
6. 源代码附录
#include "windows.h"
#include <conio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include <stdio.h>
#include<iostream>
#include<time.h>
using namespace std;
#define MAX 64
void FIFO(); //先进先出算法
void OPT(); //最佳置换算法
void LRU(); //最近最久未使用算法
void CLOCK_pro(int choose); //改进型Clock置换算法
void Init(); //初始化相关数据结构
void getRandomList(int m, int e, int s); //随机生成访问序列
void showState(); //显示当前状态及缺页情况
int isExist(int page); //查找页面是否在内存
bool randBool(); // 随机生成0或1
struct PageInfo //页面信息结构
{
int pages[MAX]; // 模拟的最大访问页面数
int isVisited; // 标志位,0表示无页面访问数据
int page_missing_num; // 缺页中断次数
int allocated_page_num; // 分配的页框数
int visit_list_length; // 访问页面序列长度
} pInfo;
struct MemInfo // 页表项信息
{
int time; //记录页框中数据的访问历史
int isVisit;//访问位
int isModify;//修改位
int pages;//页号,16位信息,前6位代表页号,后10位为偏移地址 0~2^16
}mInfo;
MemInfo pageList[MAX]; // 分配的页框
int page_loss_num = 0; // 缺页次数
int current_page; //页面访问指针
int replace_page; //页面替换指针
int is_loss_page; //缺页标志,0为不缺页,1为缺页
//初始化相关数据结构
void Init()
{
int i, pn;
is_loss_page = 0; //缺页标志,0为不缺页,1为缺页
pInfo.page_missing_num = 0; // 缺页次数
pInfo.isVisited = 0; // 标志位,0表示无页面访问数据
printf("请输入分配的页框数:"); // 自定义分配的页框数
scanf("%d", &pn);
pInfo.allocated_page_num = pn;
for (i = 0; i < MAX; i++) // 清空页面序列
{
pInfo.pages[i] = -1;
}
}
// 随机生成访问序列
void getRandomList(int m, int e, int s)
{
int pl;
Init(); //初始化
printf("请输入访问序列的长度:");
scanf("%d", &pl); //随机生成访问序列的长度
pInfo.visit_list_length = pl;
srand((unsigned)time(NULL)); // 随机种子
int p = rand() % MAX; //在0——MAX范围内随机初始化工作集的起始位置p
int i, j=0;
double t= rand() % 10 / 10.0; // 一个范围在0和1之间的值t
for (i = 0; i < s; i++) // 重复s次
{
if (j > pInfo.visit_list_length) // 不能超过访问序列的长度
break;
for (j = i * m; j < (i + 1) * m; j++)//生成m个取值范围在p~p + e间的随机数
{
pInfo.pages[j]= (p + (rand() % e)) % MAX; //并记录到页面访问序列串中;
}
double r = (rand() % 10) / 10.0;// 生成一个范围在0-1之间的随机数r
if (r < t) // 如果r < t,则为p生成一个新值
p = rand() % MAX;
else
{
p = (p + 1) % MAX; //否则向后循环移位
}
}
}
// 显示当前状态及缺页情况
void showState(void)
{
int i, n;
if (current_page == 0)
{
printf("\n~~~~~~~~~~~~~~~~~页面访问序列~~~~~~~~~~~~~~~~~\n");
for (i = 0; i < pInfo.visit_list_length; i++)
{
printf("%4d", pInfo.pages[i]);
if ((i + 1) % 10 == 0) printf("\n"); //每行显示10个
}
printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n");
}
printf("访问%3d -->内存空间[", pInfo.pages[current_page]);
for (n = 0; n < pInfo.allocated_page_num; n++) // 页框信息
{
if (pageList[n].pages >= 0)
printf("%3d", pageList[n].pages);
else
printf(" ");
}
printf(" ]");
if (is_loss_page == 1) //缺页标志,0为不缺页,1为缺页
{
printf(" --> 缺页中断 --> ");
if (current_page == 0)
{
printf("置换率 = %3.1f ", (float)(pInfo.page_missing_num) * 100.00 );
}
else{
printf("置换率 = %3.1f", (float)(pInfo.page_missing_num) * 100.00 / current_page);
}
}
printf("\n");
}
// 查找页面是否在内存,1为在内存,0为不在即缺页
int isExist(int page)
{
int n;
for (n = 0; n < pInfo.allocated_page_num; n++)
{
pageList[n].time++; // 访问历史加1
}
for (n = 0; n < pInfo.allocated_page_num; n++)
{
if (pageList[n].pages == page)
{
is_loss_page = 0; //is_loss_page缺页标志,0为不缺页,1为缺页
pageList[n].time = 0; //置访问历史为0
if (randBool()) {
pageList[n].isVisit = 1;
pageList[n].isModify = 1;
}
else {
pageList[n].isVisit = 1;
}
return 1;
}
}
is_loss_page = 1; //页面不存在,缺页
return 0;
}
// FIFO页面置换算法
void FIFO(void)
{
int n, full, status;
replace_page = 0; // 页面替换指针初始化为0
page_loss_num = 0; // 缺页数初始化为0
full = 0; // 是否装满是所有的页框
for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
pageList[n].pages = -1;
is_loss_page = 0; //缺页标志,0为不缺页,1为缺页
for (current_page = 0; current_page < pInfo.visit_list_length; current_page++) // 执行算法
{
status = isExist(pInfo.pages[current_page]); //查找页面是否在内存
if (full < pInfo.allocated_page_num) // 开始时不计算缺页
{
if (status == 0) // 页不存在则装入页面
{
pageList[replace_page].pages = pInfo.pages[current_page];
replace_page = (replace_page + 1) % pInfo.allocated_page_num;
full++;
}
}
else // 正常缺页置换
{
if (status == 0) // 页不存在则置换页面
{
pageList[replace_page].pages = pInfo.pages[current_page];
replace_page = (replace_page + 1) % pInfo.allocated_page_num;
pInfo.page_missing_num++; // 缺页次数加1
}
}
Sleep(10);
showState(); // 显示当前状态
} // 置换算法循环结束
_getch();
return;
}
// OPT 页面置换算法
void OPT()
{
int n, full, status;
replace_page = 0; // 页面替换指针初始化为0
page_loss_num = 0; // 缺页数初始化为0
full = 0; // 是否装满是所有的页框
for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
{
pageList[n].pages = -1;
}
is_loss_page = 0; //缺页标志,0为不缺页,1为缺页
for (current_page = 0; current_page < pInfo.visit_list_length; current_page++) // 执行算法
{
status = isExist(pInfo.pages[current_page]); //查找页面是否在内存
if (full < pInfo.allocated_page_num) // 开始时不计算缺页
{
if (status == 0) // 页不存在则装入页面
{
pageList[replace_page].pages = pInfo.pages[current_page];
replace_page = (replace_page + 1) % pInfo.allocated_page_num;
full++;
}
}
else // 正常缺页置换
{
if (status == 0) // 页不存在,则置换页面
{
int min,max = 0 ; //很大的数
for (int m = 0; m < pInfo.allocated_page_num ; m++)
{
min = 1000;
for (int n = current_page; n < pInfo.visit_list_length; n++)
{
if (pInfo.pages[n] == pageList[m].pages)
{
min = n;
break;
}
}
if (max < min)
{
max = min;
replace_page = m;
}
}
pageList[replace_page].pages = pInfo.pages[current_page];
replace_page = (replace_page + 1) % pInfo.allocated_page_num;
pInfo.page_missing_num++; // 缺页次数加1
}
}
Sleep(10);
showState(); // 显示当前状态
} // 置换算法循环结束
_getch();
return;
}
//查找替换页面算法,递归实现
int replace_page_pro(int num) {
int j;
// 将所有可能被置换的页面排成一个循环队列,用(访问位,修改位)的形式表示各页面状态
// 1.从当前位置开始扫描到第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 0)
return (j+num) % pInfo.allocated_page_num;
}
//2. 若第一轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。本轮将所有扫描过的帧访问位设为0
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 1)
return (j + num) % pInfo.allocated_page_num;
pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
}
// 3.若第二轮扫描失败,则重新扫描,查找第一个(0,0)的帧用于替换。本轮扫描不修改任何标志位
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 0)
return (j+num) % pInfo.allocated_page_num;
}
//4. 若第三轮扫描失败,则重新扫描,查找第一个(0,1)的帧用于替换。
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 && pageList[(j + num) % pInfo.allocated_page_num].isModify == 1)
return (j + num) % pInfo.allocated_page_num;
// pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
}
}
int replace_page_clock(int num) {
int j;
// 将所有可能被置换的页面排成一个循环队列,用(访问位,修改位)的形式表示各页面状态
// 1.当需要淘汰一个页面时,只需检查页的访问位。如果是0,就选择该页换出;如果是1,则将它置为0,暂不换出,继续检查下一个页面
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 )
return (j+num) % pInfo.allocated_page_num;
pageList[(j + num) % pInfo.allocated_page_num].isVisit = 0;
}
//2. 若第一轮扫描中所有页面都是1,则将这些页面的访问位依次置为0后,再进行第二轮扫描(第二轮扫描中一定会有访问位为0的页面
for (j = 0; j < pInfo.allocated_page_num; j++){
if (pageList[(j + num) % pInfo.allocated_page_num].isVisit == 0 )
return (j + num) % pInfo.allocated_page_num;
}
}
//随机化函数
bool randBool()
{
if ((rand() % 2 + 1) == 1)
return true;
else
return false;
}
void CLOCK_pro(int choose)
{
int n, full, status;
int num = -1;
replace_page = 0; // 页面替换指针初始化为0
page_loss_num = 0; // 缺页数初始化为0
full = 0; // 是否装满是所有的页框
for (n = 0; n < pInfo.allocated_page_num; n++) // 清除页框信息
{
pageList[n].pages = -1;
pageList[n].isModify = 0;
pageList[n].isVisit = 0;
pageList[n].time = 0;
}
is_loss_page = 0; //缺页标志,0为不缺页,1为缺页
for (current_page = 0; current_page < pInfo.visit_list_length; current_page++) // 执行算法
{
status = isExist(pInfo.pages[current_page]); //查找页面是否在内存
if (full < pInfo.allocated_page_num) // 开始时不计算缺页
{
if (status == 0) // 页不存在则装入页面
{
pageList[replace_page].pages = pInfo.pages[current_page];
replace_page = (replace_page + 1) % pInfo.allocated_page_num;
pageList[n].isVisit = 1;
full++;
}
}
else // 正常缺页置换
{
if (status == 0) // 页不存在则置换页面
{
if(choose==1)
replace_page = replace_page_pro(++num); // 当choose =1时采用改进的clock算法
else if(choose==0)
replace_page = replace_page_clock(++num); // 当choose =0时采用基本的clock算法
pageList[replace_page].pages = pInfo.pages[current_page];
pageList[replace_page].isVisit = 1;
pInfo.page_missing_num++; // 缺页次数加1
}
}
Sleep(10);
showState(); // 显示当前状态
} // 置换算法循环结束
_getch();
return;
}
// LRU页面置换算法
void LRU(void)
{
int n, full, status, max;
replace_page = 0; // 页面替换指针
page_loss_num = 0; // 缺页次数初始化为0
full = 0; // 是否装满所有的页框
for (n = 0; n < pInfo.allocated_page_num; n++)
{
pageList[n].pages = -1; // 清除页框信息
pageList[n].time = 0; // 清除页框历史
}
is_loss_page = 0; //缺页标志,0为不缺页,1为缺页
for (current_page = 0; current_page < pInfo.visit_list_length; current_page++) // 执行算法
{
status = isExist(pInfo.pages[current_page]); //查找页面是否在内存
if (full < pInfo.allocated_page_num) // 开始时不计算缺页
{
if (status == 0) // 页不存在则装入页面
{
pageList[replace_page].pages = pInfo.pages[current_page]; //把要调入的页面放入一个空的页框里
replace_page = (replace_page + 1) % pInfo.allocated_page_num;
full++;
}
}
else //正常缺页置换
{
if (status == 0)//页不存在则置换页面
{
max = 0;
for (n = 1; n < pInfo.allocated_page_num; n++)
{
if (pageList[n].time > pageList[max].time)
{
max = n;
}
}
replace_page = max;
pageList[replace_page].pages = pInfo.pages[current_page];
pageList[replace_page].time = 0;
pInfo.page_missing_num++; // 缺页次数加1
}
}
Sleep(10);
showState(); // 显示当前状态
} // 置换算法循环结束
_getch();
return;
}
// 主函数
int main()
{
char ch;
system("cls");
printf("**********页面置换算法********\n");
int m,e,s;
printf("请输入工作集移动率(m):");
scanf("%d",&m); //8
printf("请输入工作集包含的页数(e):");
scanf("%d",&e); //8
printf("请输入重复次数(s):");
scanf("%d",&s); // 10
getRandomList(m, e, s); // 随机生成访问序列
clock_t start, finish;
double totaltime;
while (true)
{
printf("====================MENU=========================\n");
printf("1. 最佳淘汰算法(OPT)\n");
printf("2. 先进先出淘汰算法(FIFO)\n");
printf("3. 最近最久未使用淘汰算法(LRU)\n");
printf("4. 基本的Clock 淘汰算法\n");
printf("5. 改进型的Clock 淘汰算法\n");
printf("=================================================\n");
printf("请输入选择的算法(1/2/3/4/5): ");
ch = (char)_getch();
switch (ch) {
case '1':
printf("\n《------------1. 最佳淘汰算法(OPT)--------------》\n");
start = clock();
OPT();
finish = clock();
totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "\n运行总时长= " << totaltime << " 秒" << endl;
break;
case '2':
printf("\n\n《----------2. 先进先出淘汰算法(FIFO)------------》\n");
start = clock();
FIFO();
finish = clock();
totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "\n运行总时长= " << totaltime << " 秒" << endl;
break;
case '3':
printf("\n\n《--------3. 最近最久未使用淘汰算法(LRU)-----------》\n");
start = clock();
LRU();
finish = clock();
totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "\n运行总时长= " << totaltime << " 秒" << endl;
break;
case '4':
printf("\n\n《------------ 4. Clock 淘汰算法--------------》\n");
start = clock();
CLOCK_pro(0);
finish = clock();
totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "\n运行总时长= " << totaltime << "秒" << endl;
break;
case '5':
printf("\n\n《------------ 5. 改进的Clock 淘汰算法--------------》\n");
start = clock();
CLOCK_pro(1);
finish = clock();
totaltime = (double)(finish - start) / CLOCKS_PER_SEC;
cout << "\n运行总时长= " << totaltime << "秒" << endl;
break;
default:
return 0;
}
}
return 0;
}
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)