Buddy 簡介
記憶體是計算機系統中最重要的核心資源之一,Buddy 系統是 Linux 最底層的記憶體管理機制,它使用 Page 粒度來管理記憶體。通常情況下一個 Page 的大小為 4K,在 Buddy 系統中配置設定、釋放、回收的最小機關都是 Page。
上圖是 Buddy 系統的内部組織結構,本篇文章隻關心未配置設定區域Free區域的管理,下篇文章再來分析可回收區域的管理。
一個系統的記憶體總大小動辄幾G幾十G,不同的記憶體區域也有不同的特性。Buddy 使用階層化的結構把這些特性給組織起來:
- 1、Node。在 NUMA 架構下存在多個 Memory 和 CPU 節點,不同 CPU 通路不同 Memory 節點的速度是不一樣的,使用 Node 的形式把各個 Memory 節點的記憶體管理起來。
- 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 塊合并成一個大塊挂載到更高一階的連結清單,在挂載的時候繼續嘗試合并。這就是 Buddy 的核心思想,已2的幂個 page 的長度來管理記憶體友善配置設定和釋放,最核心的目的就是減少記憶體的碎片化。
- 4、Migrate Type。為了進一步減少碎片化,系統對記憶體按照遷移類型進行了分類,最基本的遷移類型有:不可移動(unmovable)、可移動(movable)、可回收(reclaimable)。初始的最大塊空閑記憶體都是 unmovable 的,如果其中一小塊配置設定給了 reclaimable ,那麼剩下的記憶體都變成了 reclaimable。這樣壞的類型和壞的類型集中到了一起,避免壞情況的擴散進而造成多個 Free 區域無法合并的情況。
- 5、PerCPU 1 Page Cache。大于 1 Page 的記憶體配置設定大多發生在核心态,而使用者态的記憶體配置設定使用的是缺頁機制是以配置設定的大小一般是 1 Page。針對大小為 1 Page 的記憶體配置設定,系統設計了一個免鎖的 PerCPU cache 來支撐。1 Page (Order = 0) 的空閑記憶體優先釋放到 PCP 中,超過了一定 batch 才會釋放到 Order Freelist當中;同樣 1 Page 的記憶體也優先在 PCP 中配置設定。
更多linux核心視訊教程文檔資料免費領取背景私信【核心】自行擷取.
Buddy 初始化
Struct Page 初始化
以 Page 大小的粒度來管理記憶體,一個 Page 對應的實體記憶體稱為頁框 (Page Frame)。另外為了應對複雜的管理,系統給每個 Page 還配置設定了一個管理結構 struct page,系統在初始化時會預留這部分的實體記憶體并且映射到 vmemmap 區域 (參考:核心位址空間布局),核心根據實體頁幀的編号 pfn 就能在 vmemmap 區域中找到對應的 struct page 結構。
struct page 結構存儲了很多資訊 (參考:Page 頁幀管理詳解)。在 sparse_init() 時已經把所有的struct page 結構清零,zone_sizes_init() 初始化時主要初始化兩部分資訊:
- 1、初始化 struct page :
将 page->flags 中儲存的 setcion、node、zone 設定成對應的 index,這樣後續操作 struct page 結構時就能快速的找到對應的 setcion、node、zone 而不需要重新根據 pfn 來進行計算。page->flags 中的 flag 部分初始化為 0。
另外給 page->_refcount、_mapcount、_last_cpupid、lru 等成員都進行了初始化。
start_kernel() → setup_arch() → x86_init.paging.pagetable_init() → native_pagetable_init() → paging_init() → zone_sizes_init() → free_area_init_nodes() → free_area_init_node() → free_area_init_core():
|→ memmap_init() → memmap_init_zone()
void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
unsigned long start_pfn, enum memmap_context context)
{
for (pfn = start_pfn; pfn < end_pfn; pfn++) {
/* (2) 目前是一個pageblock的第一個page */
if (!(pfn & (pageblock_nr_pages - 1))) {
struct page *page = pfn_to_page(pfn);
/* (2.1) 初始化對應的 struct page 結構 */
__init_single_page(page, pfn, zone, nid,
context != MEMMAP_HOTPLUG);
/* (2.2) 初始化時把所有pageblock的migratetype設定成MIGRATE_MOVABLE */
set_pageblock_migratetype(page, MIGRATE_MOVABLE);
cond_resched();
/* (3) pageblock中的其他page */
} else {
/* (3.1) 初始化對應的 struct page 結構 */
__init_single_pfn(pfn, zone, nid,
context != MEMMAP_HOTPLUG);
}
}
}
↓
__init_single_pfn()
↓
static void __meminit __init_single_page(struct page *page, unsigned long pfn,
unsigned long zone, int nid, bool zero)
{
/* (2.1.1) 如果需要,對struct page結構清零 */
if (zero)
mm_zero_struct_page(page);
/* (2.1.2) 設定page->flags中的setcion index、node index、zone index */
set_page_links(page, zone, nid, pfn);
/* (2.1.3) 設定page->_refcount = 1 */
init_page_count(page);
/* (2.1.4) 設定page->_mapcount = -1 */
page_mapcount_reset(page);
/* (2.1.5) 設定page->_last_cpupid = -1 */
page_cpupid_reset_last(page);
/* (2.1.6) 初始化page->lru */
INIT_LIST_HEAD(&page->lru);
#ifdef WANT_PAGE_VIRTUAL
/* The shift won't overflow because ZONE_NORMAL is below 4G. */
if (!is_highmem_idx(zone))
set_page_address(page, __va(pfn << PAGE_SHIFT));
#endif
}
- 2、配置設定并初始化 zone->pageblock_flags:文章開始時說了 migrate type 的概念。系統把記憶體劃分成多個 pageblock,一個 pageblock 即對應 (2^max_order x Page),每個 pageblock 擁有自己的 migrate type。
系統以 zone 為機關配置設定空間來儲存所有 pageblock 的 migrate type:
start_kernel() → setup_arch() → x86_init.paging.pagetable_init() → native_pagetable_init() → paging_init() → zone_sizes_init() → free_area_init_nodes() → free_area_init_node() → free_area_init_core():
|→ setup_usemap()
static void __init setup_usemap(struct pglist_data *pgdat,
struct zone *zone,
unsigned long zone_start_pfn,
unsigned long zonesize)
{
unsigned long usemapsize = usemap_size(zone_start_pfn, zonesize);
zone->pageblock_flags = NULL;
if (usemapsize)
/* (1) 配置設定存儲目前zone裡所有pageblock的migrate标志 */
zone->pageblock_flags =
memblock_virt_alloc_node_nopanic(usemapsize,
pgdat->node_id);
}
pageblock 的初始 migrate type 為 MIGRATE_MOVABLE:
void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
unsigned long start_pfn, enum memmap_context context)
{
for (pfn = start_pfn; pfn < end_pfn; pfn++) {
/* (2) 目前是一個pageblock的第一個page */
if (!(pfn & (pageblock_nr_pages - 1))) {
struct page *page = pfn_to_page(pfn);
/* (2.1) 初始化對應的 struct page 結構 */
__init_single_page(page, pfn, zone, nid,
context != MEMMAP_HOTPLUG);
/* (2.2) 初始化時把所有pageblock的migratetype設定成MIGRATE_MOVABLE */
set_pageblock_migratetype(page, MIGRATE_MOVABLE);
cond_resched();
/* (3) pageblock中的其他page */
} else {
/* (3.1) 初始化對應的 struct page 結構 */
__init_single_pfn(pfn, zone, nid,
context != MEMMAP_HOTPLUG);
}
}
}
pageblock 中第一個配置設定的記憶體的 migrate type 決定了整個 pageblock 的 migrate type。
Buddy 初始化
在核心啟動過程中在 Buddy 初始化以前,系統使用一個簡便的 Memblock 機制來管理記憶體。在 Buddy 資料結構準備好後,需要把 Memblock 中的記憶體釋放到 Buddy 當中。
這就是 Buddy 系統初始的狀态,除了保留的記憶體,其他的記憶體都處于 Free 狀态:
start_kernel() → mm_init() → mem_init() → free_all_bootmem():
unsigned long __init free_all_bootmem(void)
{
unsigned long pages;
/* (1) 将每個node每個zone管理的page清零:z->managed_pages = 0 */
reset_all_zones_managed_pages();
/* (2) 将memblock中的記憶體轉移到buddy系統中 */
pages = free_low_memory_core_early();
totalram_pages += pages;
return pages;
}
↓
static unsigned long __init free_low_memory_core_early(void)
{
unsigned long count = 0;
phys_addr_t start, end;
u64 i;
memblock_clear_hotplug(0, -1);
/* (2.1) 周遊memblock中的保留記憶體,将其對應的`struct page`結構page->flags設定PG_reserved标志 */
for_each_reserved_mem_region(i, &start, &end)
reserve_bootmem_region(start, end);
/*
* We need to use NUMA_NO_NODE instead of NODE_DATA(0)->node_id
* because in some case like Node0 doesn't have RAM installed
* low ram will be on Node1
*/
/* (2.2) 周遊memblock中尚未配置設定的記憶體,将其釋放到buddy系統中 */
for_each_free_mem_range(i, NUMA_NO_NODE, MEMBLOCK_NONE, &start, &end,
NULL)
count += __free_memory_core(start, end);
return count;
}
↓
__free_memory_core()
↓
static void __init __free_pages_memory(unsigned long start, unsigned long end)
{
int order;
/* (2.2.1) 對需要釋放的區域,拆分成盡可能大的 2^order 記憶體塊去釋放 */
while (start < end) {
order = min(MAX_ORDER - 1UL, __ffs(start));
/* (2.2.1.1) 計算最大的釋放長度(2^order)page */
while (start + (1UL << order) > end)
order--;
/* (2.2.1.2) 繼續釋放 */
__free_pages_bootmem(pfn_to_page(start), start, order);
start += (1UL << order);
}
}
↓
__free_pages_bootmem() → __free_pages_boot_core() → __free_pages()
具體的釋放細節 __free_pages() 在下一節中解析。
- - 核心技術中文網 - 建構全國最權威的核心技術交流分享論壇
轉載位址:深入剖析Buddy 記憶體管理機制(上) - 圈點 - 核心技術中文網 - 建構全國最權威的核心技術交流分享論壇