首頁 >系統教程 >Linux >Linux kernel記憶體碎片防治技術:深入理解記憶體管理

Linux kernel記憶體碎片防治技術:深入理解記憶體管理

WBOY
WBOY轉載
2024-02-12 09:54:15884瀏覽

你是否曾經遇到過在Linux系統中出現的各種記憶體問題?例如記憶體洩漏、記憶體碎片等等。這些問題都可以透過深入理解Linux kernel記憶體碎片防治技術來解決。

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函數來計算:

1. /*
2. \* Index is between 0 and 1000
3. *
4. \* 0 => allocation would fail due to lack of memory
5. \* 1000 => allocation would fail due to fragmentation
6. */
7. 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的空閒內存組織方式如下:

1. struct zone {
2. ......
3. struct free_area free_area[MAX_ORDER];
4. ......
5. }
6.  
7. struct free_area {
8. struct list_head free_list[MIGRATE_TYPES];
9. unsigned long nr_free;
10. };

當在指定類型的free_area申請不到記憶體時,可以從備用類型挪用,挪用之後的記憶體就會釋放到新指定的類型清單中,kernel把這個過程稱為「盜用」。
備用類型優先權清單如下定義:

1. static int fallbacks[MIGRATE_TYPES][4] = {
2. [MIGRATE_UNMOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_MOVABLE, MIGRATE_RESERVE },
3. [MIGRATE_RECLAIMABLE] = { MIGRATE_UNMOVABLE, MIGRATE_MOVABLE, MIGRATE_RESERVE },
4. \#ifdef CONFIG_CMA
5. [MIGRATE_MOVABLE] = { MIGRATE_CMA, MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },
6. [MIGRATE_CMA] = { MIGRATE_RESERVE }, /* Never used */
7. \#else
8. [MIGRATE_MOVABLE] = { MIGRATE_RECLAIMABLE, MIGRATE_UNMOVABLE, MIGRATE_RESERVE },
9. \#endif
10. [MIGRATE_RESERVE] = { MIGRATE_RESERVE }, /* Never used */
11. \#ifdef CONFIG_MEMORY_ISOLATION
12. [MIGRATE_ISOLATE] = { MIGRATE_RESERVE }, /* Never used */
13. \#endif
14. };

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

1. void __ref build_all_zonelists(pg_data_t *pgdat, struct zone *zone)
2. {
3. ......
4. if (vm_total_pages else
7. page_group_by_mobility_disabled = 0;
8. ......
9. }

如果page_group_by_mobility_disabled,則所有記憶體都是不可移動的。
其中有個參數決定了每個記憶體區域至少擁有的頁,pageblock_nr_pages,它的定義如下:

#define pageblock_order HUGETLB_PAGE_ORDER

1. \#else /* CONFIG_HUGETLB_PAGE */
2. /* If huge pages are not used, group by MAX_ORDER_NR_PAGES */
3. \#define pageblock_order (MAX_ORDER-1)
4. \#endif /* CONFIG_HUGETLB_PAGE */
5. \#define pageblock_nr_pages (1UL 

在系統初始化期間,所有頁面都被標記為MOVABLE:

1. void __meminit memmap_init_zone(unsigned long size, int nid, unsigned long zone,
2. unsigned long start_pfn, enum memmap_context context)
3. {
4. ......
5. if ((z->zone_start_pfn 

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

1. /* Remove an element from the buddy allocator from the fallback list */
2. static inline struct page *
3. __rmqueue_fallback(struct zone *zone, int order, int start_migratetype)
4. {
5. ......
6. /* Find the largest possible block of pages in the other list */
7. for (current_order = MAX_ORDER-1; current_order >= order;
8. --current_order) {
9. for (i = 0;; i++) {
10. migratetype = fallbacks[start_migratetype][i];
11. ......
12. }

可以透過/proc/pageteypeinfo查看目前系統各種類型的頁分佈。

3 虛擬可移動記憶體域

#在依據可移動性組織頁的技術之前,還有一個方法已經合入kernel,那就是虛擬記憶體域:ZONE_MOVABLE。基本想法很簡單:把記憶分成兩部分,可移動的和不可移動的。 ###
1. enum zone_type {
2. \#ifdef CONFIG_ZONE_DMA
3. ZONE_DMA,
4. \#endif
5. \#ifdef CONFIG_ZONE_DMA32
6. ZONE_DMA32,
7. \#endif
8. ZONE_NORMAL,
9. \#ifdef CONFIG_HIGHMEM
10. ZONE_HIGHMEM,
11. \#endif
12. ZONE_MOVABLE,
13. __MAX_NR_ZONES
14. };

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内存域申请内存。

总之,Linux kernel内存碎片防治技术是一个非常重要的概念,可以帮助你更好地理解Linux系统中的内存管理。如果你想了解更多关于这个概念的信息,可以查看本文提供的参考资料。

以上是Linux kernel記憶體碎片防治技術:深入理解記憶體管理的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:lxlinux.net。如有侵權,請聯絡admin@php.cn刪除