linux核心原始碼分析-夥伴系統
之前的文章已經介紹了夥伴系統,這篇我們主要看看原始碼中是如何初始化夥伴系統、從夥伴系統中分配頁框,返回頁框於夥伴系統中的。
我們知道,每個管理區都有自己的夥伴系統管理屬於這個管理區的頁框,這也說明了,在夥伴系統初始化時,管理區必須要已經存在(初始化完成)了。在管理區描述符(struct zone)中,struct free_area就專門用於描述夥伴系統的。在一個管理區中,夥伴系統一共維護著包含1,2,4,8,16,...,512,1024個連續頁框的連結串列,當我需要從此管理區分配8個頁框時,夥伴系統會從包含8個連續頁框的連結串列取出一個結點(一個結點就是一個連續的8個頁框),然後分配給我。這時如果8個連續頁框的連結串列為空,則會從16個連續頁框的連結串列取出一個結點將其分為兩個8個連續頁框的結點,並把它們放入8頁框的連結串列,然後再分配其中一個結點給我。而當回收頁框時,會嘗試與前後連續頁框組成更大的頁框塊,加入到更高階的頁框頁表中,比如,我釋放了8個頁框,夥伴系統會嘗試將我釋放這連續的8個頁框與前8個頁框或者後8個頁框合併,一直嘗試合併到不能合併為止,並加入相應的連結串列中。
而注意的是,在系統中,需要向夥伴系統申請頁框時,頁框的數量都是以2的次方計算的,也就是申請頁框的數量一定是1,2,4,8,16,32,64......1024這些數中的一個。釋放時也是這樣的道理,不過一般而言,會從夥伴系統中申請頁框操作最頻繁的模組可能就是slab/slub和建立程序的線性區了。
/* 記憶體管理區描述符 */ struct zone {........ </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 實現每CPU頁框快取記憶體,裡面包含每個CPU的單頁框的連結串列 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span> <span style="color:rgb(0,0,255); line-height:1.5!important">struct</span> per_cpu_pageset __percpu *<span style="line-height:1.5!important">pageset; ........ </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 對應於夥伴系統中MIGRATE_RESEVE鏈的頁塊的數量 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span> <span style="color:rgb(0,0,255); line-height:1.5!important">int</span><span style="line-height:1.5!important"> nr_migrate_reserve_block; ........ </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 標識出管理區中的空閒頁框塊,用於夥伴系統 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span> <span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> MAX_ORDER為11,分別代表包含大小為1,2,4,8,16,32,64,128,256,512,1024個連續頁框的連結串列,具體見下面 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span> <span style="color:rgb(0,0,255); line-height:1.5!important">struct</span><span style="line-height:1.5!important"> free_area free_area[MAX_ORDER]; ......
}
/ 夥伴系統的一個塊,描述1,2,4,8,16,32,64,128,256,512或1024個連續頁框的塊 / struct free_area { / 指向這個塊中所有空閒小塊的第一個頁描述符,這些小塊會按照MIGRATE_TYPES型別存放在不同指標裡 / struct list_head free_list[MIGRATE_TYPES]; / 空閒小塊的個數 / unsigned long nr_free; };
/ 每CPU快取記憶體描述符 / struct per_cpu_pageset { / 核心結構,快取記憶體頁框結構 / struct per_cpu_pages pcp; #ifdef CONFIG_NUMA s8 expire;
struct per_cpu_pages { / 當前CPU快取記憶體中頁框個數 / int count; / number of pages in the list / / 上界,當此CPU快取記憶體中頁框個數大於high,則會將batch個頁框放回夥伴系統 / int high; / high watermark, emptying needed / / 在快取記憶體中將要新增或被刪去的頁框個數,當連結串列中頁框數量多個上界時會將batch個頁框放回夥伴系統,當連結串列中頁框數量為0時則從夥伴系統中獲取batch個頁框 / int batch; / chunk size for buddy add/remove /
<span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> Lists of pages, one per migrate type stored on the pcp-lists </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span>
<span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 頁框的連結串列,如果需要冷快取記憶體,從連結串列尾開始獲取頁框,如果需要熱快取記憶體,從連結串列頭開始獲取頁框 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span>
<span style="color:rgb(0,0,255); line-height:1.5!important">struct</span><span style="line-height:1.5!important"> list_head lists[MIGRATE_PCPTYPES];
};
在各個頁框塊連結串列中又有幾個以MIGRATE_TYPES區分的小連結串列,MIGRATE_TYPES型別如下:
- MIGRATE_UNMOVABLE:頁框內容不可移動,在記憶體中位置必須固定,無法移動到其他地方,核心核心分配的大部分頁面都屬於這一類。
- MIGRATE_RECLAIMABLE:頁框內容可回收,不能直接移動,但是可以回收,因為還可以從某些源重建頁面,比如對映檔案的資料屬於這種類別,kswapd會按照一定的規則,週期性的回收這類頁面。
- MIGRATE_MOVABLE:頁框內容可移動,屬於使用者空間應用程式的頁屬於此類頁面,它們是通過頁表對映的,因此我們只需要更新頁表項,並把資料複製到新位置就可以了,當然要注意,一個頁面可能被多個程序共享,對應著多個頁表項。
- MIGRATE_PCPTYPES:用來表示每CPU頁框快取記憶體的資料結構中的連結串列的遷移型別數目。
- MIGRATE_CMA: 預留一段的記憶體給驅動使用,但當驅動不用的時候,夥伴系統可以分配給使用者程序用作匿名記憶體或者頁快取。而當驅動需要使用時,就將程序佔用的記憶體通過回收或者遷移的方式將之前佔用的預留記憶體騰出來,供驅動使用。
- MIGRATE_ISOLATE:不能從這個連結串列分配頁框,因為這個連結串列專門用於NUMA結點移動實體記憶體頁,將實體記憶體頁內容移動到使用這個頁最頻繁的CPU。
/* 這幾個連結串列主要用於反記憶體碎片 */ enum { MIGRATE_UNMOVABLE, /* 頁框內容不可移動,在記憶體中位置必須固定,無法移動到其他地方,核心核心分配的大部分頁面都屬於這一類。 */ MIGRATE_RECLAIMABLE, /* 頁框內容可回收,不能直接移動,但是可以回收,因為還可以從某些源重建頁面,比如對映檔案的資料屬於這種類別,kswapd會按照一定的規則,週期性的回收這類頁面。 */ MIGRATE_MOVABLE, /* 頁框內容可移動,屬於使用者空間應用程式的頁屬於此類頁面,它們是通過頁表對映的,因此我們只需要更新頁表項,並把資料複製到新位置就可以了 * 當然要注意,一個頁面可能被多個程序共享,對應著多個頁表項。 */ MIGRATE_PCPTYPES, /* 用來表示每CPU頁框快取記憶體的資料結構中的連結串列的遷移型別數目 */ MIGRATE_RESERVE = MIGRATE_PCPTYPES, #ifdef CONFIG_CMA MIGRATE_CMA, /* 預留一段的記憶體給驅動使用,但當驅動不用的時候,夥伴系統可以分配給使用者程序用作匿名記憶體或者頁快取。而當驅動需要使用時,就將程序佔用的記憶體通過回收或者遷移的方式將之前佔用的預留記憶體騰出來,供驅動使用。 */ #endif #ifdef CONFIG_MEMORY_ISOLATION MIGRATE_ISOLATE, /* 不能從這個連結串列分配頁框,因為這個連結串列專門用於NUMA結點移動實體記憶體頁,將實體記憶體頁內容移動到使用這個頁最頻繁的CPU */ #endif MIGRATE_TYPES };
很簡單地看出,相應型別的頁要從相應的連結串列中獲取,而它們之間也是有一定的優先順序順序的:
static int fallbacks[MIGRATE_TYPES][4] = { [MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, MIGRATE_RESERVE }, [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE, MIGRATE_RESERVE }, #ifdef CONFIG_CMA [MIGRATE_MOVABLE] = { MIGRATE_CMA, MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE }, [MIGRATE_CMA] = { MIGRATE_RESERVE }, /* Never used */ #else [MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE }, #endif [MIGRATE_RESERVE] = { MIGRATE_RESERVE }, /* Never used */ #ifdef CONFIG_MEMORY_ISOLATION [MIGRATE_ISOLATE] = { MIGRATE_RESERVE }, /* Never used */ #endif };
以MIGRATE_RECLAIMABLE為例,如果我需要申請這種頁框,當然會優先從這類頁框的連結串列中獲取,如果沒有,我會依次嘗試從MIGRATE_UNMOVABLE -> MIGRATE_MOVABLE -> MIGRATE_RESERVE鏈進行分配。
之前的文章也有提到頁描述符,它用於描述一個物理頁框,所有的頁描述符儲存在mem_map陣列中,通過頁框號可以直接獲取它對應的頁描述符(以頁框號做mem_map陣列下標獲取的就是對應的頁描述符),通過頁描述符我們可以確定物理頁框所在位置,所以在夥伴系統中,儲存在連結串列中的結點,就是這組連續頁框的首個頁框的頁描述符。
初始化夥伴系統
在初始化夥伴系統之前,所有的node和zone的描述符都已經初始化完畢,同時實體記憶體中所有的頁描述符頁相應的初始化為了MIGRATE_MOVABLE型別的頁。
初始化過程中首先將所有管理區的夥伴系統連結串列置空,這些工作處於start_kernel() -> setup_arch() -> x86_init.paging.pagetable_init()中進行:
//以下程式碼處於free_area_init_core()函式中
/ 將管理區ZONE的夥伴系統置空 / static void __meminit zone_init_free_lists(struct zone *zone) { unsigned int order, t;
</span><span style="color:rgb(0,0,255); line-height:1.5!important">for</span> (order = <span style="color:rgb(128,0,128); line-height:1.5!important">0</span>; order < MAX_ORDER; order++<span style="line-height:1.5!important">)
</span><span style="color:rgb(0,0,255); line-height:1.5!important">for</span> (type = <span style="color:rgb(128,0,128); line-height:1.5!important">0</span>; type < MIGRATE_TYPES; type++<span style="line-height:1.5!important">) {
INIT_LIST_HEAD(</span>&zone-><span style="line-height:1.5!important">free_area[order].free_list[t]);
zone</span>->free_area[order].nr_free = <span style="color:rgb(128,0,128); line-height:1.5!important">0</span><span style="line-height:1.5!important">;
}
}
void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone, unsigned long start_pfn, enum memmap_context context) { …
</span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 該區所有頁都設定為MIGRATE_MOVABLE </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span>
<span style="color:rgb(0,0,255); line-height:1.5!important">if</span> ((z->zone_start_pfn <= pfn) && (pfn < zone_end_pfn(z)) && !(pfn & (pageblock_nr_pages - <span style="color:rgb(128,0,128); line-height:1.5!important">1</span><span style="line-height:1.5!important">)))
set_pageblock_migratetype(page, MIGRATE_MOVABLE);
........
}
初始化高階記憶體區
在夥伴系統的初始化過程中,X86下高階記憶體和低端記憶體的夥伴系統初始化是分開來進行的,順序是從高階記憶體區到低端記憶體區,在初始化夥伴系統之前,系統中的node、zone、page都已經初始化完成了,基本上夥伴系統的初始化就是將所有不用的頁框釋放回夥伴系統中。我們先看看高階記憶體區的夥伴系統初始化,主要在set_highmem_pages_init(void)函式中:
/* 所有高階記憶體管理區初始化,將所有node的所有zone的managed_pages置為0,並將他們的頁框回收到頁框分配器中 */ void __init set_highmem_pages_init(void) { struct zone *zone; int nid;</span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 將所有node的所有zone的managed_pages置為0,即將所有管理區的所管理頁數量設定為0 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> reset_all_zones_managed_pages(); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 遍歷所有管理區,這裡只初始化高階記憶體區 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> for_each_zone(zone) { unsigned </span><span style="color:rgb(0,0,255); line-height:1.5!important">long</span><span style="line-height:1.5!important"> zone_start_pfn, zone_end_pfn; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 如果不是高階記憶體區,則下一個 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span> <span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 判斷方法: 當前zone描述符地址 - 所屬node的zone描述符陣列基地址 == 高階記憶體區偏移量 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span> <span style="color:rgb(0,0,255); line-height:1.5!important">if</span> (!<span style="line-height:1.5!important">is_highmem(zone)) </span><span style="color:rgb(0,0,255); line-height:1.5!important">continue</span><span style="line-height:1.5!important">; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 該管理區開始頁框號 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> zone_start_pfn </span>= zone-><span style="line-height:1.5!important">zone_start_pfn; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 該管理區結束頁框號 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> zone_end_pfn </span>= zone_start_pfn + zone-><span style="line-height:1.5!important">spanned_pages; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 該管理區所屬的node結點號 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> nid </span>=<span style="line-height:1.5!important"> zone_to_nid(zone); printk(KERN_INFO </span><span style="color:rgb(128,0,0); line-height:1.5!important">"</span><span style="color:rgb(128,0,0); line-height:1.5!important">Initializing %s for node %d (%08lx:%08lx)\n</span><span style="color:rgb(128,0,0); line-height:1.5!important">"</span><span style="line-height:1.5!important">, zone</span>-><span style="line-height:1.5!important">name, nid, zone_start_pfn, zone_end_pfn); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 將start_pfn到end_pfn中所有頁框回收,並放入頁框分配器 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> add_highpages_with_active_regions(nid, zone_start_pfn, zone_end_pfn); }
}
我們具體看add_highpages_with_active_regions函式:
/* 將start_pfn到end_pfn中所有頁框回收,並放入頁框分配器 */ void __init add_highpages_with_active_regions(int nid, unsigned long start_pfn, unsigned long end_pfn) { phys_addr_t start, end; u64 i;</span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 遍歷所有memblock.memory,此結構有e820傳遞的資料初始化成,每個node會是其中一個這種結構 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> for_each_free_mem_range(i, nid, </span>&start, &<span style="line-height:1.5!important">end, NULL) { </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 修正後第一個頁框號,因為有可能頁框號不在node上 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> unsigned </span><span style="color:rgb(0,0,255); line-height:1.5!important">long</span> pfn = clamp_t(unsigned <span style="color:rgb(0,0,255); line-height:1.5!important">long</span><span style="line-height:1.5!important">, PFN_UP(start), start_pfn, end_pfn); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 修正後最後一個頁框號 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> unsigned </span><span style="color:rgb(0,0,255); line-height:1.5!important">long</span> e_pfn = clamp_t(unsigned <span style="color:rgb(0,0,255); line-height:1.5!important">long</span><span style="line-height:1.5!important">, PFN_DOWN(end), start_pfn, end_pfn); </span><span style="color:rgb(0,0,255); line-height:1.5!important">for</span> ( ; pfn < e_pfn; pfn++<span style="line-height:1.5!important">) </span><span style="color:rgb(0,0,255); line-height:1.5!important">if</span><span style="line-height:1.5!important"> (pfn_valid(pfn)) </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 這裡會將頁框回收到頁框分配器中 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> free_highmem_page(pfn_to_page(pfn)); }
}
繼續
void free_highmem_page(struct page *page) {__free_reserved_page(page); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 系統中總頁數量++ </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> totalram_pages</span>++<span style="line-height:1.5!important">; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 頁所屬的管理區的managed_pages++ </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> page_zone(page)</span>->managed_pages++<span style="line-height:1.5!important">; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 高階記憶體頁數量++ </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> totalhigh_pages</span>++<span style="line-height:1.5!important">;
}
static inline void __free_reserved_page(struct page page) { ClearPageReserved(page); / page->_count = 1 / init_page_count(page); / 釋放到夥伴系統中,之後給出詳細程式碼 */ __free_page(page); }
到這裡整個高階記憶體區的夥伴系統就初始化完成了,關於__free_page()函式我們之後會說明,這個函式是夥伴系統的是否單個頁框的API。
初始化低端記憶體區(ZONE_DMA、ZONE_NORMAL)
在系統初始化階段會先啟用一個bootmem分配器,此分配器是專門用於啟動階段的,一個bootmem分配器管理著一個node結點的所有記憶體,也就是在numa架構中多個node有多個bootmem,他們被鏈入bdata_list連結串列中儲存。而夥伴系統的初始化就是將bootmem管理的所有物理頁框釋放到夥伴系統中去。
我們先看看bootmem的struct bootmem_data結構:
/* bootmem分配器結點(管理著一整塊連續記憶體,可以管理一個node中所有的實體記憶體),啟動時使用 */ typedef struct bootmem_data { /* 此塊記憶體開始頁框號 */ unsigned long node_min_pfn; /* 此塊記憶體結束頁框號,如果是32位系統下此儲存的是 ZONE_NORMAL最後一個頁框號 */ unsigned long node_low_pfn; /* 指向點陣圖記憶體區,node中所有ZONE_HIGHMEM之前的頁框都在這裡面有一個位,每次需要分配記憶體時就會掃描找出一個空閒頁框,空洞的記憶體也會佔用位,不過空洞的記憶體應該設定為已分配 */ void *node_bootmem_map; /* 上次分配距離末尾的偏移量 */ unsigned long last_end_off; unsigned long hint_idx; /* 鏈入bdata_list結構連結串列 */ struct list_head list; } bootmem_data_t;
bootmem分配器核心就是node_bootmem_map這個點陣圖,每一位代表這個node的一個頁,當需要分配時就會掃描這個點陣圖,然後獲取一段物理頁框進行分配,一般都會從開始處向後進行分配,並沒有什麼特殊的演算法在其中。而夥伴系統初始化時頁會根據這個點陣圖,將點陣圖中空閒的頁釋放回到夥伴系統中,而已經分配出去的頁則不會在初始化階段釋放回夥伴系統,不過有可能會在系統執行過程中釋放回夥伴系統中,我們具體看看是如何實現初始化的:
/* 釋放所有啟動後不需要的記憶體到頁框分配器 */ unsigned long __init free_all_bootmem(void) { unsigned long total_pages = 0; /* 系統會為每個node分配一個這種結構,這個管理著node中所有頁框,可以叫做bootmem分配器 */ bootmem_data_t *bdata;</span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 設定所有node的所有zone的managed_pages = 0,該函式在啟動時只會呼叫一次,如果初始化高階記憶體的夥伴系統時呼叫過,這裡就不會再次呼叫了 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> reset_all_zones_managed_pages(); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 遍歷所有需要釋放的啟動記憶體資料塊 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> list_for_each_entry(bdata, </span>&<span style="line-height:1.5!important">bdata_list, list) </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 釋放bdata啟動記憶體塊中所有頁框到頁框分配器 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> total_pages </span>+=<span style="line-height:1.5!important"> free_all_bootmem_core(bdata); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 所有記憶體頁數量 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> totalram_pages </span>+=<span style="line-height:1.5!important"> total_pages; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 返回總共釋放的頁數量 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span> <span style="color:rgb(0,0,255); line-height:1.5!important">return</span><span style="line-height:1.5!important"> total_pages;
}
繼續,主要看free_all_bootmem_core()函式:
/* 釋放bdata啟動記憶體塊中所有頁框到頁框分配器 */ static unsigned long __init free_all_bootmem_core(bootmem_data_t *bdata) { struct page *page; unsigned long *map, start, end, pages, count = 0;</span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 此bootmem沒有點陣圖,也就是沒有管理記憶體 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span> <span style="color:rgb(0,0,255); line-height:1.5!important">if</span> (!bdata-><span style="line-height:1.5!important">node_bootmem_map) </span><span style="color:rgb(0,0,255); line-height:1.5!important">return</span> <span style="color:rgb(128,0,128); line-height:1.5!important">0</span><span style="line-height:1.5!important">; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 此bootmem的點陣圖 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> map </span>= bdata-><span style="line-height:1.5!important">node_bootmem_map; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 此bootmem包含的開始頁框 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> start </span>= bdata-><span style="line-height:1.5!important">node_min_pfn; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 此bootmem包含的結束頁框 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> end </span>= bdata-><span style="line-height:1.5!important">node_low_pfn; bdebug(</span><span style="color:rgb(128,0,0); line-height:1.5!important">"</span><span style="color:rgb(128,0,0); line-height:1.5!important">nid=%td start=%lx end=%lx\n</span><span style="color:rgb(128,0,0); line-height:1.5!important">"</span><span style="line-height:1.5!important">, bdata </span>-<span style="line-height:1.5!important"> bootmem_node_data, start, end); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 釋放 bdata->node_min_pfn 到 bdata->node_low_pfn 之間空閒的頁框到夥伴系統 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span> <span style="color:rgb(0,0,255); line-height:1.5!important">while</span> (start <<span style="line-height:1.5!important"> end) { unsigned </span><span style="color:rgb(0,0,255); line-height:1.5!important">long</span><span style="line-height:1.5!important"> idx, vec; unsigned shift; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 一次迴圈檢查long所佔位數長度的頁框數量(32或64) </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> idx </span>= start - bdata-><span style="line-height:1.5!important">node_min_pfn; shift </span>= idx & (BITS_PER_LONG - <span style="color:rgb(128,0,128); line-height:1.5!important">1</span><span style="line-height:1.5!important">); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 做個整理,因為有可能start並不是按long位數對其的,有可能出現在了vec的中間位數 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> vec </span>= ~map[idx /<span style="line-height:1.5!important"> BITS_PER_LONG]; </span><span style="color:rgb(0,0,255); line-height:1.5!important">if</span><span style="line-height:1.5!important"> (shift) { vec </span>>>=<span style="line-height:1.5!important"> shift; </span><span style="color:rgb(0,0,255); line-height:1.5!important">if</span> (end - start >=<span style="line-height:1.5!important"> BITS_PER_LONG) vec </span>|= ~map[idx / BITS_PER_LONG + <span style="color:rgb(128,0,128); line-height:1.5!important">1</span>] <<<span style="line-height:1.5!important"> (BITS_PER_LONG </span>-<span style="line-height:1.5!important"> shift); } </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 如果檢查的這一塊記憶體塊全是空的,則一次性釋放 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span> <span style="color:rgb(0,0,255); line-height:1.5!important">if</span> (IS_ALIGNED(start, BITS_PER_LONG) && vec == ~<span style="color:rgb(128,0,128); line-height:1.5!important">0UL</span><span style="line-height:1.5!important">) { </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 這一塊長度的記憶體塊都為空閒的,計算這塊記憶體的order,如果這塊記憶體塊長度是8個頁框,那order就是3(2的3次方) </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span> <span style="color:rgb(0,0,255); line-height:1.5!important">int</span> order =<span style="line-height:1.5!important"> ilog2(BITS_PER_LONG); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 從start開始,釋放2的order次方的頁框到夥伴系統 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> __free_pages_bootmem(pfn_to_page(start), order); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> count用來記錄總共釋放的頁框 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> count </span>+=<span style="line-height:1.5!important"> BITS_PER_LONG; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 開始位置向後移動 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> start </span>+=<span style="line-height:1.5!important"> BITS_PER_LONG; } </span><span style="color:rgb(0,0,255); line-height:1.5!important">else</span><span style="line-height:1.5!important"> { </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 記憶體塊中有部分是頁框是空的,一頁一頁釋放 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> unsigned </span><span style="color:rgb(0,0,255); line-height:1.5!important">long</span> cur =<span style="line-height:1.5!important"> start; start </span>= ALIGN(start + <span style="color:rgb(128,0,128); line-height:1.5!important">1</span><span style="line-height:1.5!important">, BITS_PER_LONG); </span><span style="color:rgb(0,0,255); line-height:1.5!important">while</span> (vec && cur !=<span style="line-height:1.5!important"> start) { </span><span style="color:rgb(0,0,255); line-height:1.5!important">if</span> (vec & <span style="color:rgb(128,0,128); line-height:1.5!important">1</span><span style="line-height:1.5!important">) { </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 獲取頁框描述符,頁框號實際上就是頁描述符在mem_map的偏移量 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> page </span>=<span style="line-height:1.5!important"> pfn_to_page(cur); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 將此頁釋放到夥伴系統 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> __free_pages_bootmem(page, </span><span style="color:rgb(128,0,128); line-height:1.5!important">0</span><span style="line-height:1.5!important">); count</span>++<span style="line-height:1.5!important">; } vec </span>>>= <span style="color:rgb(128,0,128); line-height:1.5!important">1</span><span style="line-height:1.5!important">; </span>++<span style="line-height:1.5!important">cur; } } }</span></pre>
在__free_pages_bootmem()中也是呼叫了__free_pages()將頁釋放到夥伴系統中:
void __init __free_pages_bootmem(struct page *page, unsigned int order) { /* 需要釋放的頁數量 */ unsigned int nr_pages = 1 << order; struct page *p = page; unsigned int loop;</span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 預取指令,該指令用於把將要使用到的資料從記憶體提前裝入快取中,以減少訪問主存的指令執行時的延遲 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> prefetchw(p); </span><span style="color:rgb(0,0,255); line-height:1.5!important">for</span> (loop = <span style="color:rgb(128,0,128); line-height:1.5!important">0</span>; loop < (nr_pages - <span style="color:rgb(128,0,128); line-height:1.5!important">1</span>); loop++, p++<span style="line-height:1.5!important">) { </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 預取下一個頁描述符 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> prefetchw(p </span>+ <span style="color:rgb(128,0,128); line-height:1.5!important">1</span><span style="line-height:1.5!important">); __ClearPageReserved(p); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 設定page->_count = 0 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> set_page_count(p, </span><span style="color:rgb(128,0,128); line-height:1.5!important">0</span><span style="line-height:1.5!important">); } __ClearPageReserved(p); set_page_count(p, </span><span style="color:rgb(128,0,128); line-height:1.5!important">0</span><span style="line-height:1.5!important">); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 管理區的managed_pages加上這些頁數量 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> page_zone(page)</span>->managed_pages +=<span style="line-height:1.5!important"> nr_pages; </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 將首頁框的_count設定為1,代表被使用,因為被使用的頁框才能夠釋放 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> set_page_refcounted(page); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 釋放到管理區的夥伴系統 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> __free_pages(page, order);
}
到這裡,高階記憶體和低端記憶體的初始化就已經完成了。所以未使用的頁框都已經放入夥伴系統中供夥伴系統進行管理。之後我們會詳細說明申請頁框和釋放頁框的相關的操作。
申請頁框
對於申請單個頁框而言,系統會從每CPU快取記憶體維護的單頁框連結串列中進行分配,而對於申請多個頁框,系統則從夥伴系統中進行分配,可以說每CPU快取記憶體算是夥伴系統中的一小部分,專門用於分配單個頁框,因為系統希望儘量讓那些剛釋放掉的單頁框分配出去,這樣可以有效地提高快取命中率,因為釋放掉的頁框可能還處於快取中,而剛分配的頁框一般都會馬上使用,系統就不用對這些頁框換進換出快取了。因為每個CPU都有自己的快取記憶體,所以這個結構就叫每CPU快取記憶體。
夥伴系統提供了多個介面供其他模組申請頁框使用,如下:
- struct page * alloc_pages (gfp_mask, order):向夥伴系統請求連續的2的order次方個頁框,返回第一個頁描述符。
- struct page * alloc_page (gfp_mask):相當於struct page * alloc_pages(gfp_mask, 0)。
- void * __get_free_pages (gfp_mask, order):該函式類似於alloc_pages(),但返回第一個所分配頁的線性地址。
- void * __get_free_page (gfp_mask):相當於void * __get_free_pages (gfp_mask, 0)。
對於gfp_mask掩碼,有如下這些:
/* 允許核心對等待空閒頁框的當前程序進行阻塞 */ #define __GFP_WAIT ((__force gfp_t)___GFP_WAIT) /* 允許核心訪問保留的頁框池 */ #define __GFP_HIGH ((__force gfp_t)___GFP_HIGH) /* 允許核心在低端記憶體頁上執行IO傳輸 */ #define __GFP_IO ((__force gfp_t)___GFP_IO) /* 如果清0,則不允許核心執行依賴於檔案系統的操作 */ #define __GFP_FS ((__force gfp_t)___GFP_FS) /* 所請求的頁框可能是"冷"的 */ #define __GFP_COLD ((__force gfp_t)___GFP_COLD) /* 一次記憶體分配失敗將不會產生警告 */ #define __GFP_NOWARN ((__force gfp_t)___GFP_NOWARN)
#define __GFP_REPEAT ((__force gfp_t)___GFP_REPEAT) / 同上 / #define __GFP_NOFAIL ((__force gfp_t)___GFP_NOFAIL) / 一次分配失敗後不再重試 / #define __GFP_NORETRY ((__force gfp_t)___GFP_NORETRY) #define __GFP_MEMALLOC ((__force gfp_t)___GFP_MEMALLOC) / 屬於擴充套件頁的頁框 / #define __GFP_COMP ((__force gfp_t)___GFP_COMP) / 任何返回的頁框必須被填滿0 / #define __GFP_ZERO ((__force gfp_t)___GFP_ZERO)
而這些型別進行一些組合,會產生如下的一些掩碼:
/* 原子分配,分配期間不會進行阻塞 */ #define GFP_ATOMIC (__GFP_HIGH) #define GFP_NOIO (__GFP_WAIT) #define GFP_NOFS (__GFP_WAIT | __GFP_IO) #define GFP_KERNEL (__GFP_WAIT | __GFP_IO | __GFP_FS) #define GFP_TEMPORARY (__GFP_WAIT | __GFP_IO | __GFP_FS | \ __GFP_RECLAIMABLE) #define GFP_USER (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL) #define GFP_HIGHUSER (__GFP_WAIT | __GFP_IO | __GFP_FS | __GFP_HARDWALL | \ __GFP_HIGHMEM) #define GFP_HIGHUSER_MOVABLE (__GFP_WAIT | __GFP_IO | __GFP_FS | \ __GFP_HARDWALL | __GFP_HIGHMEM | \ __GFP_MOVABLE) #define GFP_IOFS (__GFP_IO | __GFP_FS) #define GFP_TRANSHUGE (GFP_HIGHUSER_MOVABLE | __GFP_COMP | \ __GFP_NOMEMALLOC | __GFP_NORETRY | __GFP_NOWARN | \ __GFP_NO_KSWAPD)
好了,我們主要看申請頁框的介面,這些介面最後都會呼叫到__alloc_pages_nodemask()函式,這裡我只用alloc_pages()函式進行講解,我們先看從alloc_pages()如何到__alloc_pages_nodemask()函式的:
/* 分配頁框 * gfp_mask: 標誌 * order: 需求2的次方個數頁框 */ #define alloc_pages(gfp_mask, order) \ alloc_pages_node(numa_node_id(), gfp_mask, order)
static inline struct page alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order) { / Unknown node is current node */ if (nid < 0) nid = numa_node_id();
</span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 根據node號獲取此node相應的zonelist,因為如果此node上沒法分配出多餘的記憶體,會從zonelist的其他node的zone中分配 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span>
<span style="color:rgb(0,0,255); line-height:1.5!important">return</span><span style="line-height:1.5!important"> __alloc_pages(gfp_mask, order, node_zonelist(nid, gfp_mask));
}
static inline struct page __alloc_pages(gfp_t gfp_mask, unsigned int order, struct zonelist zonelist) { / 最後呼叫到的函式 / return __alloc_pages_nodemask(gfp_mask, order, zonelist, NULL); }
在node中會有多個zonelist,這個zonelist的作用是將所有的zone按相對於此node的優先順序進行排序,連結串列頭4個就是此node的ZONE_MOVABLE、ZONE_HIGHMEM、ZONE_NORMAL、ZONE_DMA,之後是排在此node之後的node的這4個管理區。然後再是排在此node之前的node的這4個管理區。在分配時就按照這個順序,直到分配出相應數量的頁框為止。
在同一個node中的管理區中分配也有一定的順序,當我需要從高階記憶體區申請記憶體時,系統會按照 ZONE_HIGHMEM -> ZONE_NORMAL -> ZONE_DMA 順序為我嘗試分配,而當我需要從ZONE_NORMAL區申請記憶體時,系統會按照ZONE_NORMAL -> ZONE_DMA 順序為我嘗試分配,當我需要從ZONE_DMA區申請記憶體時,系統只會從ZONE_DMA區為我分配。在NUMA架構裡也是一樣,只會在其他node上的相應的管理區中分配記憶體。
接下來我們要著重說明__alloc_pages_nodemask()函式。
__alloc_pages_nodemask()
此函式可以說是頁框分配的心臟,裡面組織了整個分配過程中遇到各種問題時的處理情況。
夥伴系統的頁框分配方式主要有兩種:快速分配和慢速分配。
- 快速分配:會根據zonelist連結串列的優先順序順序,以zone的low閥值從相應zone的夥伴系統中分配連續頁框。
- 慢速分配:在快速分配失敗之後執行,頁會根據zonelist連結串列的優先順序順序,以zone的min閥值從相遇zone的夥伴系統中分配連續頁框,如果失敗,會喚醒kswapd核心執行緒,進行頁框的回收、頁框的非同步壓縮和輕同步壓縮,以及oom等操作來使系統獲得更多的空閒頁框,並且在這些操作的過程中會呼叫快速分配嘗試獲取記憶體。
夥伴系統分配可用頁框給申請者時,首先會根據zonelist對每個可用的zone進行快速分配,成功則返回第一個頁框的頁描述符,如果所有zone的快速分配都不成功,則會zonelist中的zone進行慢速分配,慢速分配中會進行記憶體回收、記憶體壓縮和喚醒kswapd執行緒也同時進行記憶體的回收工作,之後再嘗試繼續分配。我們先看看__alloc_pages_nodemask()原始碼:
/* gfp_mask: 上層要求分配記憶體時使用的標誌 * order: 需要的連續頁框的order值,如果是1個頁框,則為0 * zonelist: 合適的zone列表 * nodemask: node結點掩碼,用於判斷允許從哪些node上分配 */ struct page * __alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order, struct zonelist *zonelist, nodemask_t *nodemask) { enum zone_type high_zoneidx = gfp_zone(gfp_mask); /* preferred_zone指向第一個合適的管理區 */ struct zone *preferred_zone; struct zoneref *preferred_zoneref; struct page *page = NULL; /* 從gfp_mask中獲取選定的頁框型別,當中只會檢查__GFP_MOVABLE和__GFP_RECLAIMABLE */ int migratetype = gfpflags_to_migratetype(gfp_mask); unsigned int cpuset_mems_cookie; /* 這個需要注意一下,之後分配是會根據這個flags進行一定的操作,預設是使用zone的低閥值判斷是否需要進行記憶體回收 */ int alloc_flags = ALLOC_WMARK_LOW|ALLOC_CPUSET|ALLOC_FAIR; int classzone_idx;gfp_mask </span>&=<span style="line-height:1.5!important"> gfp_allowed_mask; lockdep_trace_alloc(gfp_mask); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 如果設定了__GFP_WAIT,就檢查當前程序是否需要排程,如果要則會進行排程 * 大多數情況的分配都會有__GFP_WAIT標誌 </span><span style="color:rgb(0,128,0); line-height:1.5!important">*/</span><span style="line-height:1.5!important"> might_sleep_if(gfp_mask </span>&<span style="line-height:1.5!important"> __GFP_WAIT); </span><span style="color:rgb(0,128,0); line-height:1.5!important">/*</span><span style="color:rgb(0,128,0); line-height:1.5!important"> 檢查gfp_mask和order是否符合要求,就是跟fail_page_alloc裡面每一項對比檢查 </span><span style="color:rgb(0