1.前言
本文所述關於內存管理的系列文章主要是對陳莉君老師所講述的內存管理知識講座的整理。
本講座主要分三個主題展開對內存管理進行講解:內存管理的硬件基礎、虛擬地址空間的管理、物理地址空間的管理.
本文將主要以X86架構為例來介紹伙伴算法和slab分配
2.伙伴算法概述
- 塊鏈表
Linux的伙伴算法將所有的空閑頁面分成MAX_ORDER+1(MAX_ORDER默認大小為11)個塊鏈表
每個鏈表中的一個節點指向一個含有2的冪次個頁面的塊,即頁塊或簡稱塊
圖 伙伴算法結構實例圖
0:每個頁塊的大小為1個頁
1:每個頁塊的大小為2個頁
。。。
MAX_ORDER:每個頁塊的大小為2的MAX_ORDER次方個頁
- 伙伴
大小相同、物理地址連續的兩個頁塊稱為伙伴
- 伙伴算法工作原理
首先在大小滿足要求的塊鏈表中查找是否有空閑塊,若有則直接分配,否則在更大的塊的塊鏈表中查找;
逆過程是塊的釋放,此時會把滿足伙伴關系的塊合並,組成一個更大的塊,並插入到相應的塊鏈表中
3.與伙伴算法有關的數據結構
每個頁框對應一個struct page實例
每個內存區關聯一個struct zone區域,該結構中用free_area數組對空閑頁框進行管理
4.物理內存管理機制
- 伙伴算法
負責大塊連續物理內存的分配和釋放,以頁框為基本單位,避免外部碎片
- slab緩存
負責小塊物理內存的分配,並且它也作為一個緩存,主要針對內核中經常分配並釋放的對象
- per-CPU頁框緩存
內核經常請求和釋放單個頁框,該緩存包含預先分配的頁框,用於滿足本地CPU發出的單一頁框請求
5.伙伴算法分配原理
- 伙伴算法的分配原理
如果分配階為i的頁框塊,那么先從第i條頁框塊鏈表中查找是否存在這么大小的空閑塊。如果有則分配,否則在第i+1條鏈表中繼續查找,直到找到為止
- 伙伴算法分配舉例
如果申請大小為8個(分配階為3)頁的頁塊,但卻在頁塊大小為32(分配階為5)的鏈表中找到空閑塊,則先將這32個頁面對半等分,前一半作為分配使用,另一半作為新元素插入下級大小為16(分配階為4)的鏈表中;
繼續將前一半大小為16的頁塊等分,一半分配,另一半插入大小為8(分配階為3)的鏈表中
6. 頁框分配的實現
5節描述的伙伴算法通過__rmqueue()主要調用如下兩個函數:
- __rmqueue_smallest()
在指定的內存分配區上,從說請求分配階order對應的鏈表開始查找所需大小的空閑塊,如果不成功者從高一階的鏈表上繼續查找
- expand()
如果所得到的內存塊大於說請求的內存塊,則按照伙伴算法的分配原理將大的頁框塊分裂為小的頁框塊
7.物理內存分配器
基於伙伴算法、每CPU高速緩存和slab高速緩存形成兩種內存分配器
- 分區頁框分配器(zoned page frame allocator)
處理對連續頁框的內存分配請求。分區頁框分配器分為兩大部分:前端的管理區分配器和伙伴系統
管理區分配器負責搜索一個能滿足請求頁框大小的管理區;
在每個管理區中,具體的頁框分配工作由伙伴系統負責,為了達到更好的性能,單個頁框的申請直接由每cpu頁框高速緩存完成
圖 分區頁框分配器示例圖
- slab分配器
將各種分配對象分區放進高速緩存,即每個高速緩存都對同類型分配對象的一種“儲備”
8. 頁框分配函數的關系圖
圖 頁框分配函數的關系
- 內核有6個稍有差別的函數或宏來請求物理頁框,他們將核心的分配函數__alloc_pages_nodemask封裝滿足不同的分配需求
- 綠色函數返回線性地址,藍色函數返回頁塊的首頁框描述符的地址
- __alloc_pages_nodemask是對分區頁框分配器的具體體現
9. malloc的最終具體
進程調用malloc()->
獲得一塊虛擬內存->
內存訪問,如果還未分配物理內存->
發生缺頁異常->
內核調用__alloc_pages為進程分配物理頁框,並將物理內存和虛擬內存的映射關系寫入頁表