Linux系统中动态分区分配方式和页面置换算法模拟
一、实验目的和要求
1.掌握动态分区分配方式使用的数据结构和分配算法(首次/最佳 /最坏适应算法)。
2.进一步加深对动态分区分配管理方式及其实现过程的理解。
3.理解虚拟内存管理的原理和技术。
4.掌握请求分页存储管理的常用理论-页面置换算法。
5.理解请求分页中的按需调页机制。
二、实验设备和环境
硬件环境:HuaWei MateBook 14
软件环境:RedHat CentOS 7.0操作系统与g++编译环境
三、动态分区分配实验:实验内容
编写C 程序,模拟实现首次/最佳/最坏适应算法的内存块分配与回收,要求每次分配与回收后显示出空闲分区和已分配分区的情况。假设在初始状态下,可用的内存空间为640KB 。
四、动态分区分配实验:实验步骤
定义的结构体
struct node{
int left,right;
int vis; //0表示未使用内存;1表示已经使用了内存空间;
int work;
bool operator < (const node &t) const
{
return left<t.left;
}
}node[1010];
首次适应算法
空闲分区以地址递增的次序排列。每次分配内存时顺序查找空闲分区链(或空闲分区表),找到大小能满足要求的第一个空闲分区。
运行步骤
先选择你要执行的算法:1.首次适应算法;2。最佳分配算法;3.退出
之后再选择在该算法下要执行的操作:1.分配内存;2.回收内存;3.查看内存情况;4.退出
查看分区情况,总的分区设置为640kB,呈现结果包括分区的占有情况起始地址,大小
五、动态分区分配实验:代码及注释
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll maxn=1e18;
const ll minn=-1e18;
const ll mod=1000000007;
const ll MAX=100005;
bool cmp(ll a,ll b){return a>b;}
/*拟实现首次/最佳/最坏适应算法的内存块分配和回收,要求每次分配和回收后显示出空闲分区和已分配分区的情况。
假设初始状态下,可用的内存空间为640KB。*/
int n; //初始空间
string algin;
int pos=1; //记录当前内存块的个数;
struct node{
int left,right;
int vis; //0表示未使用内存;1表示已经使用了内存空间;
int work;
bool operator < (const node &t) const
{
return left<t.left;
}
}node[1010];
void opin(int nu,int nulen) //插入
{
string FIR="FIR";
string BST="BST";
string WST="WST";
sort(node+1,node+pos+1);
if(algin==FIR) //首次
{
for(int i=1 ; i<=pos ; i++)
{
if(node[i].right-node[i].left>=nulen&&node[i].vis==0)
{
pos++;
node[pos].left=node[i].left;
node[pos].right=node[pos].left+nulen;
node[pos].vis=1;
node[pos].work=nu;
node[i].left=node[pos].right;
cout<<nu<<"的内存分配成功!"<<endl;
return ;
}
}
cout<<endl<<nu<<"的内存分配失败!"<<endl;
}
else if(algin==BST) //最优
{
vector<int> temp;
for(int i=1 ; i<=pos ; i++)
{
if(node[i].right-node[i].left==nulen&&node[i].vis==0) //刚好的时候一定是最优;
{
node[i].vis=1;
node[i].work=nu;
cout<<nu<<"的内存分配成功!"<<endl;
return ;
}
else if(node[i].vis==0)
{
temp.push_back(i);
}
}
int len=temp.size();
sort(temp.begin(),temp.end()); //升序;
for(int i=0 ; i<len ; i++)
{
if(node[temp[i]].right-node[temp[i]].left>nulen)
{
pos++;
node[pos].left=node[temp[i]].left;
node[pos].right=node[pos].left+nulen;
node[pos].vis=1;
node[pos].work=nu;
node[temp[i]].left=node[pos].right;
cout<<nu<<"的内存分配成功!"<<endl;
return ;
}
}
cout<<endl<<nu<<"的内存分配失败!"<<endl;
}
else if(algin==WST) //最坏
{
vector<int> temp;
for(int i=1 ; i<=pos ; i++)
{
if(node[i].vis==0)
{
temp.push_back(i);
}
}
int len=temp.size();
sort(temp.begin(),temp.end(),cmp); //降序;
for(int i=0 ; i<len ; i++)
{
if(node[temp[i]].right-node[temp[i]].left>=nulen)
{
if(node[temp[i]].right-node[temp[i]].left==nulen) //相等
{
node[temp[i]].vis=1;
node[temp[i]].work=nu;
cout<<nu<<"的内存分配成功!"<<endl;
return ;
}
else //大于
{
pos++;
node[pos].left=node[temp[i]].left;
node[pos].right=node[pos].left+nulen;
node[pos].vis=1;
node[pos].work=nu;
node[temp[i]].left=node[pos].right;
cout<<nu<<"的内存分配成功!"<<endl;
return ;
}
}
}
cout<<endl<<nu<<"的内存分配失败!"<<endl;
}
}
void opde(int nu) //回收
{
sort(node+1,node+pos+1);
for(int i=1 ; i<=pos ; i++)
{
if(node[i].work==nu)
{
node[i].vis=0;
node[i].work=0;
int temp=0;
for(int j=i-1 ; j>=1 ; j--) //从后往前找
{
if(node[j].vis!=0)
break;
else
{
node[i].left=node[j].left;
node[j].left=n;
node[j].right=n;
temp++;
}
}
for(int j=i+1 ; j<=pos ; j++) //从前往后找
{
if(node[j].vis!=0)
break;
else
{
node[i].right=node[j].right;
node[j].left=n+1;
node[j].right=n+1;
temp++;
}
}
sort(node,node+pos);
pos-=temp;
}
}
}
/*
void printtest()
{
sort(node+1,node+pos+1);
for(int i=1 ; i<=pos ; i++)
{
cout<<i<<" : "<<node[i].work<<" "<<node[i].left<<" "<<node[i].right<<" "<<node[i].right-node[i].left<<" "<<node[i].vis<<endl;
}
}
*/
void print()
{
sort(node+1,node+pos+1);
cout<<endl;
for(int i=1 ; i<=pos ; i++)
{
cout<<"|<-";
for(int j=0 ; j<(node[i].right-node[i].left)/20-5; j++)
cout<<" ";
if(node[i].vis==0)
cout<<"None";
else
cout<<"work"<<node[i].work;
for(int j=0 ; j<(node[i].right-node[i].left)/20-4; j++)
cout<<" ";
if(i==pos)
cout<<"->|"<<endl;
else
cout<<"->";
}
//底线
for(int i=0 ; i<(node[pos].right-node[1].left)/10 ; i++)
cout<<"-";
cout<<endl;
cout<<node[1].left;
for(int i=1 ; i<=pos ; i++)
{
for(int j=0 ; j<(node[i].right-node[i].left)/10-3 ; j++)
cout<<" ";
cout<<node[i].right;
}
cout<<endl;
cout<<endl;
}
void fi(int n) //初始化
{
node[0].left=0;
node[0].right=0;
node[0].vis=0;
node[0].work=0;
node[pos].left=node[pos-1].right;
node[pos].right=n;
node[pos].vis=0;
node[pos].work=0;
print();
}
void init()
{
cout<<"请输入初始系统空间大小:"<<endl;
cin>>n;
fi(n);
int t;
while(1)
{
cout<<"请输入操作:1-插入 2-删除 0-退出"<<endl;
cin>>t;
if(t==0)
break;
else if(t==1)
{
cout<<"请输入插入所适应算法:FIR-首次 BST-最佳 WST-最坏:"<<endl;
cin>>algin;
cout<<"请输入需要分配的作业号和大小"<<endl;
int nu,nulen;
cin>>nu>>nulen;
opin(nu,nulen);
//printtest();
print();
}
else if(t==2)
{
cout<<"请输入需要回收的作业号"<<endl;
int nu;
cin>>nu;
opde(nu);
print();
//printtest();
}
}
}
int main()
{
init();
return 0;
}
六、动态分区分配实验:实验结果
实现了首次适应算法和最佳/坏分配算法的运行截图
图3.1 动态分区分配_首次适应算法的分配运行结果
图3.2动态分区分配_回收运行结果
图3.3 动态分区分配_最佳适应算法的分配运行结果
图3.4 动态分区分配_最坏适应算法的分配运行结果_1
图3.4 动态分区分配_最坏适应算法的分配运行结果_2
七、页面置换算法模拟实验:实验内容
设计一个虚拟存储区和一个内存工作区,并使用下述常用页面置换算法计算访问命中率。
FIFO算法
LRU算法
OPT算法
要求
通过随机数产生一个指令序列,里面共320 条指令。
将指令序列转换成页面序列。假设:1. 页面大小为1 KB; 2. 用户内存容量为4~32 页; 3. 用户虚存容量为32 KB 。在用户虚存中,按每页存放10 条指令排列虚存地址,因此320条指令将存放在32 个页面中。
计算并输出不同页面置换算法在不同内存容量下的访问命中率。访问命中率的计算公式为:
访问命中率=1-页面失效次数/页面总数
八、页面置换算法模拟实验:实验步骤
- 首先使用随机函数srand()和rand()随机产生指令序列,然后将指令序列转换成相应 的页面序列。
- 设计页面类型、页面控制结构等数据结构。
- 计算使用指定页面置换算法时的访问命中率。
4)指令地址是按如下原则产生的: 50%的指令是顺序执行的;@25 %的指令均匀地分布在前地址部分;@25 %的指令均匀地 分布在后地址部分
文件运行后提示输入第一条指令号,之后通过随机数产生一个指令序列,里面共320 条指令,将指令序列转换成页面序列,在提示下输入第页框大小,根据不同的算法可以输出每一步页面置换命中的情况
九、页面置换算法模拟实验:代码及注释
九、页面置换算法模拟实验:代码及注释
#include<stdio.h>
#include<stdlib.h>
#define N 320
int num[N]; //存放随机数
int page[N]; //存放页地址流
int mc[33]; //memory capacity内存容量 ,并初始化为0
void randomnumber()//random number随机数 程序第一步,产生320个指令序列
{
int pc;
int flag=0;
scanf("%d",&pc);
printf("\n在0-319之间产生的320个随机数如下:\n");
for(int i=0;i<320;i++)
{
num[i]=pc;
if(flag%2==0) pc=++pc%320; //flag=0||2 50%的指令是顺序执行的
if(flag==1) pc=rand()% (pc-1); //flag=1 25%的指令是均匀分布在前地址部分
if(flag==3) pc=pc+1+(rand()%(320-(pc+1))); //flag=3 25%的指令是均匀分布在后地址部分
flag=++flag%4;
printf("%3d ",num[i]);
if((i+1)%10==0) printf("\n"); //每行输出10个数
}
}
void pageaddress() //pageaddress页地址 程序第二步,将指令序列变换为页地址流
{
for(int i=0;i<320;i++)
{
printf("%3d ",page[i]=num[i]/10);
if((i+1)%10==0) printf("\n"); //每行输出10个数
}
}
int FIFO(int capacity)
{
int j,x,y,m;
int sum=0; //mc中分配的个数
int exist=0; //命中次数
int flag; //标志是否命中 flag=0没命中 flag=1命中
int ep=1; //elimination position淘汰位置
mc[1]=page[0];
printf(" %2d加入 \t",page[0]);
for(m=1;m<=capacity;m++) //输出当前内存块的存储情况
printf("%2d ",mc[m]);
printf("\n");
sum+=1;
for(j=1;j<320;j++)
{
flag=0;
for(y=1;y<=sum;y++) //判断这个页地址流是否命中
if(mc[y]==page[j]) {
exist++;
flag=1;
printf(" %2d命中 \t",page[j]);
for(m=1;m<=capacity;m++) //输出当前内存块的存储情况
printf("%2d ",mc[m]);
printf("\n");
break;}
//没命中
if(flag==0)
{
if(sum<capacity) //还有空块
{for(x=1;x<=capacity;x++) //查找内存块中第一个空块
if(mc[x]==-1) {
mc[x]=page[j];
sum++;
printf(" %2d加入 \t",page[j]);
for(m=1;m<=capacity;m++) //输出当前内存块的存储情况
printf("%2d ",mc[m]);
printf("\n");
break;}
}
else if(sum>=capacity)
{
int t=mc[ep];
mc[ep]=page[j];
printf(" %2d置换%2d\t",page[j],t);
for(m=1;m<=capacity;m++) //输出当前内存块的存储情况
printf("%2d ",mc[m]);
printf("\n");
ep+=1;
if(ep==capacity+1) ep=1;
}
}
}
printf("\nexist=%d\n命中率=%lf",exist,exist/320.0);
}
int main()
{
int capacity; //内存块数
printf("请输入第一条指令号(0~319):");
randomnumber();
printf("\n指令序列对应的页地址流:\n");
pageaddress();
printf("\n\n\n\t\t先进先出算法(FIFO):\n\n");
printf("请输入内存块数(4-32):");
scanf("%d",&capacity);
for(int i=1;i<=32;i++) //给数组赋初值
mc[i]=-1;
FIFO(capacity);
return 0;
}
十、页面置换算法模拟实验:实验结果
第一种页面置换算法FIFO
图3.2FIFO页面置换代码的运行结果_1
图3.2FIFO页面置换的运行结果_2
十一、实验思考
1、 如何修改你的程序,使分配内存时从低地址开始。
首次适应算法中空闲区的排列顺序是按地址大小排列,小地址在前
2、 如何完善你的程序,以实现基于最佳适应算法和最坏适应算法的内存分配与回收。
基于最佳适应算法
最佳适应算法(Best Fit):该算法总是把既能满足要求,又是最小的空闲分区分配给作业。为了加速查找,该算法要求将所有的空闲区按其大小排序后,以递增顺序形成一个空白链。这样每次找到的第一个满足要求的空闲区,必然是最优的。孤立地看,该算法似乎是最优的,但事实上并不一定。因为每次分配后剩余的空间一定是最小的,在存储器中将留下许多难以利用的小空闲区。同时每次分配后必须重新排序,
基于最坏适应算法
最坏适应算法是将输入的作业放置到主存中与它所需大小差距最大的空闲区中。空闲区大小由大到小排序
3、 如何实现计算使用LRU 和OPT 算法时的访问命中率。
实现LRU算法
只需要将上述代码中FIFO(compacity)方法换成以下代码
void LRU(int capacity) {
int j, x, y, m;
int sum = 0; //mc中分配的个数
int exist = 0; //命中次数
int flag; //标志是否命中 flag=0没命中 flag=1命中
int time[33]; //各个物理块最近一次访问至现在的时间
int store = 1; //存储当前置换块的位置
for (int i = 1; i <= capacity; i++)
time[i] = 0;
mc[1] = page[0];
printf(" %2d加入 \t", page[0]);
for (m = 1; m <= capacity; m++)//输出当前内存块的存储情况
printf("%2d ", mc[m]);
printf("\n");
sum += 1;
for (j = 1; j < 320; j++) {
flag = 0;
for (y = 1; y <= sum; y++) { //判断这个页地址流是否命中
time[y]++;
if (mc[y] == page[j]) {
exist++;
time[y] = 0; //LRU计数器清零
flag = 1;
printf(" %2d命中 \t", page[j]);
for (m = 1; m <= capacity; m++) //输出当前内存块的存储情况
printf("%2d ", mc[m]);
printf("\n");
break;
}
}
//没命中
if (flag == 0) {
if (sum < capacity) {//还有空块
for (x = 1; x <= capacity; x++) //查找内存块中第一个空块
if (mc[x] == -1) {
mc[x] = page[j];
sum++;
printf(" %2d加入 \t", page[j]);
for (m = 1; m <= capacity; m++)//输出当前内存块的存储情况
printf("%2d ", mc[m]);
printf("\n");
break;
}
}
else if (sum >= capacity) {
int max = 0, temp = 0;
for (int i = store; i <= capacity + store - 1; i++) {
max = time[(i - 1) % capacity + 1] > max ? time[(i - 1) % capacity + 1] : max;
} for (int i = store; i <= capacity + store - 1; i++) {
if (time[(i - 1) % capacity + 1] == max) {
temp = (i - 1) % capacity + 1;
store = temp % capacity + 1;
break;
}
}
int t = mc[temp];
mc[temp] = page[j];
printf(" %2d置换%2d\t", page[j], t);
time[temp] = 0;
for (m = 1; m <= capacity; m++) //输出当前内存块的存储情况
printf("%2d ", mc[m]);
printf("\t\t\t");
for (m = 1; m <= capacity; m++) //输出当前内存块的存储情况
printf("%2d ", time[m]);
// printf("%d", store);
printf("\n");
}
}
}
printf("\nexist=%d\n命中率=%lf\n", exist, exist / 320.0);
}
输出结果如下
图3.4 LRU页面置换的运行结果
**实现OPT算法
同样将FIFO代码换成如下代码
**
int OPT(int capacity)
{
int j, x, y, m;
int sum = 0; //mc中分配的个数
int exist = 0; //命中次数
int flag; //标志是否命中 flag=0没命中 flag=1命中
int ep = 1; //elimination position淘汰位置
mc[1] = page[0];
printf(" %2d加入 \t", page[0]);
for (m = 1; m <= capacity; m++) //输出当前内存块的存储情况
printf("%2d ", mc[m]);
printf("\n");
sum += 1;
for (j = 1; j < 320; j++)
{
flag = 0;
for (y = 1; y <= sum; y++) //判断这个页地址流是否命中
if (mc[y] == page[j]) {
exist++;
flag = 1;
printf(" %2d命中 \t", page[j]);
for (m = 1; m <= capacity; m++) //输出当前内存块的存储情况
printf("%2d ", mc[m]);
printf("\n");
break;
}
//没命中
if (flag == 0)
{
if (sum < capacity) //还有空块
{
for (x = 1; x <= capacity; x++) //查找内存块中第一个空块
if (mc[x] == -1) {
mc[x] = page[j];
sum++;
printf(" %2d加入 \t", page[j]);
for (m = 1; m <= capacity; m++) //输出当前内存块的存储情况
printf("%2d ", mc[m]);
printf("\n");
break;
}
}
else if (sum >= capacity)
{
int opt_temp[33] = { 0 };//定义一个“预言”数组,记录每个作业之后出现的位置
for (int number = 1; number <= capacity; number++)
{
bool temp_flag = false;//记录这个作业在之后是否被找到
for (int temp_j = j; temp_j < 320; temp_j++)
{
if (page[temp_j] == mc[number])
{
/*printf(" page[temp_j]=%d ", page[temp_j]);
printf(" temp_j=%d ", temp_j);
printf(" mc[number]=%d ", mc[number]);*/
opt_temp[number] = temp_j;
temp_flag = true;
break;
}
}
if (temp_flag == false)//没被找到则被定义为无穷大
opt_temp[number] = 9999;
//printf(" opt_temp[number]=%d \n", opt_temp[number]);
}
int temp_max = 0;//记录最晚出现作业的位置
for (int number = 0; number < capacity; number++)
{
if (opt_temp[number + 1] > temp_max)
{
temp_max = opt_temp[number + 1];
ep = number + 1;
}
}
//printf(" temp_max=%d \n", temp_max);
printf(" %2d置换%2d\t", page[j], mc[ep]);
mc[ep] = page[j];
for (m = 1; m <= capacity; m++) //输出当前内存块的存储情况
printf("%2d ", mc[m]);
printf("\n");
//int t = mc[ep];
//mc[ep] = page[j];
//printf(" %2d置换%2d\t", page[j], t);
//for (m = 1; m <= capacity; m++) //输出当前内存块的存储情况
// printf("%2d ", mc[m]);
//printf("\n");
//ep += 1;
//if (ep == capacity + 1) ep = 1;
}
}
}
printf("\nexist=%d\n命中率=%lf\n", exist, exist / 320.0);
}
输出结果如下
图3.5 OPT页面置换的运行结果_1
图3.5 OPT页面置换的运行结果_2
4、 如何修改指令序列的产生方法(如简单生成320 条指令,指令地址无具体要求),并与以上示例代码的结果进行比较,说明随机指令序列的产生对程序运行结果有何影响。
在某些随机数生成的函数里面,是伪随机函数,有一些规律导致最后的命中率特别高
5、 分析比较各种页面置换算法之间的差异。
1、FIFO(先进先出算法)
(优先淘汰最早进入内存的页面)
FIFO 算法是最简单的页面置换算法。FIFO 页面置换算法为每个页面记录了调到内存的时间,当必须置换页面时会选择最旧的页面
“FIFO 算法当进程分配到的页面数增加时,缺页中断的次数可能增加也可能减少”
FIFO 算法基于队列实现,不是堆栈类算法
注意,并不需要记录调入页面的确切时间,可以创建一个 FIFO 队列,来管理所有的内存页面。置换的是队列的首个页面。当需要调入页面到内存时,就将它加到队列的尾部
FIFO 页面置换算法易于理解和编程。然而,它的性能并不总是十分理想:
其一,所置换的页面可以是很久以前使用过但现已不再使用的初始化模块
其二,所置换的页面可以包含一个被大量使用的变量,它早就初始化了,但仍在不断使用
2、OPT(最佳置换算法)
(淘汰以后不会使用的页面)
发现 Belady 异常的一个结果是寻找最优页面置换算法,这个算法具有所有算法的最低的缺页错误率,并且不会遭受 Belady 异常。这种算法确实存在,它被称为 OPT 或 MIN
这种页面置换算法确保对于给定数量的帧会产生最低的可能的缺页错误率
FIFO 和 OPT 算法的区别在于:除了在时间上向后或向前看之外,FIFO 算法使用的是页面调入内存的时间,OPT 算法使用的是页面将来使用的时间
3、LRU(最近最少使用算法)
(淘汰最近没有使用的页面)
选择最近最长时间未访问过的页面予以淘汰,它认为过去一段时间内未访问过的页面,在最近的将来可能也不会被访问。该算法为每个页面设置一个访问字段,来记录页面自上次被访问以来所经历的时间,淘汰页面时选择现有页面中值最大的予以淘汰
OPT 和 LRU 算法的区别在于:LRU 算法根据各页以前的情况,是“向前看”的,而最佳置换算法则根据各页以后的使用情况,是“向后看”的
LRU 性能较好,但需要寄存器和栈的硬件支持
LRU 是堆栈类的算法,理论上可以证明,堆栈类算法不可能出现 Belady 异常
4、Clock(时钟置换算法)
简单的 CLOCK 算法是给每一帧关联一个附加位,称为使用位。
当某一页首次装入主存时,该帧的使用位设置为1;
当该页随后再被访问到时,它的使用位也被置为1。
对于页替换算法,用于替换的候选帧集合看做一个循环缓冲区,并且有一个指针与之相关联。
当某一页被替换时,该指针被设置成指向缓冲区中的下一帧。
当需要替换一页时,操作系统扫描缓冲区,以查找使用位被置为0的一帧。
每当遇到一个使用位为1的帧时,操作系统就将该位重新置为0;
如果在这个过程开始时,缓冲区中所有帧的使用位均为0,则选择遇到的第一个帧替换;
如果所有帧的使用位均为1,则指针在缓冲区中完整地循环一周,把所有使用位都置为0,并且停留在最初的位置上,替换该帧中的页。
由于该算法循环地检查各页面的情况,故称为 CLOCK 算法,又称为最近未用( Not Recently Used, NRU )算法。
5、LFU(最不常用算法)
最不经常使用(LFU)页面置换算法要求置换具有最小计数的页面。
这种选择的原因是,积极使用的页面应当具有大的引用计数。然而,当一个页面在进程的初始阶段大量使用但是随后不再使用时,会出现问题。由于被大量使用,它有一个大的计数,即使不再需要却仍保留在内存中。一种解决方案是,定期地将计数右移 1 位,以形成指数衰减的平均使用计数。
6、MFU(最常使用算法)
最经常使用(MFU)页面置换算法是基于如下论点:具有最小计数的页面可能刚刚被引入并且尚未使用。
MFU 和 LFU 置换都不常用。这些算法的实现是昂贵的,并且它们不能很好地近似 OPT 置换。