內存管理算法--Buddy夥伴算法

Buddy算法的優缺點:

1)儘管夥伴內存算法在內存碎片問題上已經做的相當出色,但是該算法中,一個很小的塊往往會阻礙一個大塊的合併,一個系統中,對內存塊的分配,大小是隨機的,一片內存中僅一個小的內存塊沒有釋放,旁邊兩個大的就不能合併。

2)算法中有一定的浪費現象,夥伴算法是按2的冪次方大小進行分配內存塊,當然這樣做是有原因的,即爲了避免把大的內存塊拆的太碎,更重要的是使分配和釋放過程迅速。但是他也帶來了不利的一面,如果所需內存大小不是2的冪次方,就會有部分頁面浪費。有時還很嚴重。比如原來是1024個塊,申請了16個塊,再申請600個塊就申請不到了,因爲已經被分割了。

3)另外拆分和合並涉及到 較多的鏈表和位圖操作,開銷還是比較大的。

Buddy(夥伴的定義):

這裏給出夥伴的概念,滿足以下三個條件的稱爲夥伴:
1)兩個塊大小相同;
2)兩個塊地址連續;
3)兩個塊必須是同一個大塊中分離出來的;

Buddy算法的分配原理:

假如系統需要4(2*2)個頁面大小的內存塊,該算法就到free_area[2]中查找,如果鏈表中有空閒塊,就直接從中摘下並分配出去。如果沒有,算法將順着數組向上查找free_area[3],如果free_area[3]中有空閒塊,則將其從鏈表中摘下,分成等大小的兩部分,前四個頁面作爲一個塊插入free_area[2],後4個頁面分配出去,free_area[3]中也沒有,就再向上查找,如果free_area[4]中有,就將這16(2*2*2*2)個頁面等分成兩份,前一半掛如free_area[3]的鏈表頭部,後一半的8個頁等分成兩等分,前一半掛free_area[2]
的鏈表中,後一半分配出去。假如free_area[4]也沒有,則重複上面的過程,知道到達free_area數組的最後,如果還沒有則放棄分配。



Buddy算法的釋放原理:

內存的釋放是分配的逆過程,也可以看作是夥伴的合併過程。當釋放一個塊時,先在其對應的鏈表中考查是否有夥伴存在,如果沒有夥伴塊,就直接把要釋放的塊掛入鏈表頭;如果有,則從鏈表中摘下夥伴,合併成一個大塊,然後繼續考察合併後的塊在更大一級鏈表中是否有夥伴存在,直到不能合併或者已經合併到了最大的塊(2*2*2*2*2*2*2*2*2個頁面)。


整個過程中,位圖扮演了重要的角色,如圖2所示,位圖的某一位對應兩個互爲夥伴的塊,爲1表示其中一塊已經分配出去了,爲0表示兩塊都空閒。夥伴中無論是分配還是釋放都只是相對的位圖進行異或操作。分配內存時對位圖的
是爲釋放過程服務,釋放過程根據位圖判斷夥伴是否存在,如果對相應位的異或操作得1,則沒有夥伴可以合併,如果異或操作得0,就進行合併,並且繼續按這種方式合併夥伴,直到不能合併爲止。


Buddy內存管理的實現:

提到buddy 就會想起linux 下的物理內存的管理 ,這裏的memory pool 上實現的 buddy 系統

和linux 上按page 實現的buddy系統有所不同的是,他是按照字節的2的n次方來做block的size

實現的機制中主要的結構如下:

整個buddy 系統的結構:

struct mem_pool_table

{

#define MEM_POOL_TABLE_INIT_COOKIE (0x62756479)

uint32 initialized_cookie; /* Cookie 指示內存已經被初始化後的魔數,  如果已經初始化設置爲0x62756479*/

uint8 *mem_pool_ptr;/* 指向內存池的地址*/

uint32 mem_pool_size; /* 整個pool 的size,下面是整個max block size 的大小*/

uint32 max_block_size; /* 必須是2的n次方,表示池中最大塊的大小*/   
boolean assert_on_empty; /* 如果該值被設置成TRUE,內存分配請求沒有完成就返回 並輸出出錯信息*/
 uint32 mem_remaining; /* 當前內存池中剩餘內存字節數*/                                              
uint32 max_free_list_index; /* 最大freelist 的下標,*/
struct mem_free_hdr_type     *free_lists[MAX_LEVELS];/* 這個就是夥伴系統的level數組*/

#ifdef FEATURE_MEM_CHECK
uint32 max_block_requested;
  uint32 min_free_mem; /* 放mem_remaining */
#endif /* FEATURE_ONCRPC_MEM_CHECK*/
}
;

