通常情況下,乙個高階作業系統必須要給程序提供基本的、能夠在任意時刻申請和釋放任意大小記憶體的功能,就像malloc 函式那樣,然而,實現malloc 函式並不簡單,由於程序申請記憶體的大小是任意的,如果作業系統對malloc 函式的實現方法不對,將直接導致乙個不可避免的問題,那就是記憶體碎片。
記憶體碎片就是記憶體被分割成很小很小的一些塊,這些塊雖然是空閒的,但是卻小到無法使用。隨著申請和釋放次數的增加,記憶體將變得越來越不連續。最後,整個記憶體將只剩下碎片,即使有足夠的空閒頁框可以滿足請求,但要分配乙個大塊的連續頁框就可能無法滿足,所以減少記憶體浪費的核心就是盡量避免產生記憶體碎片。
針對這樣的問題,有很多行之有效的解決方法,其中夥伴演算法被證明是非常行之有效的一套記憶體管理方法,因此也被相當多的作業系統所採用。
夥伴演算法,簡而言之,就是將記憶體分成若干塊,然後盡可能以最適合的方式滿足程式記憶體需求的一種記憶體管理演算法,夥伴演算法的一大優勢是它能夠完全避免外部碎片的產生。什麼是外部碎片以及內部碎片,前面博文slab分配器後面已有介紹。申請時,夥伴演算法會給程式分配乙個較大的記憶體空間,即保證所有大塊記憶體都能得到滿足。很明顯分配比需求還大的記憶體空間,會產生內部碎片。所以夥伴演算法雖然能夠完全避免外部碎片的產生,但這恰恰是以產生內部碎片為代價的。
linux 便是採用這著名的夥伴系統演算法來解決外部碎片的問題。把所有的空閒頁框分組為 11 塊鍊錶,每一塊鍊錶分別包含大小為1,2,4,8,16,32,64,128,256,512 和 1024 個連續的頁框。對1024 個頁框的最大請求對應著 4mb 大小的連續ram 塊。每一塊的第乙個頁框的實體地址是該塊大小的整數倍。例如,大小為 16個頁框的塊,其起始位址是 16 * 2^12 (2^12 = 4096,這是乙個常規頁的大小)的倍數。
下面通過乙個簡單的例子來說明該演算法的工作原理:
假設要請求乙個256(129~256)個頁框的塊。演算法先在256個頁框的鍊錶中檢查是否有乙個空閒塊。如果沒有這樣的塊,演算法會查詢下乙個更大的頁塊,也就是,在512個頁框的鍊錶中找乙個空閒塊。如果存在這樣的塊,核心就把512的頁框分成兩等分,一般用作滿足需求,另一半則插入到256個頁框的鍊錶中。如果在512個頁框的塊鍊錶中也沒找到空閒塊,就繼續找更大的塊——1024個頁框的塊。如果這樣的塊存在,核心就把1024個頁框塊的256個頁框用作請求,然後剩餘的768個頁框中拿512個插入到512個頁框的鍊錶中,再把最後的256個插入到256個頁框的鍊錶中。如果1024個頁框的鍊錶還是空的,演算法就放棄並發出錯誤訊號。
簡而言之,就是在分配記憶體時,首先從空閒的記憶體中搜尋比申請的記憶體大的最小的記憶體塊。如果這樣的記憶體塊存在,則將這塊記憶體標記為「已用」,同時將該記憶體分配給應用程式。如果這樣的記憶體不存在,則作業系統將尋找更大塊的空閒記憶體,然後將這塊記憶體平分成兩部分,一部分返回給程式使用,另一部分作為空閒的記憶體塊等待下一次被分配。
以上過程的逆過程就是頁框塊的釋放過程,也是該演算法名字的由來。核心試圖把大小為 b 的一對空閒夥伴塊合併為乙個大小為 2b 的單獨塊。滿足以下條件的兩個塊稱為夥伴:
該演算法是迭代的,如果它成功合併所釋放的塊,它會試圖合併 2b 的塊,以再次試圖形成更大的塊。
假設要釋放乙個256個頁框的塊,演算法就把其插入到256個頁框的鍊錶中,然後檢查與該記憶體相鄰的記憶體,如果存在同樣大小為256個頁框的並且空閒的記憶體,就將這兩塊記憶體合併成512個頁框,然後插入到512個頁框的鍊錶中,如果不存在,就沒有後面的合併操作。然後再進一步檢查,如果合併後的512個頁框的記憶體存在大小為512個頁框的相鄰且空閒的記憶體,則將兩者合併,然後插入到1024個頁框的鍊錶中。
簡而言之,就是當程式釋放記憶體時,作業系統首先將該記憶體**,然後檢查與該記憶體相鄰的記憶體是否是同樣大小並且同樣處於空閒的狀態,如果是,則將這兩塊記憶體合併,然後程式遞迴進行同樣的檢查。
下面通過乙個例子,來深入地理解一下夥伴演算法的真正內涵(下面這個例子並不嚴格表示linux 核心中的實現,是闡述夥伴演算法的實現思想):
假設系統中有 1mb 大小的記憶體需要動態管理,按照夥伴演算法的要求:需要將這1m大小的記憶體進行劃分。這裡,我們將這1m的記憶體分為 64k、64k、128k、256k、和512k 共五個部分,如下圖 a 所示
1.此時,如果有乙個程式a想要申請一塊45k大小的記憶體,則系統會將第一塊64k的記憶體塊分配給該程式(產生內部碎片為代價),如圖b所示;
2.然後程式b向系統申請一塊68k大小的記憶體,系統會將128k記憶體分配給該程式,如圖c所示;
3.接下來,程式c要申請一塊大小為35k的記憶體。系統將空閒的64k記憶體分配給該程式,如圖d所示;
4.之後程式d需要一塊大小為90k的記憶體。當程式提出申請時,系統本該分配給程式d一塊128k大小的記憶體,但此時記憶體中已經沒有空閒的128k記憶體塊了,於是根據夥伴演算法的原理,系統會將256k大小的記憶體塊平分,將其中一塊分配給程式d,另一塊作為空閒記憶體塊保留,等待以後使用,如圖e所示;
5.緊接著,程式c釋放了它申請的64k記憶體。在記憶體釋放的同時,系統還負責檢查與之相鄰並且同樣大小的記憶體是否也空閒,由於此時程式a並沒有釋放它的記憶體,所以系統只會將程式c的64k記憶體**,如圖f所示;
6.然後程式a也釋放掉由它申請的64k記憶體,系統隨機發現與之相鄰且大小相同的一段記憶體塊恰好也處於空閒狀態。於是,將兩者合併成128k記憶體,如圖g所示;
7.之後程式b釋放掉它的128k,系統也將這塊記憶體與相鄰的128k記憶體合併成256k的空閒記憶體,如圖h所示;
8.最後程式d也釋放掉它的記憶體,經過三次合併後,系統得到了一塊1024k的完整記憶體,如圖i所示。
有了前面的了解,我們通過linux 核心原始碼(mmzone.h)來看看夥伴演算法是如何實現的:
夥伴演算法管理結構
[cpp]view plain
copy
#define max_order 11
struct
zone
struct
free_area ;
前面說到夥伴演算法把所有的空閒頁框分組為11塊鍊錶,記憶體分配的最大長度便是2^10頁面。
上面兩個結構體向我們揭示了夥伴演算法管理結構。zone結構中的free_area陣列,大小為11,分別存放著這11個組,free_area結構體裡面又標註了該組別空閒記憶體塊的情況。
將所有空閒頁框分為11個組,然後同等大小的串成乙個鍊錶對應到free_area陣列中。這樣能很好的管理這些不同大小頁面的塊。
(啊哦,有時間再補充吧...)
記憶體管理演算法 Buddy夥伴演算法
buddy演算法的優缺點 1 儘管夥伴記憶體演算法在記憶體碎片問題上已經做的相當出色,但是該演算法中,乙個很小的塊往往會阻礙乙個大塊的合併,乙個系統中,對記憶體塊的分配,大小是隨機的,一片記憶體中僅乙個小的記憶體塊沒有釋放,旁邊兩個大的就不能合併。2 演算法中有一定的浪費現象,夥伴演算法是按2的冪次...
記憶體管理演算法 Buddy夥伴演算法
buddy system記憶體管理,努力讓記憶體分配與相鄰記憶體合併能快速進行 對於普通演算法來講,合併記憶體相當困難 它利用的是計算機擅長處理2的冪運算。我們建立一系列空閒塊列表,每一種都是2的倍數。舉個例子,如果最小分配單元是8位元組,整個記憶體空間有1m。我們建立8位元組記憶體塊鍊錶,16位元...
記憶體管理演算法 Buddy夥伴演算法
buddy演算法的優缺點 1 儘管夥伴記憶體演算法在記憶體碎片問題上已經做的相當出色,但是該演算法中,乙個很小的塊往往會阻礙乙個大塊的合併,乙個系統中,對記憶體塊的分配,大小是隨機的,一片記憶體中僅乙個小的記憶體塊沒有釋放,旁邊兩個大的就不能合併。2 演算法中有一定的浪費現象,夥伴演算法是按2的冪次...