前言:在《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天自制操作系統》的內存的兩種管理方式,正在學習其他類型的內存管理方式,因此後面我會補充更多的內容。