linux kernel內存碎片防治技術


Linux kernel組織管理物理內存的方式是buddy system(伙伴系統),而物理內存碎片正式buddy system的弱點之一,為了預防以及解決碎片問題,kernel采取了一些實用技術,這里將對這些技術進行總結歸納。

1 低內存時整合碎片

從buddy申請內存頁,如果找不到合適的頁,則會進行兩步調整內存的工作,compact和reclaim。前者是為了整合碎片,以得到更大的連續內存;后者是回收不一定必須占用內存的緩沖內存。這里重點了解comact,整個流程大致如下:

__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

並不是所有申請不到內存的場景都會compact,首先要滿足order大於0,並且gfp_mask攜帶__GFP_FS和__GFP_IO;另外,需要zone的剩余內存情況滿足一定條件,kernel稱之為“碎片指數”(fragmentation index),這個值在0~1000之間,默認碎片指數大於500時才能進行compact,可以通過proc文件extfrag_threshold來調整這個默認值。fragmentation index通過fragmentation_index函數來計算:

/*
  * 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)

在整合內存碎片的過程中,碎片頁只會在本zone的內部移動,將位於zone低地址的頁盡量移到zone的末端。申請新的頁面位置通過compaction_alloc函數實現。

移動過程又分為同步和異步,內存申請失敗后第一次compact將會使用異步,后續reclaim之后將會使用同步。同步過程只移動當面未被使用的頁,異步過程將遍歷並等待所有MOVABLE的頁使用完成后進行移動。

2 按可移動性組織頁

按照可移動性將內存頁分為以下三個類型:

  • UNMOVABLE:在內存中位置固定,不能隨意移動。kernel分配的內存基本屬於這個類型;
  • RECLAIMABLE:不能移動,但可以刪除回收。例如文件映射內存;
  • MOVABLE:可以隨意移動,用戶空間的內存基本屬於這個類型。

申請內存時,根據可移動性,首先在指定類型的空閑頁中申請內存,每個zone的空閑內存組織方式如下:

struct zone {
......
struct free_area    free_area[MAX_ORDER];
......
}
struct free_area {
struct list_head free_list[MIGRATE_TYPES];
unsigned long nr_free;
};

當在指定類型的free_area申請不到內存時,可以從備用類型挪用,挪用之后的內存就會釋放到新指定的類型列表中,kernel把這個過程稱為“盜用”。

備用類型優先級列表如下定義:

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
};

值得注意的是並不是所有場景都適合按可移動性組織頁,當內存大小不足以分配到各種類型時,就不適合啟用可移動性。有個全局變量來表示是否啟用,在內存初始化時設置:

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,則所有內存都是不可移動的。其中有個參數決定了每個內存區域至少擁有的頁,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)

在系統初始化期間,所有頁都被標記為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);
......
}

其它可移動性類型的頁都是后來產生的,也就是前面說的“盜取”。在這種情況發生時,通常會“盜取”fallback中更高優先級、更大塊連續的頁,從而避免小碎片的產生。

/* 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];
......
}

可以通過/proc/pageteypeinfo查看當前系統各種類型的頁分布。

3 虛擬可移動內存域

在依據可移動性組織頁的技術之前,還有一個方法已經合入kernel,那就是虛擬內存域:ZONE_MOVABLE。基本思想很簡單:把內存分為兩部分,可移動的和不可移動的。

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參數kernelcore或者movablecore,kernelcore用來指定不可移動的內存數量,movablecore指定可移動的內存大小,如果兩個都指定,取不可移動內存數量較大的一個。如果都不指定,則不啟動。

與其它內存域不同的是ZONE_MOVABLE不關聯任何物理內存范圍,該域的內存取自高端內存域或者普通內存域。find_zone_movable_pfns_for_nodes用來計算每個node中ZONE_MOVABLE的內存數量,采用的內存區域通常是每個node的最高內存域,在函數find_usable_zone_for_movable中體現。

在對每個node分配ZONE_MOVABLE內存時,kernelcore會被平均分配到各個Node:

kernelcore_node = required_kernelcore / usable_nodes;

在kernel alloc page時,如果gfp_flag同時指定了__GFP_HIGHMEM和__GFP_MOVABLE,則會從ZONE_MOVABLE內存域申請內存。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM