淺析Buddy演算法
導讀 | 核心記憶體管理比較複雜,主要包含了Buddy演算法,vmalloc管理,slab演算法,kmapper及與初始化階段實體記憶體管理相關的兩個模組memblock和bootmem。除了上述模組外,還有記憶體遷移,水線檢測,kmemleak,記憶體資訊統計,PCP等輔助內容。在本文中,我將重點介紹Buddy演算法(又稱為夥伴演算法),該演算法是以頁為單位進行管理的。 |
目前,核心中的Buddy演算法採用下圖的方式管理記憶體。我把Buddy演算法分為如下圖所示的三部分,在區域一中,核心的資料結構是struct zone;在區域二中核心的資料結構是struct free_area free_area[MAX_ORDER];在區域三中,page連結串列是核心內容。接下來,我將詳細介紹這三個板塊,並透過分配函式來說明Buddy的工作細節。
通常用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組織在一起的。
在記憶體管理和分配過程中,有一個重要引數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分配一個物理頁(即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演算法在進行記憶體分配時,會根據水線設定,來進行記憶體回收或者喚醒核心執行緒kswapd,或者是採用CPU的冷熱頁面佇列進行記憶體分配,或者是進行頁面移動等。假如已經嘗試頁面移動,kswapd已經喚出了一些頁面,同時也進行了記憶體回收,依然沒有可分配的記憶體,此時就會觸發out_of_memory。總之,這些特殊情況的處理方式均離不開圖1所示的Buddy框架。
下圖是我根據我本地的程式碼整理的採用kmalloc申請大記憶體時的呼叫過程(使用kmalloc申請小記憶體時不透過Buddy),其中最關鍵的記憶體分配函式是prepare_alloc_pages,get_page_from_freelist,rmqueue 和__alloc_pages_slowpath。感興趣的朋友可以結合圖1所示的框架檢視這幾個函式的具體實現。
當向系統釋放一個物理頁的時候,會透過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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 淺析雜湊演算法演算法
- Java 集合中的排序演算法淺析Java排序演算法
- RSA非對稱加密演算法淺析加密演算法
- 淺析vue2.0的diff演算法Vue演算法
- 整合學習演算法(Ensemble Method)淺析演算法
- JVM 系列文章之 GC 演算法淺析JVMGC演算法
- iOS Block淺淺析iOSBloC
- 對話 | 淺析NEO的dBFT共識演算法演算法
- 淺析RedisRedis
- Jvm 淺析JVM
- CGLib淺析CGLib
- 淺析XMLXML
- MongoDB淺析MongoDB
- 淺析 JWTJWT
- 淺析 DDD
- RunLoop 淺析OOP
- 淺析 ReentrantLockReentrantLock
- Unstated淺析
- 淺析SharedPreferences
- Nginx淺析Nginx
- 淺析PromisePromise
- ejs 淺析JS
- 淺析KubernetesStatefulSet
- AIDL淺析AI
- ArrayList淺析
- DIFF演算法淺析(三)在react中的實現演算法React
- 淺析AIGC for MMKGAIGC
- Seata原理淺析
- 淺析JNDI注入
- 淺析DES原理
- shadow DOM 淺析
- 淺析Promise原理Promise
- AQS原理淺析AQS
- css: clip淺析CSS
- Webpack 原理淺析Web
- Jackson - 淺析@JsonIncludeJSON
- InheritedWidget原理淺析
- Kubernetes Scheduler淺析