1. 程式人生 > >一個簡單的內存分配器-《深入理解計算機操作系統》

一個簡單的內存分配器-《深入理解計算機操作系統》

nbsp 都是 開始 一個數 合並 區分 兩個 由於 方便

  我相信很多人都知道《深入理解計算機操作系統》這本書,並且很多人都會對它研讀。實際本人剛開始看的時候,只是加深了對操作系統的理解,別的到是沒有感覺的到,

但是在看到公司的軟件框架裏面對於內存堆的管理,才發現和書上講的異曲同工。於是乎,自己對利用隱式的空閑鏈表實現分配器做了總結,並且和自己想到的架構做了對比分析。

  我們知道一個實際的分配器,不僅要考慮好吞吐率和內存利用率之間的平衡,還要考慮:

  ①空閑塊組織:我們如何記錄空閑塊(一般剛開始會把一整段堆當做空閑塊,然後再分割)

  ②放置:我們如何選擇一個合適的空閑塊來放置一個新分配的塊?

  ③分割:在一個空閑塊裏面放置一個塊後,剩余的空閑部分怎麽處理?

  ④合並:我們如何處理剛剛釋放的塊?假如有兩個塊都是空閑的,需要合並嗎 ?

  管理於是書中定義一個數據結構來區分塊邊界,用來記錄空閑塊,這個數據結構是:

技術分享圖片

塊大小包括頭、有效負荷和填充部分,這樣就可以記錄出空閑和以分配的塊,對應的隱式空閑鏈表如下圖所示:

技術分享圖片

  放置和分割:假如我們想要放置已經分配塊時,分配器會搜索鏈表,尋求一個足夠大的空閑塊來放置,有首次放配的策略、下一次分配的策略和最佳適配的策略,為了方便起見,我們將使用首次分配,從堆頭開始搜索空閑鏈表,選擇第一個合適的空閑塊,這樣的優點是趨向於將大的空閑塊放在最後,缺點是開始的地方有小空閑塊的“碎片”。當你找到一個匹配的空閑塊時,你要做決定是否需要分割,這樣的話第一部分變成分配塊,而剩下的部分還為空閑塊,如下圖所示:

技術分享圖片

  合並:合並的問題最需要考慮的,當分配器釋放一個分配塊時,假如其他的空閑塊於之相連,這個時候需要合並,我們假設是立即合並(實際快速的分配器不會是立即合並,會選擇推遲合並)。如果是下一個塊時空閑塊,可以讓釋放掉的空閑塊的頭部,加上塊大小,指向下一個塊,然後讀出下一個塊的大小,就將它的大小簡單的加到當前塊頭部的大小上。但是如果是前面的塊時空閑塊的話,就需要搜索整個鏈表,記錄前面的塊再進行合並,這樣free需要的時間與堆的大小成線性關系,對於吞吐率是有很大的影響,這個時候Knuth記錄提出了邊界標記(boundary tag),在腳部添加一個頭部的副本,這個腳部總是在距當前塊開始位置一個字的距離。實際這樣是犧牲了內存來實現吞吐率的。

當然了,為了我們結構的可讀性更好,我們可以定義一個頭部結構體,不用放腳部標記了

typedef struct

 {

  size_t sec_size;//塊的大小,用於比較和申請內存的塊,找到能放的下的空閑塊(在已經分配的塊上,這個值一般等於alloc_size)

size alloc_size;//有效負載的大小,由於知道結構體的大小,相當於上文中知道了塊大小

size_t prev_sec_addr;//上一個塊的起始地址,是從頭部開始,用於和前合並

}

一個簡單的內存分配器-《深入理解計算機操作系統》