這個結構是包含在free node 或alloc node 中的結構:

其中check 和 fill 都被設置爲某個pattern
用來檢查該node 的合法性
#define MEM_HDR_CHECK_PATTERN ((uint16)0x3CA4)
#define MEM_HDR_FILL_PATTERN ((uint8)0x5C)


typedef struct  tagBuddyMemBlockHeadType

{

    mem_pool_type pool; /*回指向內存池*/

    uint16 check; 

    uint8 state; /* bits 0-3 放該node 屬於那1級 bit 7 如果置1,表示已經分配(not free)

    uint8 fill;

} BUDDY_MEM_BLOCK_HEAD_TYPE;



這個結構就是包含node 類型結構的 free header 的結構:

typedef struct  tagBuddyMemHeadType

{

    mem_node_hdr_type hdr;

    struct mem_free_hdr_type * pNext;   /* next,prev,用於連接free header的雙向 list*/

    struct mem_free_hdr_type * pPrev;

} mem_free_hdr_type;

這個結構就是包含node 類型結構的 alloc header 的結構:
已分配的mem 的node 在內存中就是這樣表示的
  1. typedef struct mem_alloc_hdr_type
  2. {
  3.    mem_node_hdr_type hdr;

  4. #ifdef FEATURE_MEM_CHECK_OVERWRITE
  5.    uint32     in_use_size;
  6. #endif

  7. } mem_alloc_hdr_type;
其中用in_use_size 來表示如果請求分配的size 所屬的level上實際用了多少
比如申請size=2000bytes, 按size to level 應該是2048,實際in_use_size
爲2000,剩下48byte 全部填充爲某一數值,然後在以後free 是可以check 
是否有overwite 到着48byte 中的數值,一般爲了速度,只 檢查8到16byte

另外爲什麼不把這剩下的48byte 放到freelist 中其他level 中呢,這個可能
因爲本來buddy 系統的缺點就是容易產生碎片,這樣的話就更碎了

關於free or alloc node 的示意圖:

假設

最小塊爲2^4=16,着是由mem_alloc_hdr_type (12byte)決定的, 實際可分配4byte

如果假定最大max_block_size =1024,

如果pool 有mem_free_hdr_type[0]上掛了兩個1024的block node

上圖是free node, 下圖紫色爲alloc node


接下來主要是buddy 系統的操作主要包括pool init , mem alloc ,mem free

pool init :
 1. 將實際pool 的大小去掉mem_pool_table 結構大小後的size 放到
     mem_pool_size, 並且修改實際mem_pool_ptr指向前進mem_pool_table
     結構大小的地址
 2.  接下來主要將mem_pool_size 大小的內存,按最大塊掛到free_lists 上
    level 爲0的list 上,然後小於該level block size 部分,繼續掛大下一
    級,循環到全部處理完成  (感覺實際用於pool的size ,應該爲減去
    mem_pool_table 的大小,然後和最大塊的size 對齊,這樣比較好,
    但沒有實際測試過)
    
    
mem alloc:
    這部分相當簡單,先根據請求mem的size ,實際分配時需要加上mem_alloc_hdr_type
這12byte ,然後根據調整後的size,計算實際應該在那個 level上分配,如果有相應級
很簡單,直接返回,如果沒有,一級一級循環查找,找到後,把省下的部分,在往下一級
一級插入到對應級的freelist 上

mem free:
     其中free 的地址,減去12 就可以獲得mem_alloc_hdr_type 結構
     然後確定buddy 在該被free block 前,還是後面, 然後合併buddy,
     循環尋找上一級的buddy ,有就再合併,只到最大block size 那級



關於這個算法,在<<The Art  of Computer Programming>> vol 1,的

動態存儲分配中有描述,對於那些只有OSAL 的小系統,該算法相當有用


轉自:https://blog.csdn.net/orange_os/article/details/7392986

發佈了55 篇原創文章 · 獲贊 86 · 訪問量 97萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章