1. 程式人生 > >記憶體分配-----夥伴演算法buddy和slab演算法

記憶體分配-----夥伴演算法buddy和slab演算法

記憶體管理問題:

記憶體碎片大小和管理記憶體碎片的效率問題(即空間和時間效率的問題):

記憶體碎片是指當回收一塊記憶體時,一般將記憶體直接放入free連結串列中,由於記憶體越分配越小,記憶體塊就會特別多而且特別小,當需要一塊大的記憶體塊的時候無法找到.原因就在於回收記憶體的時候,不能把相鄰兩塊可用記憶體合併.

解決方法:

1.小塊記憶體單獨分配,大塊記憶體有系統自動分配.(nginx和stl就是使用這種方法)

2.夥伴演算法.

3.slab演算法.

夥伴演算法:

1.將空閒頁面分為m個組,第1組儲存2^0個單位的記憶體塊,,第2組儲存2^1個單位的記憶體塊,第3組儲存2^2個單位的記憶體塊,第4組儲存2^3個單位的記憶體塊,以此類推.直到m組.

2.每個組是一個連結串列,用於連線同等大小的記憶體塊.

3.夥伴塊的大小是相等的,並且第1塊和第2塊是夥伴,第三塊和第四塊是夥伴.以此類推.

 

夥伴演算法分配記憶體:

若申請的記憶體大小為n則將n向上取整為2的冪設次數為s,則需要分配s大小的記憶體塊,定位大相應陣列,

1.如果該陣列有剩餘記憶體塊,則分配出去.

2.若沒有剩餘記憶體塊就沿陣列向上查詢,然後再將該記憶體塊分割出來s並將剩餘的記憶體塊放入相應大小的陣列中.

例如分配5大小的記憶體塊

----------->定位到大小為8的連結串列中 -------->若該連結串列中之中沒有空餘元素,則定位到16的連結串列中,16中有剩餘元素,則取出該元素,並分割出大小為8的記憶體塊供使用者使用,然後將剩餘的8連線到大小為8的陣列中.

夥伴演算法的記憶體合併:

當用戶用完記憶體後會歸還,然後根據該記憶體塊實際大小(向上取整為2的冪)歸入連結串列中,在歸入之前,

1.我們還要檢測他的夥伴記憶體塊是否空閒,

2.如果空閒就合併在一起,合併後轉到1,繼續執行.

3.若果不是空閒的就直接歸入連結串列中.

一般來說,夥伴演算法實現中會用點陣圖記錄記憶體塊是否被使用,用於夥伴記憶體的合併.

夥伴演算法的特點:

        顯而易見,夥伴演算法會浪費大量的記憶體,(如果需要大小為9的記憶體塊必須分配大小為16的記憶體塊).而優點也是明顯的,分配和合並演算法都很簡單易行.但是,當分配和回收較快的時候,例如分配大小為9的記憶體塊,此時分配16,然後又回收,即合併夥伴記憶體塊,這樣會造成不必要的cpu浪費,應該設定連結串列中記憶體塊的低潮個數,即當連結串列中記憶體塊個數小於某個值的時候,並不合併夥伴記憶體塊,只要當高於低潮個數的時候才合併.

slab演算法:

         一般來說,夥伴演算法的改進演算法用於作業系統分配和回收記憶體,而且記憶體塊的單位較大,利於Linux使用的夥伴演算法以頁為單位.對於小塊記憶體的分配和回收,夥伴演算法就顯得有些得不償失了.

        對於小塊記憶體,一般採用slab演算法,或者叫做slab機制.

        Linux 所使用的 slab 分配器的基礎是 Jeff Bonwick 為SunOS 作業系統首次引入的一種演算法。Jeff的分配器是圍繞物件快取進行的。在核心中,會為有限的物件集(例如檔案描述符和其他常見結構)分配大量記憶體。Jeff發現對核心中普通物件進行初始化所需的時間超過了對其進行分配和釋放所需的時間。因此他的結論是不應該將記憶體釋放回一個全域性的記憶體池,而是將記憶體保持為針對特定目而初始化的狀態。例如,如果記憶體被分配給了一個互斥鎖,那麼只需在為互斥鎖首次分配記憶體時執行一次互斥鎖初始化函式(mutex_init)即可。後續的記憶體分配不需要執行這個初始化函式,因為從上次釋放和呼叫析構之後,它已經處於所需的狀態中了。

        Linux slab分配器使用了這種思想和其他一些思想來構建一個在空間和時間上都具有高效性的記憶體分配器。

        圖中給出了 slab結構的高層組織結構。在最高層是 cache_chain,這是一個 slab 快取的連結列表。這對於 best-fit演算法非常有用,可以用來查詢最適合所需要的分配大小的快取(遍歷列表)。cache_chain 的每個元素都是一個 kmem_cache 結構的引用(稱為一個 cache)。它定義了一個要管理的給定大小的物件池。

每個快取都包含了一個 slabs 列表,這是一段連續的記憶體塊(通常都是頁面)。存在3 種 slab:

slabs_full:完全分配的slab

slabs_partial:部分分配的slab

slabs_empty:空slab,或者沒有物件被分配

        slab 列表中的每個 slab都是一個連續的記憶體塊(一個或多個連續頁),它們被劃分成一個個物件。這些物件是從特定快取中進行分配和釋放的基本元素。注意 slab 是 slab分配器進行操作的最小分配單位,因此如果需要對 slab 進行擴充套件,這也就是所擴充套件的最小值。通常來說,每個 slab 被分配為多個物件。

        由於物件是從 slab 中進行分配和釋放的,因此單個 slab 可以在 slab列表之間進行移動。例如,當一個 slab中的所有物件都被使用完時,就從slabs_partial 列表中移動到 slabs_full 列表中。當一個 slab完全被分配並且有物件被釋放後,就從 slabs_full 列表中移動到slabs_partial 列表中。當所有物件都被釋放之後,就從 slabs_partial 列表移動到 slabs_empty 列表中。

slab背後的動機

        與傳統的記憶體管理模式相比, slab快取分配器提供了很多優點。首先,核心通常依賴於對小物件的分配,它們會在系統生命週期內進行無數次分配。slab快取分配器通過對類似大小的物件進行快取而提供這種功能,從而避免了常見的碎片問題。slab分配器還支援通用物件的初始化,從而避免了為同一目而對一個物件重複進行初始化。最後,slab分配器還可以支援硬體快取對齊和著色,這允許不同快取中的物件佔用相同的快取行,從而提高快取的利用率並獲得更好的效能。