深入剖析Buddy 內存管理機制(上)
Buddy 簡介
內存是計算機系統(tǒng)中最重要的核心資源之一,Buddy 系統(tǒng)是 Linux 最底層的內存管理機制,它使用 Page 粒度來管理內存。通常情況下一個 Page 的大小為 4K,在 Buddy 系統(tǒng)中分配、釋放、回收的最小單位都是 Page。

上圖是 Buddy 系統(tǒng)的內部組織結構,本篇文章只關心未分配區(qū)域Free區(qū)域的管理,下篇文章再來分析可回收區(qū)域的管理。
一個系統(tǒng)的內存總大小動輒幾G幾十G,不同的內存區(qū)域也有不同的特性。Buddy 使用層次化的結構把這些特性給組織起來:
1、Node。在 NUMA 架構下存在多個 Memory 和 CPU 節(jié)點,不同 CPU 訪問不同 Memory 節(jié)點的速度是不一樣的,使用 Node 的形式把各個 Memory 節(jié)點的內存管理起來。
2、Zone。某些外設只能訪問低 16M 的物理地址,某些外設只能訪問低 4G 的物理地址,32bit 的內核空間只能直接映射低 896M 物理地址。根據這些地址空間的限制,把同一個 node 內的內存再劃分成多個 zone 。
3、Order Freelist。按照空閑內存塊的長度,把內存掛載到不同長度的 freelist 鏈表中。freelist 的長度是以 (2^order x Page) 來遞增的,即 1 page、2 page、4 page … 2^n,通常情況下最大 order 為10 對應的空閑內存大小為 4M bytes。在分配時,如果一個空閑塊的大小不能被任一長度整除,它就從大到小逐個分解成多個 (2^order x Page) 塊來掛載;在釋放時,首先把內存釋放到對應長度的鏈表中,隨后看看和該內存大小相同、地址相鄰的兄弟塊(Buddy)是不是free的,如果可以和 buddy 塊合并成一個大塊掛載到更高一階的鏈表,在掛載的時候繼續(xù)嘗試合并。這就是 Buddy 的核心思想,已2的冪個 page 的長度來管理內存方便分配和釋放,最核心的目的就是減少內存的碎片化。
4、Migrate Type。為了進一步減少碎片化,系統(tǒng)對內存按照遷移類型進行了分類,最基本的遷移類型有:不可移動(unmovable)、可移動(movable)、可回收(reclaimable)。初始的最大塊空閑內存都是 unmovable 的,如果其中一小塊分配給了 reclaimable ,那么剩下的內存都變成了 reclaimable。這樣壞的類型和壞的類型集中到了一起,避免壞情況的擴散從而造成多個 Free 區(qū)域無法合并的情況。
5、PerCPU 1 Page Cache。大于 1 Page 的內存分配大多發(fā)生在內核態(tài),而用戶態(tài)的內存分配使用的是缺頁機制所以分配的大小一般是 1 Page。針對大小為 1 Page 的內存分配,系統(tǒng)設計了一個免鎖的 PerCPU cache 來支撐。1 Page (Order = 0) 的空閑內存優(yōu)先釋放到 PCP 中,超過了一定 batch 才會釋放到 Order Freelist當中;同樣 1 Page 的內存也優(yōu)先在 PCP 中分配。

【文章福利】小編推薦自己的Linux內核技術交流群:【891587639】整理了一些個人覺得比較好的學習書籍、視頻資料共享在群文件里面,有需要的可以自行添加哦?。。。ê曨l教程、電子書、實戰(zhàn)項目及代碼)? ?


Buddy 初始化
Struct Page 初始化
以 Page 大小的粒度來管理內存,一個 Page 對應的物理內存稱為頁框 (Page Frame)。另外為了應對復雜的管理,系統(tǒng)給每個 Page 還分配了一個管理結構 struct page,系統(tǒng)在初始化時會預留這部分的物理內存并且映射到 vmemmap 區(qū)域 (參考:內核地址空間布局),內核根據物理頁幀的編號 pfn 就能在 vmemmap 區(qū)域中找到對應的 struct page 結構。
struct page 結構存儲了很多信息 (參考:Page 頁幀管理詳解)。在 sparse_init() 時已經把所有的struct page 結構清零,zone_sizes_init() 初始化時主要初始化兩部分信息:
1、初始化 struct page :

將 page->flags 中保存的 setcion、node、zone 設置成對應的 index,這樣后續(xù)操作 struct page 結構時就能快速的找到對應的 setcion、node、zone 而不需要重新根據 pfn 來進行計算。page->flags 中的 flag 部分初始化為 0。
另外給 page->_refcount、_mapcount、_last_cpupid、lru 等成員都進行了初始化。
2、分配并初始化 zone->pageblock_flags:文章開始時說了 migrate type 的概念。系統(tǒng)把內存劃分成多個 pageblock,一個 pageblock 即對應 (2^max_order x Page),每個 pageblock 擁有自己的 migrate type。
系統(tǒng)以 zone 為單位分配空間來保存所有 pageblock 的 migrate type:
pageblock 的初始 migrate type 為 MIGRATE_MOVABLE:
pageblock 中第一個分配的內存的 migrate type 決定了整個 pageblock 的 migrate type。
Buddy 初始化
在內核啟動過程中在 Buddy 初始化以前,系統(tǒng)使用一個簡便的 Memblock 機制來管理內存。在 Buddy 數據結構準備好后,需要把 Memblock 中的內存釋放到 Buddy 當中。

這就是 Buddy 系統(tǒng)初始的狀態(tài),除了保留的內存,其他的內存都處于 Free 狀態(tài):
具體的釋放細節(jié) __free_pages() 在下一節(jié)中解析。
原文作者:人人極客社區(qū)
