前言:在《30天自制操作系统》中,第九天介绍了内存的两种管理方式,在此理清一下思路。
1.为什么需要管理内存
管理内存无非就是管理一些地址,那么为什么要管理地址呢? 如果程序A需要分配100KB的大小的内存,程序B需要分配200KB的大小的内存,那么如果我们随便分配(其实分配说白了就是指定一个内存的地址)的话,A程序的在内存中的数据会与B程序的数据互相覆盖,导致程序出错;而且如果程序结束了,那么内存中的数据不再需要,因此会释放(将使用完的内存归还给内存管理程序)内存。所以这些都是我们要考虑的问题。
2.管理内存方式一:
以4KB为一个单位进行管理,每一个单位配置一个标志位表示这4KB在不在使用(使用中:1 未使用:0)如下图所示:
(假设内存128MB) 这部分代码很简单,只要创建一个数组将里面的数据都初始化为0就可以,那么现在程序A需要分配一段100KB的内存空间,那么我们只需要遍历数组找到连续的25个0即可,程序如下:
<span style="font-size:18px;"> int a[32768],i=0,j=0;
for(;i<25;i++)
{
if(a[i+j]!=0)
{
j=j+i;
if(j<32768-25)
{
return 0;
}
}
}</span>
找到数组的位置j(j表示25个连续0的第一个位置)之后,这个j只是在数组中的位置,我们要知道在内存中的地址,由于数组每个元素管理4KB=0x1000B因此实际的地址是addr=j*0x1000;将这25个数组标记为1,表示正在使用。代码:
<span style="font-size:18px;"> for(i=0;i<25;i++)
{
//addr代表内存中的地址
a[addr+i]=1;
} </span>
下面A程序结束,需要释放内存,那么就将这25个数组标记为0,代码:
<span style="font-size:18px;"> j=addr/0x1000
for(i=0;i<25;i++)
{
a[addr+i]=0;
}</span>
上面就是第一种方案一(windows的软盘管理方法),让我们计算一下算法的复杂度:(假设内存大小为128MB)
(1)首先从空间来看:每个4KB的小内存块都要分配1B的内存用于记录,那么需要128/4KB=32KB的存储空间来存储,大约占总内存的32KB/128MB=0.02%(并不是太大)。
(2)其次从时间来看:刚开始给标记数组分配需要for循环32*1024次,而且分配内存的时候需要查找连续的单元。
(3)我们也可以将标记数组用BYTE(位)来表示,那么需要的存储空间大小为32/8=4KB(一种优化)
3.内存的管理方式二(列表管理):
这种方式与第一种有点像,同样是分配数组记录每一块内存的信息,但是数组里保存的并不是(4K内存块是否使用)标志位了,而保存的是每一块空闲内存的起始地址和大小。看下图
在这里我们需要建立管理内存的‘机制’,即结构体,想想需要哪些结构体,结构体又保存了什么?
首先每个空闲内存块需要有个结构体表示,至少含有开始地址和(空闲)内存块大小,然后需要一个大的结构体来保存这么多的空闲块的信息,即保存空闲块信息的数组以及空闲块的个数。建立结构体如下:
<span style="font-size:18px;">struct FREEINFO
{
unsigned int addr,size;//空闲块开始地址和空闲块的大小
};
struct MEMMAN
{
unsigned int frees;//空闲块的个数
struct FREEINFO freeInfo[MEMMAX_FREE];
} </span>
接下来讨论分配内存:
首先我们需要找到内存中的空闲块,这个空闲块比我们需要分配的大小要大,依次遍历空闲块数组直至找到满足大小的为止。
<span style="font-size:18px;">unsigned int mem_alloc(struct MEMMAN *m,unsigned int size)//size是需要分配的内存块的大小
{
unsigned int i,a;
for(i=0;i<frees;i++)
{
//如果找到了合适的空闲内存块
if(m->freeInfo[i].size>=size)
{
//保存地址
a=m->freeInfo[i].addr;
m->freeInfo[i].size-=size;
m->freeInfo[i].addr+=size;
//如果与空闲内存大小刚好相等 ,
//那么空闲内存总数减一,并且从i开始的每个数组元素往前移一位
if(m->freeInfo[i].size==0)
{
m->frees--;
for(;i<m->frees;i++)
{
m->freeInfo[i]=m->freeInfo[i+1];
}
}
}
}
//返回分配内存的起始地址
return a;
}</span>
接下来比较麻烦的就是释放内存,我们可以预见以下情况。
(1)当释放的内存上边界和上一个空闲内存块相接壤(地址邻接),那么内存就需要与上一块归并;
(2)当释放的内存下边界和下一个空闲内存块相接壤(地址邻接),那么内存就需要与下一块归并;
(3)以上两种情况都符合
(4)以上两种情况都不发生
<span style="font-size:18px;">//释放内存
void mem_free(struct MEMMAN *m,unsigned int addr,unsigned int size)
{
int i;
for(i=0;i<m->frees;i++)
{
if(m->freeInfo[i].addr>addr)
{
break;
}
}
//1.释放之后与上一个内存块相接壤
if(i>0)
{
//与前面接壤,不接壤就退出
if(m->freeInfo[i-1].addr+m->freeInfo[i-1].size==addr)
{
m->freeInfo[i-1].size+=addr;
if(i<m->frees)
{
if(addr+size==m->freeInfo[i].addr)
{
m->freeInfo[i].addr=addr;
m->freeInfo[i-1].size+=m->freeInfo[i].size;
m->frees--;
for(;i<m->frees;i++)
{
m->freeInfo[i]=m->freeInfo[i+1];
}
}
}
return;
}
}
//2.释放之后与下一个内存块相接壤
if(i<m->frees)
{
if(addr+size==m->freeInfo[i].addr)
{
m->freeInfo[i].addr=addr;
m->freeInfo[i].size+=size;
return;
}
}
//3.释放之后与两边都不接壤
if(m->frees<MEMMAX_FREES)
{
for(;i<m->frees;i++)
{
m->freeInfo[i]=m->freeInfo[i+1];
}
m->frees++;
if(m->frees>m->freeMax)
{
m->freeMax++;
}
m->freeInfo[i].addr=addr;
m->freeInfo[i].size=size;
}
} </span>
上面是《30天自制操作系统》的内存的两种管理方式,正在学习其他类型的内存管理方式,因此后面我会补充更多的内容。