淺析Buddy演算法

大雄45發表於2022-07-30
導讀 核心記憶體管理比較複雜,主要包含了Buddy演算法,vmalloc管理,slab演算法,kmapper及與初始化階段實體記憶體管理相關的兩個模組memblock和bootmem。除了上述模組外,還有記憶體遷移,水線檢測,kmemleak,記憶體資訊統計,PCP等輔助內容。在本文中,我將重點介紹Buddy演算法(又稱為夥伴演算法),該演算法是以頁為單位進行管理的。

淺析Buddy演算法淺析Buddy演算法

Buddy演算法介紹

目前,核心中的Buddy演算法採用下圖的方式管理記憶體。我把Buddy演算法分為如下圖所示的三部分,在區域一中,核心的資料結構是struct zone;在區域二中核心的資料結構是struct free_area free_area[MAX_ORDER];在區域三中,page連結串列是核心內容。接下來,我將詳細介紹這三個板塊,並透過分配函式來說明Buddy的工作細節。

淺析Buddy演算法淺析Buddy演算法

1.zone

通常用zone代表不同的記憶體管理區,即把記憶體劃分成不同的組,每個組就是一個zone。

在核心中,每個zone透過結構體struct zone來表示,其結構體中主要成員如下:

struct zone {
  unsigned long watermark[NR_WMARK];
unsigned long   zone_start_pfn;    
unsigned long   managed_pages;     
unsigned long   spanned_pages;     
unsigned long   present_pages;
struct free_area  free_area[MAX_ORDER];
const char  *name;
struct pglist_data  *zone_pgdat;
}

標識記憶體水線的成員unsigned long watermark[NR_WMARK],其中NR_WMARK定義如下:

enum zone_watermarks {
  WMARK_MIN,
  WMARK_LOW,
  WMARK_HIGH,
  NR_WMARK
};

從上面定義可知,每個zone存在三個水線,若當前zone中空閒頁高於WMARK_HIGH,則當前zone區的空閒記憶體較多;若空閒頁低於WMARK_LOW,則交換守護程式開始將記憶體交換到磁碟上;若空閒頁低於WMARK_MIN,則記憶體回收系統還需要大量回收記憶體。在使用Buddy進行記憶體申請時,會進行水線判斷,從而進行相應的操作;

夥伴系統管理相關的成員

unsigned long   zone_start_pfn;    
unsigned long   managed_pages;     
unsigned long   spanned_pages;     
unsigned long   present_pages;
struct free_area  free_area[MAX_ORDER]

前四個是相應zone區對應的頁號資訊,free_area是夥伴演算法中的關鍵成員,也是區域一和區域二銜接的關鍵成員,每個zone區會劃分為MAX_ORDER個組,陣列free_area中的每個成員就代表一個組。

成員const char *name指明瞭對應zone區的名稱,通常情況下為dma,Normal或highmem,其中highmem不會出現在64位的系統中;下面是典型的32位系統中的zone劃分方式:
ZONE_DMA(0~16M):DMA記憶體分配區;

ZONE_NORMAL(16MB~896MB):普通對映的記憶體區域;

ZONE_HIGHMEM(896MB~):高階記憶體區域,其中的頁不能永久對映到核心地址空間;核心一般不使用,如果要使用,透過kmap做動態對映;

指向zone區對應node的指標成員struct pglist_data *zone_pgdat,node是記憶體管理中的一個重要成員。對於UMA架構,僅有一個node,所有的zone均屬於同一個node,但對於NUMA架
構,會有多個不同的node,每個node又劃分為不同的zone,所有zone區也是透過struct pglist_data組織在一起的。

2.free_area和page連結串列

在記憶體管理和分配過程中,有一個重要引數order,當我們採用kmalloc申請大記憶體時,最後會呼叫函式__alloc_pages_nodemask來進行記憶體申請。該函式需要一個引數order,當order = 0時,表示要申請的記憶體大小是1(20)個頁面;當order = 1時,表示要申請的記憶體大小是2(21)個頁面。

