Linux内存管理和页面置换相关算法
文章目录
内存管理
伙伴系统
Linux内核内存管理的一项重要工作就是如何在频繁申请释放内存的情况下,避免碎片的产生。Linux采用伙伴系统解决外部碎片的问题。
伙伴系统简介:
伙伴系统的宗旨就是用最小的内存块来满足内核的对于内存的请求。在最初,只有一个块,也就是整个内存,假如为1M大小,而允许的最小块为64K,那么当我们申请一块200K大小的内存时,就要先将1M的块分裂成两等分,各为512K,这两分之间的关系就称为伙伴,然后再将第一个512K的内存块分裂成两等分,各位256K,将第一个256K的内存块分配给内存,这样就是一个分配的过程。
Linux 采用这著名的伙伴系统算法来解决外部碎片的问题。把所有的空闲页框分组为 11 块链表,每一块链表分别包含大小为1,2,4,8,16,32,64,128,256,512 和 1024 个连续的页框。对1024 个页框的最大请求对应着 4MB 大小的连续RAM 块。每一块的第一个页框的物理地址是该块大小的整数倍。例如,大小为 16个页框的块,其起始地址是 16 * 2^12 (2^12 = 4096,这是一个常规页的大小)的倍数。 然后尽可能以最适合的方式满足程序内存需求。
算法实现:
依据算法简介,我们模拟一个链表数组,每一个链表代表一个类型的内存块,每当产生内存调用的需求时(例如要调用内存大小为a),进行判断,找到这样的情况:2i-1<2i ,然后查询第i个内存块链表,如果有没被占用的,则直接调用,设置为占用,否则向下个更大的2i+1内存块访问是否有内存块剩余,分配所需要的2i内存,将剩余的2i内存分配到上一个内存块链表尾部。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v1nURRww-1622120569583)(C:\Users\wizard\AppData\Roaming\Typora\typora-user-images\1621996572305.png)]
伙伴系统的回收算法:
当出现内存回收的指令时,我们将内存回收时为了比面产生内存碎片,需要将内存进行合并。 当用户申请的内存块不再使用时,系统需要将这部分存储块回收,回收时需要判断是否可以和其它的空闲块进行合并。
在伙伴系统中每一个存储块都有各自的“伙伴”,当用户释放存储块时只需要判断该内存块的伙伴是否为空闲块,如果是则将其合并,然后合并的新的空闲块还需要同其伙伴进行判断整合。反之直接将存储块根据大小插入到可利用空间表中即可。
判断一个存储块的伙伴的位置时,采用的方法为:如果该存储块的起始地址为 p,大小为2k,则其伙伴所在的起始地址为: p-2k和p+2k;然后判断其大小是否为2k,若是,则是一个“伙伴”。
算法实现:
在每一个链表内加入其初始地址,当有内存块被回收时,判断在该内存块中链表中是否存在有空闲的“伙伴”,若有,则进行合并。伙伴的判断条件是其地址和该回收的内存地址合并后仍然可以内存连续。
代码实现(C++):
#include<bits/stdc++.h>
using namespace std;
struct L{
bool occupyed=0;//占用情况
int location;//模拟地址
L *next=NULL;
};
L li[11];
struct PI{
L *to;//指向的链表节点
int num;//存储大小
};
PI pi[105];
/**
2的次幂函数
*/
int pows(int power)
{
int data=1;
while(power)
{
power--;
data*=2;
}
return data;
}
/**
判断该内存块的占用情况
*/
L* pan(int i)
{
L *p;
p=li[i].next;
while(p)
{
if(p->occupyed==false)
return p;
p=p->next;
}
return NULL;
}
/**
用来将一个节点从当前内存块里删除
*/
void shan(int i)
{
L *p;
p=li[i].next;
while(p->next)
{
if(p->next->occupyed==false)
{
p->next=p->next->next;
}
}
}
/**
函数意义为将当亲的内存链表的两个节点删除并合并,在上一层内存链表加入新节点
*/
void bac(L* p1,L *p2,int q)
{
L*p=li[q].next;
while(p->next)
{
if(p->next==p1)
{
p->next=p->next->next;
break;
}
else
{
p=p->next;
}
}
p=li[q].next;
while(p->next)
{
if(p->next==p2)
{
delete p->next;
p->next=p->next->next;
break;
}
else
{
p=p->next;
}
}
L *pp=li[q+1].next->next;
p1->next=p;
li[q+1].next=p1;
}
/**
初始化链表数组
我们定义一个大小共为为2048大小的数组
在2的0次方存储2两个内存块,2的1到10次方存储一个内存块
假设地址从1开始
创建好后的内存分配示意
1 1 2 4 8 16 32 64 128 256 512 1024
*/
void initLi()
{
for(int i=0;i<11;i++)
{
L *p=new L;
p->location=pows(i)+1;
li[i].next=p;
}
L *p=new L;
p->location=1;
L *pp=li[0].next;
li[0].next=p;
p->next=pp;
}
void req(int num,int id)
{
int q=0;
while(num>pows(q))q++;
L* p;
if(p=pan(q))//如果该内存块下有可用的节点,返回其地址
{
p->occupyed=1;//设置为占用状态
pi[id].num=num;
pi[id].to=p;
}
else{
q++;
while(!pan(q)&&q<11)q++;
if(q==11)
{
cout<<"内存不足,无法分配内存";//如果到了最后一个内存块也找不到可用的节点,则输出内存不足
exit(0);
}
else//这里将减少内部内存碎片的方法简化,采用将一个内存块分为平均两块,添加到下一级内存块中,一部分为已分配状态,一部分为分配状态。
{ //同时也可以为了减少内存碎片进一步一开始将剩余的内存块分为更多的下一级,下下一级内存块,这里进行简化。
p=pan(q);
shan(q);
L *pp=li[q-1].next;
li[q-1].next=p;
L* np=new L;
np->location=p->location+pows(q-1);
np->next=pp->next;
p->next=np; //将q级的内存块分为平均两块,将这一级内存块的地址定位到下一级再在下一级内存块新增一个内存块,从而实现将上一级内存块分为两个下一级,形成下一级的新链表。
p->occupyed=1;//设置为占用状态
pi[id].num=num;
pi[id].to=p;
}
}
}
void rel(int id)
{
pi[id].to->occupyed=0;//解除占用情况
int q=0;
while(pows(q)!=pi[id].num)q++;//通过内存大小找到其内存链表
L *p=li[q].next;
//此下为寻找“伙伴”的过程
while(p)
{
if(p->location=pi[id].to->location+pows(q))
{
bac(p,pi[id].to,q);//函数意义为将当亲的内存链表的两个节点删除并合并,在上一层内存链表加入新节点
break;
}
if(p->location=pi[id].to->location-pows(q))
{
bac(pi[id].to,p,q);
break;
}
else
p=p->next;
}
}
int main()
{
char c;//判断操作是什么,a的意思为请求,b的意思是释放
int i=0,num;
while(cin>>c)
{
if(c=='a')
{
cin>>num;//表明要请求的内存大小
req(num,i);//存储第i个
pi[i++].num=num;
}
else
{
cin>>i;//输入要释放的id
rel(i);
}
}
}
页面置换
FIFO算法
FIFO简介:
优先淘汰最早进入内存的页面,亦即在内存中驻留时间最久的页面。该算法实现简单,只需把调入内存的页面根据先后次序链接成队列,设置一个指针总指向最早的页面。
算法实现:
由于FIFO算法是先进先出的页面置换,所以我们可以利用队列来实现对页面的存储,先存储的页面先被置换。
其原理图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e0uUuEj3-1622120569589)(C:\Users\wizard\AppData\Roaming\Typora\typora-user-images\1622117513636.png)]
代码实现:
#include<bits/stdc++.h>
using namespace std;
int page[100],head=2,tail=0;//利用数组来下实现队列,head和tail作为队列中的头指针和尾指针,预设物理块中的总页数为3
int main()
{
int n,p;//定义请求数,每次调入的页面
cout<<"预设0为未储存页面,当前物理块状态为:0 0 0"<<endl;
cout<<"输入请求数:"<<endl;
cin>>n;
while(n--)
{
cout<<"输入请求页:"<<endl;
cin>>p;
bool b=true;
for(int i=tail;i<=head;i++)
if(page[i]==p) b=false;
if(!b)
{
cout<<"当前请求不需要进行置换页"<<endl;
}
else
{
cout<<"当前请求需要进行置换页"<<endl;
tail++,head++;
page[head]=p;
}
cout<<"当前物理块状态为:";
for(int i=tail;i<=head;i++)
cout<<page[i]<<" ";
cout<<endl;
}
}
算法结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3wm3cfpa-1622120569599)(C:\Users\wizard\AppData\Roaming\Typora\typora-user-images\1622117558051.png)]
CLOCK 算法
CLOCK简介:
时钟置换算法可以认为是一种最近未使用算法,即逐出的页面都是最近没有使用的那个。我们给每一个页面设置一个标记位b,b=1表示最近有使用b=0则表示该页面最近没有被使用,应该被逐出。
算法实现:
为了实现每次需要逐出页面是最近没被使用过的页面,可以采用一个循环队列,且每个节点都有一个判断变量x,每次头指针判断当前的页面是否x=0,如果x=0,则将该页面作为下一次需要逐出的页面,否则,将x置为0,指针向后移动。
其示意图可利用一个环形队列表示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PHhYuUat-1622120569604)(C:\Users\wizard\AppData\Roaming\Typora\typora-user-images\1622119210720.png)]
代码实现:
#include<bits/stdc++.h>
using namespace std;
struct A{
int data;
int x=0;//初始化为0,表示需要被逐出
};
A page[4];
int index=0;//利用数组来下实现循环队列,head作为头指针,预设物理块中的总页数为4
int mod=4;
int main()
{
int n,p;//定义请求数,每次调入的页面
cout<<"预设0为未储存页面,当前物理块状态为:0 0 0 0"<<endl;
cout<<"输入请求数:"<<endl;
cin>>n;
while(n--)
{
bool b=true;
cout<<"输入请求页:"<<endl;
cin>>p;
for(int i=0;i<4;i++)
if(page[i].data==p)
b=false,page[i].x=1;
if(!b)
cout<<"当前请求不需要进行置换页"<<endl;
else{
cout<<"当前请求需要进行置换页"<<endl;
while(page[index%4].x)page[index%4].x=0,index++;
page[index%4].data=p;
page[index%4].x=1;
}
cout<<"当前物理块状态为:";
for(int i=0;i<4;i++)
cout<<page[i].data<<" ";
cout<<endl;
}
}
算法结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B7ErX0xf-1622120569610)(C:\Users\wizard\AppData\Roaming\Typora\typora-user-images\1622119095919.png)]
LRU算法
LRU简介:
利用局部性原理,根据一个作业在执行过程中过去的页面访问历史来推测未来的行为。它认为过去一段时间里不曾被访问过的页面,在最近的将来可能也不会再被访问。所以,这种算法的实质是:当需要淘汰一个页面时,总是选择在最近一段时间内最久不用的页面予以淘汰。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0bfgf27u-1622120569613)(C:\Users\wizard\AppData\Roaming\Typora\typora-user-images\1622119290889.png)]
算法实现:
我们可以构建一个结构体数组,其中的元素包括缓冲的页面id,其距离上一次被缓冲的时间t,每次有请求时,遍历数组,若找到页面,则将此页面的t置为0;否则在数组中找到页面t最大的页面,逐出并缓冲新页面;
代码实现:
#include<bits/stdc++.h>
using namespace std;
struct A{
int data;
int t=1;
};
A page[4];
int main()
{
int n,p;//定义请求数,每次调入的页面
cout<<"预设0为未储存页面,当前物理块状态为:0 0 0 0"<<endl;
cout<<"输入请求数:"<<endl;
cin>>n;
while(n--)
{
bool b=true;
cout<<"输入请求页:"<<endl;
cin>>p;
for(int i=0;i<4;i++)
if(page[i].data==p)
b=false,page[i].t=0;
if(!b)
cout<<"当前请求不需要进行置换页"<<endl;
else{
cout<<"当前请求需要进行置换页"<<endl;
int index=0,t=0;
for(int i=0;i<4;i++)
if(page[i].t>t)
{
t=page[i].t;
index=i;
}
page[index].data=p;
page[index].t=0;
}
cout<<"当前物理块状态为:";
for(int i=0;i<4;i++)
{
page[i].t++;
cout<<page[i].data<<" ";
}
cout<<endl;
}
}
代码结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YfT0snF6-1622120569615)(C:\Users\wizard\AppData\Roaming\Typora\typora-user-images\1622120147986.png)]