連續分配
為了能將用戶程序裝入內存,必須為它分配一定大小的內存空間。連續分配方式是最早出現的一種存儲器分配方式,分配的策略為一個用戶程序分配一個連續的內存空間。程序中代碼或數據的邏輯地址相鄰,內存空間分配時物理地址的相鄰。連續分配方式可分為四類:單一連續分配、固定分區分配、動態分區分配以及動態可重定位分區分配算法四種方式。
此處會涉及到內存碎片的概念,內存的內部碎片是指分配給進程的內存空間有部分沒有用上,外部碎片指的是內存中某些空間分區由於太小導致難以使用。
單一連續分配
在單道程序環境下經常使用單一連續分配,這種方式把內存分為系統區和用戶區兩部分。其中系統區僅提供給 OS 使用,通常使用內存的低址部分。在用戶區內存中,僅裝有一道用戶程序,整個內存的用戶空間由該程序獨占。
采用單一連續分配可以不采取存儲器保護措施,因為在單用戶環境下機器由一用戶獨占,不可能存在其他用戶干擾的問題,同時不設置保護措施也可以節省硬件。即使出現破壞行為,也只有可能是用戶程序自己破壞操作系統,OS 通過系統的再啟動而重新裝入內存。
單一連續分配的實現簡單,不會出現外部碎片,可以采用覆蓋方式擴充內存且可以不需要內存保護措施。但是只能用於單用戶單任務的 OS 中,會出現內部碎片,存儲的的利用率比較低。
固定分區分配
分區式存儲管理
多道程序系統需要在內存中裝入多道程序,這樣需要保證程序之間又不會發生相互干擾。此時可以采用分區式存儲管理方式,將整個用戶空間划分為若干個固定大小的區域,在每個分區中只裝入一道作業。如果在內存中有四個用戶分區,便允許四個程序並發運行。當有一空閑分區時,便可以再從外存的后備作業隊列中選擇一個適當大小的作業,裝入該分區。當該作業結束時,又可再從后備作業隊列中找出另一作業調入該分區。
划分分區的方法
將內存的用戶空間划分為若干的分區,可以令分區大小相等或者不等。如果所有划分的的內存分區大小相等將缺乏靈活性,程序太小時會造成內存空間的浪費,當程序太大時一個分區又不足以裝入該程序,致使該程序無法運行。不過在計算機同時控制多個相同對象時,這些對象所需的內存空間大小往往相同,這種划分方式比較方便和實用。
所有划分的的內存分區大小不等,可以增加存儲器分配的靈活性。通常可把內存區划分成含有多個較小的分區、適量的中等分區及少量的大分區,這樣便可根據程序的大小,為之分配適當的分區。
內存分配
進行內存分配時通常將分區按其大小進行排隊,並建立一張分區使用表,其中各表項包括每個分區的起始地址、大小及狀態(是否已分配)。當有一用戶程序要裝入時,由內存分配程序依據用戶程序的大小檢索該表,找出一個能滿足要求的、尚未分配的分區分配給該程序,然后將該表項中的狀態置為“已分配”。若未找到大小足夠的分區,則拒絕為該用戶程序分配內存。
例如一張分區使用表如下:
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 12 | 20 | 已分配 |
2 | 32 | 32 | 已分配 |
3 | 64 | 64 | 未分配 |
4 | 128 | 128 | 已分配 |
實際存儲空間的分配情況可能是:
動態分區分配
動態分區分配又稱為可變分區分配,這種策略不會預先划分內存分區,而是根據進程的實際需要動態地為之分配內存空間,這種方式的優點是可以盡可能使分區大小正好適合內存所需。在實現動態分區分配時,涉及到分區分配中所用的數據結構、分區分配算法和分區的分配與回收操作這樣三方面的問題。
數據結構
為了實現動態分區分配,系統中必須配置相應的數據結構來描述空閑分區和已分配分區的情況。常用的數據結構有以下 2 種形式,第一種是空閑分區表,它在系統每個空閑分區占一個表目,表目中包括分區號、分區大小和分區始址等數據項。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 12 | 20 | 空閑 |
2 | 32 | 32 | 空閑 |
3 | 64 | 64 | 空閑 |
4 | 128 | 128 | 空閑 |
第二種是空閑分區鏈,每個分區的起始部分設置一些用於控制分區分配的信息,通過前、后向鏈接指針將所有的空閑分區鏈接成一個雙向鏈。
動態分區分配算法
為把一個新作業裝入內存,須按照一定的分配算法,從空閑分區表或空閑分區鏈中選出一分區分配給該作業。
分配內存
系統應利用某種分配算法,從空閑分區鏈(表)中找到所需大小的分區。設請求的分區大小為 u.size,表中每個空閑分區的大小可表示為 m.size。若 m.size -
u.size ≤ size(size 是事先規定的不再切割的剩余分區的大小),說明多余部分太小可不再切割,將整個分區分配給請求者。如果多余部分超過 size,便從該分區中按請求的大小划分出一塊內存空間分配出去,余下的部分仍留在空閑分區鏈(表)中,然后將分配區的首址返回給調用者。
例如當前的內存分配狀態如圖所示,內存分配表如圖所示。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 20 | 5 | 空閑 |
2 | 5 | 35 | 空閑 |
3 | 15 | 55 | 空閑 |
現在需要插入大小為 5M 的進程三,通過某種算法后插入分區 1,修改后的內存分配表和內存狀態如下。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 15 | 10 | 空閑 |
2 | 5 | 35 | 空閑 |
3 | 15 | 55 | 空閑 |
如果通過某種算法后插入分區 2,修改后的內存分配表可以把分區 2 刪除。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 20 | 5 | 空閑 |
3 | 15 | 55 | 空閑 |
回收內存
當進程運行完畢釋放內存時,系統根據回收區的首址,從空閑區鏈(表)中找到相應的插入點,此時可能出現以下四種情況之一。
情況一
第一種情況是回收區與插入點的前一個空閑分區 F1。此時應將回收區與插入點的前一分區合並,不必為回收分區分配新表項,而只需修改其前一分區 F1 的大小。例如內存分配表和內存狀態如下,此時要回收進程 1。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 20 | 5 | 空閑 |
3 | 15 | 55 | 空閑 |
將回收的空間和分區 1 和並,然后修改分區大小即可。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 30 | 5 | 空閑 |
3 | 15 | 55 | 空閑 |
情況二
第二種情況是回收分區與插入點的后一空閑分區 F2 相鄰接,此時也可將兩分區合並形成新的空閑分區,但用回收區的首址作為新空閑區的首址,大小為兩者之和。例如內存分配表和內存狀態如下,此時要回收進程 2。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 20 | 5 | 空閑 |
3 | 15 | 55 | 空閑 |
將回收的空間和分區 3 和並,除了修改分區大小還要修改起始地址。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 20 | 5 | 空閑 |
3 | 30 | 40 | 空閑 |
情況三
第三種情況是回收區同時與插入點的前、后兩個分區鄰接,此時將三個分區合並,使用 F1 的表項和 F1 的首址並取消F2的表項,大小為三者之和。例如內存分配表和內存狀態如下,此時要回收進程 1。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 15 | 10 | 空閑 |
2 | 5 | 35 | 空閑 |
3 | 15 | 55 | 空閑 |
將回收的空間和分區 1、2 和並,然后修改分區大小即可。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 30 | 10 | 空閑 |
3 | 15 | 55 | 空閑 |
情況四
第四種情況是回收區既不與 F1 鄰接又不與 F2 鄰接,這時應為回收區單獨建立一個新表項,填寫回收區的首址和大小,並根據其首址插入到空閑鏈中的適當位置。例如內存分配表和內存狀態如下,此時要回收進程 3。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 20 | 5 | 空閑 |
3 | 15 | 55 | 空閑 |
這時就需要向內存分配表加入一個新條目,並且寫上大小和起始地址。
分區號 | 大小 | 起始地址 | 狀態 |
---|---|---|---|
1 | 20 | 5 | 空閑 |
3 | 15 | 55 | 空閑 |
2 | 5 | 35 | 空閑 |
基於順序搜索的分區分配
為了實現動態分區分配,通常是將系統中的空閑分區鏈接成一個鏈。基於順序搜索的分配方式是依次搜索空閑分區鏈上的空閑分區,去尋找一個其大小能滿足要求的分區,主要有如下四種算法。
首次適應算法
首次適應(first fit,FF)算法要求空閑分區鏈以地址遞增的次序鏈接,在分配內存時從鏈首開始順序查找,直至找到一個大小能滿足要求的空閑分區為止。然后再按照作業的大小,從該分區中划出一塊內存空間,分配給請求者,余下的空閑分區仍留在空閑鏈中。若從鏈首直至鏈尾都不能找到一個能滿足要求的分區,則表明系統中已沒有足夠大的內存分配給該進程,內存分配失敗。
該算法傾向於優先利用內存中低址部分的空閑分區,保留了高址部分的大空閑區。缺點是低址部分不斷被划分,會留下許多難以利用的外部碎片。而每次查找又都是從低址部分開始的,會導致搜索的時間開銷較大。
循環首次適應算法
為避免低址部分留下許多很小的空閑分區,以及減少查找可用空閑分區的開銷,可以使用循環首次適應算法。循環首次適應(next fit,NF)算法在為進程分配內存空間時,是從上次找到的空閑分區的下一個空閑分區開始查找,直至找到一個能滿足要求的空閑分區。實現該算法一般會使用循環鏈表,如果最后一個(鏈尾)空閑分區的大小仍不能滿足要求,則應返回到第一個空閑分區。
算法能使內存中的空閑分區分布得更均勻,減少了查找空閑分區時的開銷。但這樣會導致高地址的大分區可能被划分為小分區來使用,使缺乏大的空閑分區給較大的進程。
最佳適應算法
最佳適應(best fit,BF)算法在每次為作業分配內存時,總是把能滿足要求、又是最小的空閑分區分配給作業。該算法要求將所有的空閑分區按其容量以從小到大的順序形成一空閑分區鏈,這樣第一次找到的能滿足要求的空閑區必然是最佳的。
由於每次分配后所切割下來的剩余部分總是最小的,所以在存儲器中會留下許多難以利用的碎片。
最壞適應算法
最壞適應(worst fit,WF)算法在掃描整個空閑分區表或鏈表時,總是挑選一個最大的空閑區,從中分割一部分存儲空間給作業使用。該算法要求將所有的空閑分區,按其容量以從大到小的順序形成空閑分區鏈。
這個算法會使存儲器中缺乏大的空閑分區,它的優點是可使剩下的空閑區不至於太小,產生碎片的可能性最小,對中、小作業有利。同時最壞適應分配算法查找效率很高,查找時只要看第一個分區能否滿足作業要求即可。
基於索引搜索的分區分配
基於順序搜索的動態分區分配比較適用於不太大的系統,當系統很大時系統中的內存分區可能會很多,空閑分區鏈就可能很長,這時采用順序搜索分區方法可能會很慢。為了提高搜索空閑分區的速度,在大、中型系統中往往會采用基於索引搜索的動態分區分配算法。
快速適應算法
快速適應(quick fit)算法又稱為分類搜索法,是將空閑分區根據其容量大小進行分類,對於每一類具有相同容量的所有空閑分區,單獨設立一個空閑分區鏈表。同時在內存中設立一張管理索引表,其中的每一個索引表項對應了一種空閑分區類型,並記錄了該類型空閑分區鏈表表頭的指針。該算法在搜索時先根據進程的長度,從索引表中去尋找到能容納它的最小空閑區鏈表,然后從鏈表中取下第一塊進行分配即可。
該算法在進行空閑分區分配時不會對任何分區產生分割,所以能保留大的分區,也不會產生內存碎片。優點是查找效率高,主要缺點在於分區歸還主存時的算法復雜,系統開銷較大。此外該算法在分配空閑分區時是以進程為單位的,一個分區只屬於一個進程,因此會存在內部碎片。
伙伴系統
伙伴系統(buddy system)算法規定無論已分配分區或空閑分區,其大小均為 2 的 k 次冪(k 為整數,1 ≤ k ≤ m)。將這些空閑分區按分區的大小進行分類,對於具有相同大小的所有空閑分區,單獨設立一個空閑分區雙向鏈表。當需要為進程分配一個長度為 n 的存儲空間時,首先計算一個 i (2^i-1 < n ≤ 2^i),然后在空閑分區大小為 2^i 的空閑分區鏈表中查找。若找到即把該空閑分區分配給進程。如果找不到表明長度為 2^i 的空閑分區已經耗盡,則在分區大小為 2^i+1 的空閑分區鏈表中尋找。若存在 2^i+1 的一個空閑分區,則把該空閑分區分為相等的兩個分區,其中的一個分區用於分配,而把另一個加入分區大小為 2^i 的空閑分區鏈表中。如果還是找不到就繼續搜索,以此類推。
在伙伴系統中,對於一個大小為 2^k,地址為 x 的內存塊,其伙伴塊的地址則用 buddyk(x)表示,其通式為:
在回收空閑分區時,需要對空閑分區進行合並,所以其時間性能比快速適應算法差.但由於它采用了索引搜索算法,比順序搜索算法好。由於對空閑分區進行合並,減少了小的空閑分區,提高了空閑分區的可使用率,故優於快速適應算法,比順序搜索法略差。
Hash 算法
哈希算法就是利用哈希快速查找的優點,以及空閑分區在可利用空閑區表中的分布規律建立哈希函數。構造一張以空閑分區大小為關鍵字的哈希表,該表的每一個表項記錄了一個對應的空閑分區鏈表表頭指針。當進行空閑分區分配時,根據所需空閑分區大小通過哈希函數計算,從中得到相應的空閑分區鏈表,實現最佳分配策略。
動態可重定位分區分配
緊湊
連續分配方式的一個重要特點是一個系統或用戶程序必須被裝入一片連續的內存空間中,當一台計算機按照此方法分配了較多的內存空間后,將會分割出許多小的分區。這會導致內存空間缺乏大分區,即使這些分散的許多小分區的容量總和大於要裝入的程序,但由於這些分區不相鄰接,也無法把該程序裝入內存。例如如圖所示的 3 個空閑分區的大小總合是 10 M,但是由於這些空間並不是鄰接的,所以會導致無法插入一個 10 M 大小的程序。
若想把大作業裝入,可將內存中的所有作業進行移動,使它們全都相鄰接,這樣可把原來分散的多個空閑小分區拼接成一個大分區。這種通過移動內存中作業的位置,把原來多個分散的小分區拼接成一個大分區的方法稱為“拼接”或“緊湊”。
雖然“緊湊”能獲得大的空閑空間,但是經過緊湊后的用戶程序在內存中的位置發生了變化,就需要對程序和數據的地址加以修改(變換)。系統每“緊湊”一次,就要對移動了的程序或數據的地址進行修改,這個功能不僅是實現復雜,而且還大大地影響到系統的效率。
動態重定位
為使地址的轉換不會影響到指令的執行速度,必須有硬件地址變換機構的支持。需要在系統中增設一個重定位寄存器,用它來存放程序(數據)在內存中的起始地址。程序在執行時,真正訪問的內存地址是相對地址與重定位寄存器中的地址相加而形成的。地址變換過程是在程序執行期間的,故稱為動態重定位。當系統對內存進行了“緊湊”時,不需對程序做任何修改,只要用該程序在內存的新起始地址去置換原來的起始地址即可。
參考資料
《計算機操作系統(第四版)》,湯小丹 梁紅兵 哲鳳屏 湯子瀛 編著,西安電子科技大學出版社