Linux kernel組織管理物理內(nèi)存的方式是buddy system(伙伴系統(tǒng)),而物理內(nèi)存碎片正式buddy system的弱點(diǎn)之一,為了預(yù)防以及解決碎片問(wèn)題,kernel采取了一些實(shí)用技術(shù),這里將對(duì)這些技術(shù)進(jìn)行總結(jié)歸納。
1?低內(nèi)存時(shí)整合碎片
從buddy申請(qǐng)內(nèi)存頁(yè),如果找不到合適的頁(yè),則會(huì)進(jìn)行兩步調(diào)整內(nèi)存的工作,compact和reclaim。前者是為了整合碎片,以得到更大的連續(xù)內(nèi)存;后者是回收不一定必須占用內(nèi)存的緩沖內(nèi)存。這里重點(diǎn)了解comact,整個(gè)流程大致如下:
__alloc_pages_nodemask
??? -> __alloc_pages_slowpath
??????? -> __alloc_pages_direct_compact
??????????? -> try_to_compact_pages
??????????????? -> compact_zone_order
??????????????????? -> compact_zone
??????????????????????? -> isolate_migratepages
??????????????????????? -> migrate_pages
??????????????????????? -> release_freepages
并不是所有申請(qǐng)不到內(nèi)存的場(chǎng)景都會(huì)compact,首先要滿足order大于0,并且gfp_mask攜帶__GFP_FS和__GFP_IO;另外,需要zone的剩余內(nèi)存情況滿足一定條件,kernel稱(chēng)之為“碎片指數(shù)”(fragmentation index),這個(gè)值在0~1000之間,默認(rèn)碎片指數(shù)大于500時(shí)才能進(jìn)行compact,可以通過(guò)proc文件extfrag_threshold來(lái)調(diào)整這個(gè)默認(rèn)值。fragmentation index通過(guò)fragmentation_index函數(shù)來(lái)計(jì)算:
?
/*
* Index is between 0 and 1000
*
* 0 => allocation would fail due to lack of memory
* 1000 => allocation would fail due to fragmentation
*/
return 1000 - div_u64( (1000+(div_u64(info->free_pages * 1000ULL, requested))), info->free_blocks_total)
在整合內(nèi)存碎片的過(guò)程中,碎片頁(yè)只會(huì)在本zone的內(nèi)部移動(dòng),將位于zone低地址的頁(yè)盡量移到zone的末端。申請(qǐng)新的頁(yè)面位置通過(guò)compaction_alloc函數(shù)實(shí)現(xiàn)。
移動(dòng)過(guò)程又分為同步和異步,內(nèi)存申請(qǐng)失敗后第一次compact將會(huì)使用異步,后續(xù)reclaim之后將會(huì)使用同步。同步過(guò)程只移動(dòng)當(dāng)面未被使用的頁(yè),異步過(guò)程將遍歷并等待所有MOVABLE的頁(yè)使用完成后進(jìn)行移動(dòng)。
2?按可移動(dòng)性組織頁(yè)
按照可移動(dòng)性將內(nèi)存頁(yè)分為以下三個(gè)類(lèi)型:
UNMOVABLE:在內(nèi)存中位置固定,不能隨意移動(dòng)。kernel分配的內(nèi)存基本屬于這個(gè)類(lèi)型;
RECLAIMABLE:不能移動(dòng),但可以刪除回收。例如文件映射內(nèi)存;
MOVABLE:可以隨意移動(dòng),用戶(hù)空間的內(nèi)存基本屬于這個(gè)類(lèi)型。
申請(qǐng)內(nèi)存時(shí),根據(jù)可移動(dòng)性,首先在指定類(lèi)型的空閑頁(yè)中申請(qǐng)內(nèi)存,每個(gè)zone的空閑內(nèi)存組織方式如下:
struct zone {
......
struct free_area free_area[MAX_ORDER];
......
}
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};
當(dāng)在指定類(lèi)型的free_area申請(qǐng)不到內(nèi)存時(shí),可以從備用類(lèi)型挪用,挪用之后的內(nèi)存就會(huì)釋放到新指定的類(lèi)型列表中,kernel把這個(gè)過(guò)程稱(chēng)為“盜用”。
備用類(lèi)型優(yōu)先級(jí)列表如下定義:
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
};
值得注意的是并不是所有場(chǎng)景都適合按可移動(dòng)性組織頁(yè),當(dāng)內(nèi)存大小不足以分配到各種類(lèi)型時(shí),就不適合啟用可移動(dòng)性。有個(gè)全局變量來(lái)表示是否啟用,在內(nèi)存初始化時(shí)設(shè)置:
void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
{
......
if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES))
page_group_by_mobility_disabled = 1;
else
page_group_by_mobility_disabled = 0;
......
}
如果page_group_by_mobility_disabled,則所有內(nèi)存都是不可移動(dòng)的。
其中有個(gè)參數(shù)決定了每個(gè)內(nèi)存區(qū)域至少擁有的頁(yè),pageblock_nr_pages,它的定義如下:
#define pageblock_order HUGETLB_PAGE_ORDER
#else /* CONFIG_HUGETLB_PAGE */
/* If huge pages are not used, group by MAX_ORDER_NR_PAGES */
#define pageblock_order (MAX_ORDER-1)
#endif /* CONFIG_HUGETLB_PAGE */
#define pageblock_nr_pages (1UL << pageblock_order)
在系統(tǒng)初始化期間,所有頁(yè)都被標(biāo)記為MOVABLE:
void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
unsigned long start_pfn, enum memmap_context context)
{
......
if ((z->zone_start_pfn <= pfn)
&& (pfn < zone_end_pfn(z))
&& !(pfn & (pageblock_nr_pages - 1)))
set_pageblock_migratetype(page, MIGRATE_MOVABLE);
......
}
其它可移動(dòng)性類(lèi)型的頁(yè)都是后來(lái)產(chǎn)生的,也就是前面說(shuō)的“盜取”。在這種情況發(fā)生時(shí),通常會(huì)“盜取”fallback中更高優(yōu)先級(jí)、更大塊連續(xù)的頁(yè),從而避免小碎片的產(chǎn)生。
/* Remove an element from the buddy allocator from the fallback list */
static inline struct page *
__rmqueue_fallback(struct zone *zone, int order, int start_migratetype)
{
......
/* Find the largest possible block of pages in the other list */
for (current_order = MAX_ORDER-1; current_order >= order;
--current_order) {
for (i = 0;; i++) {
migratetype = fallbacks[start_migratetype][i];
......
}
可以通過(guò)/proc/pageteypeinfo查看當(dāng)前系統(tǒng)各種類(lèi)型的頁(yè)分布。
3?虛擬可移動(dòng)內(nèi)存域
在依據(jù)可移動(dòng)性組織頁(yè)的技術(shù)之前,還有一個(gè)方法已經(jīng)合入kernel,那就是虛擬內(nèi)存域:ZONE_MOVABLE?;舅枷牒芎?jiǎn)單:把內(nèi)存分為兩部分,可移動(dòng)的和不可移動(dòng)的。
enum zone_type {
#ifdef CONFIG_ZONE_DMA
ZONE_DMA,
#endif
#ifdef CONFIG_ZONE_DMA32
ZONE_DMA32,
#endif
ZONE_NORMAL,
#ifdef CONFIG_HIGHMEM
ZONE_HIGHMEM,
#endif
ZONE_MOVABLE,
__MAX_NR_ZONES
};
ZONE_MOVABLE的啟用需要指定kernel參數(shù)kernelcore或者movablecore,kernelcore用來(lái)指定不可移動(dòng)的內(nèi)存數(shù)量,movablecore指定可移動(dòng)的內(nèi)存大小,如果兩個(gè)都指定,取不可移動(dòng)內(nèi)存數(shù)量較大的一個(gè)。如果都不指定,則不啟動(dòng)。
與其它內(nèi)存域不同的是ZONE_MOVABLE不關(guān)聯(lián)任何物理內(nèi)存范圍,該域的內(nèi)存取自高端內(nèi)存域或者普通內(nèi)存域。
find_zone_movable_pfns_for_nodes用來(lái)計(jì)算每個(gè)node中ZONE_MOVABLE的內(nèi)存數(shù)量,采用的內(nèi)存區(qū)域通常是每個(gè)node的最高內(nèi)存域,在函數(shù)find_usable_zone_for_movable中體現(xiàn)。
在對(duì)每個(gè)node分配ZONE_MOVABLE內(nèi)存時(shí),kernelcore會(huì)被平均分配到各個(gè)Node:
kernelcore_node = required_kernelcore / usable_nodes;
在kernel alloc page時(shí),如果gfp_flag同時(shí)指定了__GFP_HIGHMEM和__GFP_MOVABLE,則會(huì)從ZONE_MOVABLE內(nèi)存域申請(qǐng)內(nèi)存。
?
評(píng)論