操作系统之走进页面置换算法
概要:本文章将通过页面访问序列随机发生器完成如下五种算法,并从缺页率、算法开销等行能方面比较这五种算法。
- 最佳置换算法
- 先进先出置换算法
- 最近最久未使用置换算法
- 改进型Clock置换算法
- 页面缓冲置换算法
生成随机序列
页面访问的序列串是一个整数序列,需要我们编写函数生成,要求整数的取值范围为0-N-1。页面访问序列串中的每个元素p表示对页面p的一次访问。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步,否则结束。
随机数生成代码如下:首先输入要求的虚拟内存的尺寸N,工作集的起始位置p,工作集中包含的页数e,工作集移动率m。
void randOccur() {
cout << "请输入生成的页面访问序列的个数:";
cin>>NUM;
cout << "请输入工作集起始位置:";
cin >> p;
cout << "请输入的工作集页数:";
cin >> pageNum;
cout << "请输入工作集移动率:";
cin >> speed;
srand((int)time(0));
int j = 0;
for (int i = 0;i<NUM;i++) {
if (j < speed) {
int x = p + pageNum, y = N - 1;
list[i] = rand() % (min(x,y) - p + 1) + p;
double sn = (rand()%10)*0.1;
if (sn > 0.7) {
wrList[i] = 1;//以写方式访问
}
else {
wrList[i] = 0;//以读方式访问
}
j++;
}
else {
r = (rand()%10)*0.1;
j = 0;
if (r < t) {
p = rand()%N;
}
else {
p = (p + 1) % N;
}
}
}
}
将生成的随机序列输出
void listPrint() {
cout << "随机序列如下:" ;
for (int i = 0; i < 32; i++) {
cout << list[i] << " ";
}
cout << endl;
}
举例生成结果:
最佳置换算法
选择以后永不使用的,或是在最长(未来)时间内不再被访问的页面作为被淘汰页面。采用最佳置换算法的优点是:通常可保证最低的缺页率。这是一个理想的算法,但是却很难实现,因为我们很难知道一个进程在内存的若干个页面中,哪一个页面是未来最长时间内不再被访问的,因而该算法是无法实现的,但是我们可以利用该算法取评价其他的算法。
OPT算法实现如下
void OPT() {
pre();
t1 = clock();
missCount = 0;
int flag=-1;
for (int i = 0; i <NUM; i++) {
if (memcount < 3) {
mem[memcount] = list[i];
memcount++;
}
else {
flag = judge(i);
if (flag == 0) {
if (memcount < memNum) {
mem[memcount] = list[i];
memcount++;
}
else {
search(i);
missCount++;
}
}
}
}
t2 = clock();
missRag = missCount * 1.0 / NUM;
a1 = missRag;
b1 = double(t2 - t1) / CLOCKS_PER_SEC;
}
先进先出置换算法
先进先出置换算法是最简单的页面置换算法,只需要优先淘汰最早进入内存的页面,也就是 在内存中驻留时间最久的页面。需把调入内存的页面根据先后次序链接成队列,设置一个指针总指向最早的页面。
FIFO程序实现如下:
void FIFO(){
pre();
prd();
t1 = clock();
missCount = 0;
int flag = -1;
for (int i = 0; i < NUM; i++) {
if (memcount < 3) {
mem[memcount] = list[i];
add(memcount);
memcount++;
}
else {
flag = judge(i);
if (flag == 0) {
if (memcount < memNum) {
mem[memcount] = list[i];
add(memcount);
memcount++;
}
else {
int min = 0, cur = 0;
for (int k = 0; k < memNum; k++) {
if (first[k] > min) {
cur = k;
min = first[k];
}
}
first[cur] = 0;
add(memNum);
mem[cur] = list[i];
missCount++;
}
}
}
}
t2 = clock();
missRag = missCount/ NUM;
a2 = missRag;
b2 = double(t2 - t1) / CLOCKS_PER_SEC;
}
需要在函数开始前对该算法的计数数组进行预处理
int first[MAX];
void prd() {
for (int i = 0; i < memNum; i++) {
first[i] = 0;
}
}
void add(int n) {
for (int i = 0; i <= n; i++) {
first[i]++;
}
}
最近最久未使用置换算法
最近最久未使用算法,要求我们根据页面调入内存后的使用情况。然后再利用“最近的过去”作为“最近的将来”的近似。淘汰最近最久未使用的页面。该算法是对最优置换算法的近似。因为程序具有局部性原理,所以推测如果最近一段时间内某些页面被频繁访问,那么在将来该页面还可能被频繁访问。
我将代码分为两个函数,首先第一个函数负责找到最久没有被访问的页面元素。通过双重for循环来达到查找的目的。程序如下:
void searchL(int wei) {
int cur = 0;
int endf[MAX];
int min = MAX;
for (int i = 0; i < memNum; i++) {
endf[i] = -1;
}
for (int j = 0; j < memNum; j++) {
for (int i = wei + 1; i > -1; i--) {
if (mem[j] == list[i]) {
endf[j] = i;
break;
}
}
}
for (int i = 0; i < memNum; i++) {
if (endf[i] < min) {
cur = i;
min = endf[i];
}
}
mem[cur] = list[wei];
}
LRU代码实现如下:
void LRU() {
pre();
t1 = clock();
missCount = 0;
int flag = -1;
for (int i = 0; i < NUM; i++) {
if (memcount < 3) {
mem[memcount] = list[i];
memcount++;
}
else {
flag = judge(i);
if (flag == 0) {
if (memcount < memNum) {
mem[memcount] = list[i];
memcount++;
}
else {
searchL(i);
missCount++;
}
}
}
}
t2 = clock();
missRag = missCount * 1.0 / NUM;
a3 = missRag;
b3 = double(t2 - t1) / CLOCKS_PER_SEC;
}
改进型Clock置换算法
从查寻指针当前位置起扫描内存分页循环队列,选择A=0且M=0的第一个页面淘汰;若未找到,转②② 开始第二轮扫描,选择A=0且M=1的第一个页面淘汰,同时将经过的所有页面访问位置0;若不能找到,转①。优点:同未改进的clock相比较,减少磁盘的I/O操作次数。缺点:淘汰页的选择可能经历多次扫描,故实现算法自身的开销增大
代码实现如下:
void CLOCK() {
pre();
id = -1;
for (int i = 0; i < memNum; i++) {
states[i] = 0;
}
t1 = clock();
missCount = 0;
int flag = -1;
for (int i = 0; i < NUM; i++) {
if (memcount < 3) {
mem[memcount] = list[i];
states[memcount] = 1;
memcount++;
}
else {
flag = judge(i);
if (flag == 0) {
if (memcount < memNum) {
mem[memcount] = list[i];
states[memcount] = 1;
memcount++;
}
else {
Cloop(id,i);
missCount++;
}
}
}
}
t2 = clock();
missRag = missCount * 1.0 / NUM;
a4 = missRag;
b4 = double(t2 - t1) / CLOCKS_PER_SEC;
}
页面缓冲置换算法
算法思路:首先设立空闲页面链表和已修改页面链表,采用可变分配和基于先进先出的局部置换策略,并规定被淘汰页先不做物理移动,而是依据是否修改分别挂到空闲页面链表或已修改页面链表的末尾,空闲页面链表同时用于物理块分配。当已修改页面链表达到一定长度如Z个页面时,一起将所有已修改页面写回磁盘,故可显著减少磁盘I/O操作次数。为每个页面配置一个计数器,一旦某页被访问,则将其计数器的值加1,在需要选择一页置换时,则将选择其计数器值最小的页面,即内存中访问次数最少的页面进行淘汰。
实现代码如下:
void PBA() {
pre();
id = -1;
for (int i = 0; i < memNum; i++) {
states[i] = 0;
}
t1 = clock();
missCount = 0;
int flag = -1;
for (int i = 0; i < NUM; i++) {
if (memcount < 3) {
mem[memcount] = list[i];
states[memcount] = 1;
memcount++;
}
else {
flag = judge(i);
if (flag == 0) {
if (memcount < memNum) {
mem[memcount] = list[i];
states[memcount] = 1;
memcount++;
}
else {
Gloop(id, i);
missCount++;
}
}
}
}
t2 = clock();
missRag = missCount * 1.0 / NUM;
a5 = missRag;
b5 = double(t2 - t1) / CLOCKS_PER_SEC;
}
结果分析
运行事例1
运行事例2
运行事例3
分析结果:根据每次生成随机不同的序列所得到的不同的缺页率和时间开销我们发现,OPT最佳置换算法的缺页率较低性能在四种算法中较高,其次是PBA即页面缓冲置换算法的性能较次于OPT,再其次是改进的clock算法,时间开销每次都很大且缺页率较高性能最不好的是LRU算法即最近最久未使用算法以及FIFO算法即先进先出置换算法。