在Buddy演算法中,把記憶體按照2的冪次方(即2order,order的範圍從0到MAX_ORDER)劃分成不同的組,每個組分別用對應的free_area[order]表示,例如free_area[0]對應的就是由記憶體塊大小為20個頁塊組成的組,free_area[1]對應的就是由記憶體塊大小為21個頁塊組成的組,而結構體struct zone正是透過成員free_area把圖1中的區域一和區域二串聯在一起。

結構體struct free_area定義如下(不同平臺或者不同核心會有差異,但核心思想相同):

struct free_area {
  struct list_head  free_list[MIGRATE_TYPES];
  unsigned long   nr_free;
 };

從該結構可以看出,每一個free_area又根據MIGRATE_TYPES劃分為不同的組,每組分別透過連結串列free_list把同一類頁塊串聯在一起,這樣free_list就把圖1中的區域二和區域三串聯在一起了,從而間接的把區域一和區域三關聯在一起了。

結構體struct page比較複雜,其中有一個成員struct list_head lru,透過該成員把圖1中區域三中的頁塊同區域二中對應的free_list連結在一起。

Buddy記憶體分配與釋放

在此,我將透過一個示例來簡要地展示Buddy記憶體分配的核心思想。當透過Buddy分配一個物理頁(即order = 0)時,會從對應zone區中free_area[0]管理的區域分配一個頁面(暫時先不考慮PCP的情況),並將該頁面從 free_area[0] 連結串列中移除;當free_area[0] 上沒有可用的物理頁時,Buddy會在free_area[1]上查詢,若存在可用的物理頁,則將該頁塊從free_area[1] 的連結串列中移除,同時把該頁塊拆分成兩塊,其中一塊插入到free_area[0]中,另一塊傳遞給記憶體請求者;倘若free_area[1]上也沒有可用頁,則會繼續向上查詢。

下圖2所示是沒有對應oder = 0的頁塊情況,因此把order = 1中的一個頁塊進行拆分,一半返回給order = 1的連結串列,一半返回給請求者。假如order = 1中沒有需要的頁塊,在記憶體分配過程中會繼續從order = 3中進行查詢,直到找到頁塊或者遍歷完所有order。

淺析Buddy演算法淺析Buddy演算法

實際上,在整個記憶體分配過程中,會伴隨很多特殊情況處理。Buddy演算法在進行記憶體分配時,會根據水線設定,來進行記憶體回收或者喚醒核心執行緒kswapd,或者是採用CPU的冷熱頁面佇列進行記憶體分配,或者是進行頁面移動等。假如已經嘗試頁面移動,kswapd已經喚出了一些頁面,同時也進行了記憶體回收,依然沒有可分配的記憶體,此時就會觸發out_of_memory。總之,這些特殊情況的處理方式均離不開圖1所示的Buddy框架。

下圖是我根據我本地的程式碼整理的採用kmalloc申請大記憶體時的呼叫過程(使用kmalloc申請小記憶體時不透過Buddy),其中最關鍵的記憶體分配函式是prepare_alloc_pages,get_page_from_freelist,rmqueue 和__alloc_pages_slowpath。感興趣的朋友可以結合圖1所示的框架檢視這幾個函式的具體實現。

淺析Buddy演算法淺析Buddy演算法

當向系統釋放一個物理頁的時候,會透過struct page來推匯出該page對應的zone區(比如該page屬於NORMAL區)和對應的page型別,緊接著將根據對應的order進行頁的合併(即圖1中區域三的部分進行合併),最後將合併後的塊插入到新的order中去,這個合併過程一直持續下去,直到不能合併或者已經合併到最大的order處。例如當釋放的物理頁得到其屬於NORMAL區的free_area[0],此時free_area[0]中存在頁面可以和釋放的頁面合併,從而把合併後的新增到free_area[1]中,這個合併操作會一直持續下去,直到合併完為止。這個過程實際上就是圖2的反過程。

總結

記憶體的管理比較複雜,本文主要介紹了Buddy的核心思想,但這僅僅是記憶體管理的冰山一角,卻是比較基礎且核心的內容,因此瞭解Buddy整體架構是非常有必要的。

作者介紹
趙青窕,51CTO社群編輯,從事多年驅動開發。研究興趣包含安全OS和網路安全領域,發表過網路相關專利。

原文來自:


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69955379/viewspace-2908268/,如需轉載,請註明出處,否則將追究法律責任。