回收算法:
在用户释放不再使用的占用块时,系统需将这新的空闲块插入到可利用空间表中去。
这里,同样有一个地址相邻的空闲块归并成大块的问题。
但是在伙伴系统中仅考虑互为“伙伴”的两个空闲块的归并。
何谓“伙伴”?
如前所述,在分配时经常需要将一个大的空闲块分裂成两个大小相等的存储区,这两个由同一大块分裂出来的小块就称之“互为伙伴”。
例如:
假设p为大小为pow(2,k)的空闲块的初始地址,且pMODpow(2,k+1)=0,则初始地址为p和p+pow(2,k)的两个空闲块互为伙伴。
在伙伴系统中回收空闲块时,只当其伙伴为空闲块时才归并成大块。
也就是说,若有两个空闲块,即使大小相同且地址相邻,但不是由同一大块分裂出来的,也不归并在一起。
由此,在回收空闲块时,应首先判别其伙伴是否为空闲块,若否,则只要将释放的空闲块简单插入在相应子表中即可;若是,则需在相应子表中找到其伙伴并删除之,然后再判别合并后的空闲块的伙伴是否是空闲块。
依此重复,直到归并所得空闲块的伙伴不是空闲块时,再插入到相应的子表中去。
3.2.设计一个虚拟存储区和内存工作区,并使用下述算法计算访问命中率。
页式虚拟存储器实现的一个难点是设计页面调度(置换)算法,即将新页面调入内存时,如果内存中所有的物理页都已经分配出去,就要按某种策略来废弃某个页面,将其所占据的物理页释放出来,供新页面使用。
页面替换算法主要用于如下几个地方:
(1)虚拟存储器中,主存页面(或程序段)的替换。
(2)Cache中的块替换。
(3)虚拟存储器的快慢表中,快表的替换。
(4)虚拟存储器中,用户基地址寄存器的替换。
在虚拟存储器中常用的页面替换算法有如下几种:
(1)最优替换算法,即OPT算法(OPTimalreplacementalgorithm)。
上面介绍的几种页面替换算法主要是以主存储器中页面调度情况的历史信息为依据的,它假设将来主存储器中的页面调度情况与过去一段时间内主存储器中的页面调度情况是相同的。
显然,这种假设不总是正确的。
最好的算法应该是选择将来最久不被访问的页面作为被替换的页面,这种替换算法的命中率一定是最高的,它就是最优替换算法。
要实现OPT算法,唯一的办法是让程序先执行一遍,记录下实际的页地址流情况。
根据这个页地址流才能找出当前要被替换的页面。
显然,这样做是不现实的。
因此,OPT算法只是一种理想化的算法,然而,它也是一种很有用的算法。
实际上,经常把这种算法用来作为评价其它页面替换算法好坏的标准。
在其它条件相同的情况下,哪一种页面替换算法的命中率与OPT算法最接近,那么,它就是一种比较好的页面替换算法。
(2)先进先出算法,即FIFO算法(First-InFirst-Outalgorithm)。
这种算法选择最先调入主存储器的页面作为被替换的页面。
它的优点是比较容易实现,能够利用主存储器中页面调度情况的历史信息,但是,没有反映程序的局部性。
因为最先调入主存的页面,很可能也是经常要使用的页面。
(3)最久没有使用算法,即LRU算法(LeastRecentlyUsedalgorithm)。
这种算法把近期最久没有被访问过的页面作为被替换的页面。
它把LFU算法中要记录数量上的"多"与"少"简化成判断"有"与"无",因此,实现起来比较容易。
(4)近期最少使用算法,即LFU算法(LeastFrequentlyUsedalgorithm)。
这种算法选择近期最少访问的页面作为被替换的页面。
显然,这是一种非常合理的算法,因为到目前为止最少使用的页面,很可能也是将来最少访问的页面。
该算法既充分利用了主存中页面调度情况的历史信息,又正确反映了程序的局部性。
但是,这种算法实现起来非常困难,它要为每个页面设置一个很长的计数器,并且要选择一个固定的时钟为每个计数器定时计数。
在选择被替换页面时,要从所有计数器中找出一个计数值最大的计数器。
因此,通常采用如下一种相对比较简单的方法。
3.3实现内存的slab分配器
slab描述符和空闲对象管理部分成为slab的管理部分,也可以称为slab头
slab的头可以放在slab自身,也可以放在slab之外。
如果slab头放在了slab之外,那么用户申请obj时,需要首先访问slab头,slab头提供未使用freeobj的指针
然后再访问这个freeobj的地址。
完成这项工作需要访问2个页块。
会带来效率上的损失。
slab头始终位于slab也存在问题,比如一个页面只有4K,objsize=2K,那么slab头在slab上,就意味着,这个4K的页面只能够分配一个obj。
造成了内存的浪费。
如果页数太少,存放的obj个数少,那么增加管理开销,同时内存使用率低,如果页数太多对伙伴内存系统不好,所以需要一定的策略妥协。
这个妥协过程是有calculate_slab_order这个函数来实现的。
从0阶(即一页)到kmalloc的最高阶 KMALLOC_MAX_ORDER,挨个尝试,由cache_estimate这个函数计算如果选用order阶,那么能分配多少个obj(num),剩余空间是多少(remainder)。
所谓剩余空间,就是除去slab头(如果有的话),除去obj*num,剩下的边角料空间是多少。
需要分成两种情况去计算,分成两种情况的原因,很快就能看到
A)slab头不在slab上,即flag& CFLGS_OFF_SLAB==1的时候
这种情况比较简单,由于管理数据完全不在slab上,
size_tslab_size=PAGE_SIZE<nr_objs=slab_size/buffer_size;
B) slab头在slab上,这种情况稍复杂,原因是slab头里面有个除了固定大小的slab描述符,还有个空闲对象管理数组,这个数组的大小取决于obj的个数。
但obj的个数又取决于slab头大小。
换句话,slab头的大小取决于obj的个数,obj的个数取决于slab头的大小,
四、具体实现
4.1实现一个内存管理的伙伴算法,实现内存块申请时的分配和释放后的回收。
程序:
#include
#include
#include
#defineMIN_MOMORY_SIZE536870912//随机产生的最小内存空间
#defineWORKTIME1500//系统工作时间
#defineMAX_REQ_SIZE268435456//申请空闲内存分配的最大容量:
256M
#defineMIN_DUE30//使用内存块的最短时间
#defineMAX_DUE90//使用内存块的最长时间
#defineOCCUPY_INTERVAL60//每次分配的最大间隔
#defineUSED1//内存块被使用
#defineUNUSED0//内存块未被使用
//内存块链表结点结构
typedefstructbuddy_node{
intflag;//标记空间是否被使用
intbase;//本块儿内存的基地址
intoccupy;//实际使用空间大小
intfragment;//碎片大小
intduetime;//使用时间
structbuddy_node*nextPtr;//指向下一个结点
}Buddy,*BuddyPtr;
IndexTabletable[INDEX_SIZE];//使用哈希表管理伙伴系统
intready=0;//需要分配内存的时刻
intavailSpace;//可分配空间大小
inttotalFragment=0;//总碎片大小
//函数:
添加结点(形参为内存块结点的信息)
voidinsert_node(inti,intinbase,intf,intocc,intfrag,intd)
{
BuddyPtrnewnodePtr=NULL,prePtr=NULL,curPtr=NULL;
newnodePtr=(BuddyPtr)malloc(sizeof(Buddy));//分配结点
newnodePtr->base=inbase;
newnodePtr->flag=f;
newnodePtr->occupy=occ;
newnodePtr->fragment=frag;
newnodePtr->duetime=d;
newnodePtr->nextPtr=NULL;
if(table[i].headPtr==NULL)
table[i].headPtr=newnodePtr;
else{
curPtr=table[i].headPtr;
prePtr=NULL;
//按地址顺序插入内存块
while(curPtr&&curPtr->baseprePtr=curPtr;
curPtr=curPtr->nextPtr;
}
if(prePtr==NULL){//插在最前
newnodePtr->nextPtr=curPtr;
table[i].headPtr=newnodePtr;
}
elseif(curPtr==NULL){//插在最后
prePtr->nextPtr=newnodePtr;
}
else{//插在中间
prePtr->nextPtr=newnodePtr;
newnodePtr->nextPtr=curPtr;
}
}
}
//函数:
删除结点
intdelete_node(inti,BuddyPtrdelPtr)
{
BuddyPtrprePtr=NULL,curPtr=NULL;
intbasehold=delPtr->base;
curPtr=table[i].headPtr;
while(curPtr!
=delPtr){//寻找要删除的结点的位置
prePtr=curPtr;
curPtr=curPtr->nextPtr;
}
if(prePtr==NULL)//要删除的结点在最前
table[i].headPtr=curPtr->nextPtr;
else//要删除的结点不在链表的最前
prePtr->nextPtr=curPtr->nextPtr;
free(curPtr);//释放结点
returnbasehold;//返回删除的内存块结点的基地址
}
//函数:
伙伴系统的分配算法
voidbuddy_allocate(inttime_slice)
{
inti,j,size,due;
intstate=0;//分配状态:
0为未分配,1为已分配
intinbase,basehold;
BuddyPtrcurPtr=NULL;
if(ready==time_slice){//到达分配内存的时刻
printf("Time%d:
",time_slice);
size=1+rand()%MAX_REQ_SIZE;//申请使用内存的大小
due=MIN_DUE+rand()%(MAX_DUE-MIN_DUE);//申请使用内存的时间
if(availSpace>size){//在可分配空间大于申请空间时分配
//依次寻找可分配的内存块
for(i=0;(i//找到一个不小于申请大小的块索引
if(table[i].nodesize>=size&&table[i].headPtr){
curPtr=table[i].headPtr;
//遍历相应的循环链表中
while(curPtr&&(state==0)){
//找到空闲块
if(curPtr->flag==UNUSED){
//空闲块的大小小于申请大小的2倍,分配
if(table[i].nodesize/size==1){
//在分配的内存块上设置信息
curPtr->flag=USED;
curPtr->occupy=size;
curPtr->fragment=table[i].nodesize-size;
curPtr->duetime=due+ready;
//修改可系统分配空间和碎片大小
availSpace-=table[i].nodesize;
totalFragment+=curPtr->fragment;
state=1;//标记已分配
break;
}
//空闲块的大小刚大于申请大小的2倍
else{
basehold=delete_node(i,curPtr);//删除较大的空闲块并保留其基地址
inbase=basehold+table[i].nodesize;
j=i;
//分割空闲块
do{
j--;
inbase-=table[j].nodesize;//设置要添加内存块结点的基地址
insert_node(j,inbase,UNUSED,0,0,0);//添加较小的空闲块
printf("Ablockcuttakesplace\n");
}while(table[j].nodesize/size>1);
//分配
insert_node(j,basehold,USED,size,table[j].nodesize-size,due+ready);
//修改可系统分配空间和碎片大小
availSpace-=table[j].nodesize;
totalFragment+=table[j].nodesize-size;
state=1;//标记已分配
}
}
//块被占用,查看下一结点
else
curPtr=curPtr->nextPtr;
}
}
}
printf("Allocated%d,Fragment%d,Due%d\n",size,totalFragment,ready+due);
}
elseif((availSpace=size))
printf("Allocationfailedbecauseoffragment!
\n");
else
printf("Allocationfailedbecauseofnoenoughunusedspace!
\n");
ready+=(1+rand()%OCCUPY_INTERVAL);//下次需要分配内存的时刻
}
}
//函数:
伙伴系统的回收算法
voidbuddy_retrieve(inttime_slice)
{
inti,basehold,dif;
intf=0;
intModnext=0;
BuddyPtrcurPtr=NULL,todelPtr=NULL;
//依次查找,并回收需要回收的块
for(i=0;iif(table[i].headPtr){
curPtr=table[i].headPtr;
while(curPtr){
if((curPtr->flag==USED)&&(curPtr->duetime==time_slice)){//需要回收
//修改可系统分配空间和碎片大小
availSpace+=table[i].nodesize;
totalFragment-=curPtr->fragment;
//回收为空闲块
curPtr->flag=UNUSED;
curPtr->occupy=0;
curPtr->fragment=0;
curPtr->duetime=0;
printf("Time%d:
Retrieve%d,Fragment%d\n",time_slice,table[i].nodesize,totalFragment);
}
curPtr=curPtr->nextPtr;
}
}
}
//合并空闲块
for(i=0;iif(table[i].headPtr){
curPtr=table[i].headPtr;
while(curPtr&&curPtr->nextPtr){
//将地址连续且都为空闲的块合并后加入下一级的链表中
if(curPtr->flag==UNUSED&&(curPtr->nextPtr)->flag==UNUSED){
dif=(curPtr->nextPtr)->base-curPtr->base;
Modnext=((int)(curPtr->nextPtr->base))%(2*table[i].nodesize);
if((dif==table[i].nodesize)&&(Modnext==0)){
//删除两个结点
todelPtr=curPtr;
curPtr=curPtr->nextPtr;
basehold=delete