[TOC]
mm/page_alloc.c 伙伴系统内存分配器(Buddy System Memory Allocator) 内核物理内存管理的核心 历史与背景 这项技术是为了解决什么特定问题而诞生的? 这项技术是为了解决操作系统内核中最根本的问题之一:如何高效、可靠地管理整个系统的物理内存(RAM) 。内核自身及其服务的子系统(如进程管理、文件系统缓存、网络协议栈)都需要动态地申请和释放内存。mm/page_alloc.c
中实现的伙伴系统(Buddy System)旨在满足以下核心需求:
管理基本单位 :将物理内存划分为固定大小的“页”(通常是4KB),并以此为基本单位进行管理。
提供连续内存 :许多硬件设备(特别是进行直接内存访问DMA的设备)和某些内核数据结构要求得到的内存块在物理上是连续的。伙伴系统的设计目标之一就是能够分配不同大小的、物理连续的内存块。
对抗外部碎片 :在长时间运行的系统中,频繁地分配和释放不同大小的内存块会导致物理内存中产生许多不连续的小空闲块。这种现象称为“外部碎片”,它会导致即使总的空闲内存很多,也无法满足一个较大连续内存的申请。伙伴系统通过其独特的合并机制来持续地、主动地对抗外部碎片。
它的发展经历了哪些重要的里程碑或版本迭代? 伙伴系统算法本身是一个经典的计算机科学算法,但在Linux内核中,它经历了持续的演进和优化。
分区内存管理(Zoned Memory) :早期的硬件存在限制,例如,一些老的ISA设备只能对RAM的第一个16MB进行DMA操作。为了应对这种情况,内核将内存划分为不同的区(Zone),如ZONE_DMA
, ZONE_NORMAL
, ZONE_HIGHMEM
(用于32位系统上无法直接映射的“高端内存”)。伙伴分配器被扩展为按区管理内存。
NUMA支持(Non-Uniform Memory Access) :在多处理器的服务器架构中,CPU访问不同物理内存节点的延迟不同。伙伴分配器被改造成NUMA感知的,为每个NUMA节点都维护一套独立的内存区和伙伴系统数据结构,并遵循“本地分配优先”的原则,以获得最佳性能。
每CPU页缓存(Per-CPU Pagesets) :为了减少在多核系统上分配和释放单页内存时对全局锁的竞争,引入了每CPU页缓存。当CPU释放一个页面时,它会先被放入这个本地缓存中;申请时也优先从本地缓存获取。这极大地提高了单页分配的性能和可扩展性。
反碎片机制 :为了进一步解决在长期运行的系统上难以获得大块连续内存的问题,引入了页迁移(Page Migration)和CMA(Contiguous Memory Allocator)等机制。系统会将一些“可移动”的页面(如用户进程的匿名页)挪走,从而整理出连续的空闲空间。伙伴系统与这些机制紧密集成。
目前该技术的社区活跃度和主流应用情况如何? mm/page_alloc.c
是Linux内核最核心、最基础 的组件之一。它是整个内存管理子系统的基石,其性能和稳定性对整个系统至关重要。它是一个非常成熟、经过数十年优化和验证的代码库,被Linux支持的所有体系结构(从微小的嵌入式设备到巨型超级计算机)普遍使用。社区对它的任何修改都极为谨慎,并需要经过严格的审查和测试。
核心原理与设计 它的核心工作原理是什么? mm/page_alloc.c
实现的是一种二进制伙伴系统(Binary Buddy System) 。
基本单位与阶(Order) :内存被划分为页。分配的单位是 2 的幂次方个页,这个幂次被称为阶(order) 。一个 order-0 的块是 2^0=1个页,一个 order-1 的块是 2^1=2个页,一个 order-3 的块是 2^3=8个页,以此类推,最大可达 order-10(1024个页,即4MB)。
空闲链表数组 :伙伴系统为每个阶维护一个空闲内存块的链表。例如,free_area[3]
指向一个由所有空闲的、8页大小的内存块组成的链表。
分配过程 :
当请求一个 order-n
的块时,系统首先检查 order-n
的空闲链表。
如果链表不为空,就直接从链表中取下一个块,分配出去。
如果链表为空,系统会去检查 order-n+1
的链表。
如果 order-n+1
链表有块,就取出一个,将其**分裂(Split)**成两个大小相等的 order-n
的块。一个块用于满足请求,另一个块(它的“伙伴”)被放入 order-n
的空闲链表中。
如果 order-n+1
的链表也为空,就继续向上查找 order-n+2
,直到找到一个更大的空闲块,然后逐级分裂下来,直到得到一个 order-n
的块。
释放过程(核心) :
当一个 order-n
的块被释放时,系统会计算出它的伙伴 块的地址(伙伴地址可以通过简单的异或运算得到)。
然后系统检查这个伙伴块是否也处于空闲状态。
如果伙伴也空闲,系统就会将这两个 order-n
的块从 order-n
链表中移除,然后**合并(Merge)**成一个 order-n+1
的大块,并将其放入 order-n+1
的空闲链表中。
这个合并过程是递归 的。新合并成的 order-n+1
的块会继续尝试与它的伙伴合并,可能形成 order-n+2
的块,以此类推。这种积极的合并策略是伙伴系统对抗外部碎片的关键。
它的主要优势体现在哪些方面?
高效对抗外部碎片 :释放内存时积极的、快速的合并操作,使得小的空闲块能不断地重新组合成大的连续空闲块。
分配和释放速度快 :查找伙伴和进行分裂/合并的操作都非常快,主要是指针操作和位运算。
满足连续内存需求 :能够直接分配多种大小的、物理上连续的内存块。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
内部碎片 :这是伙伴系统最主要的缺点。由于分配大小必须是2的幂次方个页,所以当应用程序请求一个非2的幂次大小的内存时,会造成浪费。例如,请求9KB内存(3个页),伙伴系统必须分配一个 order-2 的块(4个页,16KB),其中有7KB被浪费掉了。这种在一个已分配块内部的浪费称为内部碎片 。
大块内存分配困难 :尽管伙伴系统努力对抗碎片,但在一个长期运行且内存使用复杂的系统上,要分配一个非常大(如 order-10)的连续内存块仍然可能失败。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。 伙伴系统是内核物理内存管理的唯一 解决方案,它是所有其他内存分配机制的基础。内核开发者通常不会直接调用伙伴系统的函数(如 alloc_pages()
),而是通过更高层的接口。
Slab分配器(SLUB/SLAB) :当内核需要为许多小的、相同大小的对象(如inode
结构体、task_struct
)分配内存时,Slab分配器会向伙伴系统申请一个或多个连续的页(一个Slab),然后在这些页内部进行更精细的切分。这是为了解决伙伴系统的内部碎片问题。
进程地址空间 :当一个用户进程通过 brk()
或 mmap()
扩展其堆或映射新内存时,内核需要为其分配物理页面来承载数据。这些物理页面最终都来源于伙伴系统。
DMA缓冲区 :需要大块物理连续内存进行数据传输的设备驱动程序,会通过DMA API(最终调用伙伴系统)来申请一个高阶的内存块。
内核栈和模块加载 :为新线程创建内核栈,或者加载内核模块(.ko
文件)时,所需的连续内存页也是由伙伴系统提供的。
是否有不推荐使用该技术的场景?为什么? 不应该直接使用伙伴系统(即 alloc_pages()
)的场景是:
分配小的、对象大小的内存 :对于几十或几百字节的内存需求,直接从伙伴系统分配一个完整的页(4KB)是极大的浪费。这种场景必须 使用 kmalloc()
(它背后是Slab分配器)或专门的内存池。
对比分析 请将其 与 其他相似技术 进行详细对比。 对比 Slab 分配器 (SLUB/SLAB)
特性
伙伴系统 (page_alloc.c
)
Slab 分配器 (mm/slub.c
)
管理粒度
页(Page) ,通常为4KB,以及2的幂次方个页。
对象(Object) ,大小可以从几个字节到几千字节。
主要目标
管理整个物理内存,对抗外部碎片 。
在伙伴系统之上,为小对象提供内存,对抗内部碎片 。
关系
基础提供者 。Slab分配器是伙伴系统的客户,它向伙伴系统申请”Slabs”(由页组成)。
上层消费者 。它将从伙伴系统获得的页进行精细切分,以满足小内存请求。
使用接口
alloc_pages()
, free_pages()
kmalloc()
, kfree()
, kmem_cache_alloc()
对比 vmalloc()
特性
伙伴系统 (alloc_pages()
/kmalloc()
)
vmalloc()
内存连续性
返回的内存是物理上连续的 。
返回的内存是虚拟地址上连续,但物理上不一定连续 。
实现方式
直接从物理内存空闲链表中分配。
分别从伙伴系统申请多个不连续的物理页面,然后在内核的虚拟地址空间中建立一段连续的页表映射,将这些物理页面映射到连续的虚拟地址上。
性能
高 。访问速度快,因为它通常位于内核直接映射区域,没有额外的页表转换开销。
较低 。每次访问都可能需要通过页表转换,可能导致TLB未命中,开销较大。
大小限制
受物理内存碎片化程度限制,通常难以分配非常大的块(如几十MB)。
可以分配非常大的内存区域(可达GB级别),只要总的空闲物理内存足够。
使用场景
DMA缓冲区,性能敏感的数据结构,所有常规的内核内存分配。
需要一个非常大的、逻辑上连续的内存缓冲区,但又不要求物理上连续的场景,例如为某些内核模块或数据结构分配临时的大缓冲区。应尽量避免使用。
pcp 每个 CPU 的页面集 在 Linux 内核中,PCP
是 Per-CPU Pageset 的缩写,表示每个 CPU 的页面集。它是内存管理子系统的一部分,用于优化页面分配和释放的性能。
PCP 的作用
减少锁争用 :
内存页面的分配和释放是频繁的操作。如果每次都需要访问全局的内存管理结构(如 zone
),会导致锁争用,降低性能。
PCP
通过为每个 CPU 提供一个本地的页面缓存,减少了对全局锁的依赖,从而提高了并发性能。
提升性能 :
PCP
缓存了一部分页面,允许页面分配和释放在 CPU 本地完成,避免频繁访问全局内存结构。
这种本地化的设计减少了跨 CPU 的内存访问延迟。
批量操作 :
PCP
支持批量分配和释放页面。例如,当 PCP
缓存中的页面不足时,会一次性从全局 zone
中获取一批页面;释放时也会将多余的页面批量返还给全局 zone
。
这种批量操作进一步减少了对全局结构的访问频率。
zone_pcp_init
的作用zone_pcp_init
函数用于初始化与 zone
相关的 PCP
数据结构。它在内存初始化阶段调用,为每个内存区域(zone
)设置默认的 PCP
配置。
关键字段
zone->per_cpu_pageset
:
指向 PCP
数据结构的指针,用于存储每个 CPU 的页面集。
在初始化阶段,指向 boot_pageset
,这是一个临时的 PCP
数据结构。
zone->pageset_high_min
和 zone->pageset_high_max
:
zone->pageset_batch
:
逻辑
如果 zone
是有效的(通过 populated_zone
检查),会打印调试信息,包括 zone
的名称、页面数量和批量大小。
PCP 的使用场景
页面分配 :
当某个 CPU 需要分配页面时,优先从本地的 PCP
缓存中获取页面。如果缓存中没有足够的页面,则从全局 zone
中获取。
页面释放 :
页面释放时,优先将页面放入本地的 PCP
缓存。如果缓存已满,则将多余的页面返还给全局 zone
。
NUMA 支持 :
在 NUMA 系统中,每个节点的 zone
都有自己的 PCP
,进一步优化了内存访问的本地性。
总结 PCP
是 Linux 内核内存管理中的一个重要机制,通过为每个 CPU 提供本地化的页面缓存,减少了全局锁争用和跨 CPU 的内存访问延迟,从而显著提升了页面分配和释放的性能。zone_pcp_init
函数负责初始化与 zone
相关的 PCP
数据结构,为系统的高效内存管理奠定了基础。
include/linux/gfp.h gfp_zone 确定目标内存区域(zone)所在 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #define __GFP_DMA ((__force gfp_t)___GFP_DMA) #define __GFP_HIGHMEM ((__force gfp_t)___GFP_HIGHMEM) #define __GFP_DMA32 ((__force gfp_t)___GFP_DMA32) #define __GFP_MOVABLE ((__force gfp_t)___GFP_MOVABLE) #define GFP_ZONEMASK (__GFP_DMA|__GFP_HIGHMEM|__GFP_DMA32|__GFP_MOVABLE) #define GFP_ZONE_TABLE ( \ (ZONE_NORMAL << 0 * GFP_ZONES_SHIFT) \ | (OPT_ZONE_DMA << ___GFP_DMA * GFP_ZONES_SHIFT) \ | (OPT_ZONE_HIGHMEM << ___GFP_HIGHMEM * GFP_ZONES_SHIFT) \ | (OPT_ZONE_DMA32 << ___GFP_DMA32 * GFP_ZONES_SHIFT) \ | (ZONE_NORMAL << ___GFP_MOVABLE * GFP_ZONES_SHIFT) \ | (OPT_ZONE_DMA << (___GFP_MOVABLE | ___GFP_DMA) * GFP_ZONES_SHIFT) \ | (ZONE_MOVABLE << (___GFP_MOVABLE | ___GFP_HIGHMEM) * GFP_ZONES_SHIFT)\ | (OPT_ZONE_DMA32 << (___GFP_MOVABLE | ___GFP_DMA32) * GFP_ZONES_SHIFT)\ ) static inline enum zone_type gfp_zone (gfp_t flags) { enum zone_type z ; int bit = (__force int ) (flags & GFP_ZONEMASK); z = (GFP_ZONE_TABLE >> (bit * GFP_ZONES_SHIFT)) & ((1 << GFP_ZONES_SHIFT) - 1 ); VM_BUG_ON((GFP_ZONE_BAD >> bit) & 1 ); return z; }
__get_free_pages 分配页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 struct page *__alloc_pages_noprof (gfp_t gfp , unsigned int order , int preferred_nid , nodemask_t *nodemask ) { struct page *page ; page = __alloc_frozen_pages_noprof(gfp, order, preferred_nid, nodemask); if (page) set_page_refcounted(page); return page; } EXPORT_SYMBOL(__alloc_pages_noprof); static inline struct page *__alloc_pages_node_noprof (int nid , gfp_t gfp_mask , unsigned int order ) { VM_BUG_ON(nid < 0 || nid >= MAX_NUMNODES); warn_if_node_offline(nid, gfp_mask); return __alloc_pages_noprof(gfp_mask, order, nid, NULL ); } static inline struct page *alloc_pages_node_noprof (int nid, gfp_t gfp_mask, unsigned int order) { if (nid == NUMA_NO_NODE) nid = numa_mem_id(); return __alloc_pages_node_noprof(nid, gfp_mask, order); } static inline struct page *alloc_pages_noprof (gfp_t gfp_mask, unsigned int order) { return alloc_pages_node_noprof(numa_node_id(), gfp_mask, order); } unsigned long get_free_pages_noprof (gfp_t gfp_mask, unsigned int order) { struct page *page ; page = alloc_pages_noprof(gfp_mask & ~__GFP_HIGHMEM, order); if (!page) return 0 ; return (unsigned long ) page_address(page); } EXPORT_SYMBOL(get_free_pages_noprof); #define __get_free_pages(...) alloc_hooks(get_free_pages_noprof(__VA_ARGS__))
include/linux/vmstat.h __mod_zone_page_state 修改区域页面状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static inline void zone_page_state_add (long x, struct zone *zone, enum zone_stat_item item) { atomic_long_add(x, &zone->vm_stat[item]); atomic_long_add(x, &vm_zone_stat[item]); } static inline void __mod_zone_page_state(struct zone *zone, enum zone_stat_item item, long delta) { zone_page_state_add(delta, zone, item); }
include/linux/page-flags.h pageflags 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 enum pageflags { PG_locked, PG_writeback, PG_referenced, PG_uptodate, PG_dirty, PG_lru, PG_head, PG_waiters, PG_active, PG_workingset, PG_owner_priv_1, PG_owner_2, PG_arch_1, PG_reserved, PG_private, PG_private_2, PG_reclaim, PG_swapbacked, PG_unevictable, PG_dropbehind, #ifdef CONFIG_MMU PG_mlocked, #endif #ifdef CONFIG_MEMORY_FAILURE PG_hwpoison, #endif #if defined(CONFIG_PAGE_IDLE_FLAG) && defined(CONFIG_64BIT) PG_young, PG_idle, #endif #ifdef CONFIG_ARCH_USES_PG_ARCH_2 PG_arch_2, #endif #ifdef CONFIG_ARCH_USES_PG_ARCH_3 PG_arch_3, #endif __NR_PAGEFLAGS, PG_readahead = PG_reclaim, PG_swapcache = PG_owner_priv_1, PG_checked = PG_owner_priv_1, PG_anon_exclusive = PG_owner_2, PG_mappedtodisk = PG_owner_2, PG_fscache = PG_private_2, PG_pinned = PG_owner_priv_1, PG_savepinned = PG_dirty, PG_foreign = PG_owner_priv_1, PG_xen_remapped = PG_owner_priv_1, PG_isolated = PG_reclaim, PG_reported = PG_uptodate, #ifdef CONFIG_MEMORY_HOTPLUG PG_vmemmap_self_hosted = PG_owner_priv_1, #endif PG_has_hwpoisoned = PG_active, PG_large_rmappable = PG_workingset, PG_partially_mapped = PG_reclaim, };
FOLIO_FLAG 操作 Folio(页簇)中的标志位
Folio 是内核中用于管理多个连续页面的抽象概念,旨在提高内存管理的效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static const unsigned long *const_folio_flags (const struct folio *folio, unsigned n) { const struct page *page = &folio->page; VM_BUG_ON_PGFLAGS(page->compound_head & 1 , page); VM_BUG_ON_PGFLAGS(n > 0 && !test_bit(PG_head, &page->flags), page); return &page[n].flags; } static unsigned long *folio_flags (struct folio *folio, unsigned n) { struct page *page = &folio->page; VM_BUG_ON_PGFLAGS(page->compound_head & 1 , page); VM_BUG_ON_PGFLAGS(n > 0 && !test_bit(PG_head, &page->flags), page); return &page[n].flags; } #define FOLIO_TEST_FLAG(name, page) \ static __always_inline bool folio_test_##name(const struct folio *folio) \ { return test_bit(PG_##name, const_folio_flags(folio, page)); } #define FOLIO_SET_FLAG(name, page) \ static __always_inline void folio_set_##name(struct folio *folio) \ { set_bit(PG_##name, folio_flags(folio, page)); } #define FOLIO_CLEAR_FLAG(name, page) \ static __always_inline void folio_clear_##name(struct folio *folio) \ { clear_bit(PG_##name, folio_flags(folio, page)); } #define FOLIO_FLAG(name, page) \ FOLIO_TEST_FLAG(name, page) \ FOLIO_SET_FLAG(name, page) \ FOLIO_CLEAR_FLAG(name, page)
Page Flags 页面标志(Page Flags)相关的策略 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 static inline const struct page *page_fixed_fake_head (const struct page *page) { return page; } static __always_inline int page_is_fake_head (const struct page *page) { return page_fixed_fake_head(page) != page; } static __always_inline unsigned long _compound_head(const struct page *page){ unsigned long head = READ_ONCE(page->compound_head); if (unlikely(head & 1 )) return head - 1 ; return (unsigned long )page_fixed_fake_head(page); } #define compound_head(page) ((typeof(page))_compound_head(page)) static __always_inline int PageTail (const struct page *page) { return READ_ONCE(page->compound_head) & 1 || page_is_fake_head(page); } static __always_inline int PageCompound (const struct page *page) { return test_bit(PG_head, &page->flags) || READ_ONCE(page->compound_head) & 1 ; } #define PAGE_POISON_PATTERN -1l static inline int PagePoisoned (const struct page *page) { return READ_ONCE(page->flags) == PAGE_POISON_PATTERN; } static __always_inline int PageHead (const struct page *page) { PF_POISONED_CHECK(page); return test_bit(PG_head, &page->flags) && !page_is_fake_head(page); } #define PF_POISONED_CHECK(page) ({ \ VM_BUG_ON_PGFLAGS(PagePoisoned(page), page); \ page; }) #define PF_ANY(page, enforce) PF_POISONED_CHECK(page) #define PF_HEAD(page, enforce) PF_POISONED_CHECK(compound_head(page)) #define PF_NO_TAIL(page, enforce) ({ \ VM_BUG_ON_PGFLAGS(enforce && PageTail(page), page); \ PF_POISONED_CHECK(compound_head(page)); }) #define PF_NO_COMPOUND(page, enforce) ({ \ VM_BUG_ON_PGFLAGS(enforce && PageCompound(page), page); \ PF_POISONED_CHECK(page); }) #define PF_SECOND(page, enforce) ({ \ VM_BUG_ON_PGFLAGS(!PageHead(page), page); \ PF_POISONED_CHECK(&page[1 ]); })
PAGEFLAG 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #define TESTPAGEFLAG(uname, lname, policy) \ FOLIO_TEST_FLAG(lname, FOLIO_##policy) \ static __always_inline int Page##uname(const struct page *page) \ { return test_bit(PG_##lname, &policy(page, 0)->flags); } #define SETPAGEFLAG(uname, lname, policy) \ FOLIO_SET_FLAG(lname, FOLIO_##policy) \ static __always_inline void SetPage##uname(struct page *page) \ { set_bit(PG_##lname, &policy(page, 1)->flags); } #define CLEARPAGEFLAG(uname, lname, policy) \ FOLIO_CLEAR_FLAG(lname, FOLIO_##policy) \ static __always_inline void ClearPage##uname(struct page *page) \ { clear_bit(PG_##lname, &policy(page, 1)->flags); } #define PAGEFLAG(uname, lname, policy) \ TESTPAGEFLAG(uname, lname, policy) \ SETPAGEFLAG(uname, lname, policy) \ CLEARPAGEFLAG(uname, lname, policy)
PAGEFLAG Reserved 宏定义 1 2 3 4 PAGEFLAG(Reserved, reserved, PF_NO_COMPOUND) __CLEARPAGEFLAG(Reserved, reserved, PF_NO_COMPOUND) __SETPAGEFLAG(Reserved, reserved, PF_NO_COMPOUND)
page_folio 将 struct page 转换为 struct folio
在 Linux 内核中,struct page 是用于描述单个物理内存页的核心数据结构。然而,随着内存管理需求的增加,内核引入了 struct folio,它表示一组连续的物理页,旨在减少对单个页的操作开销并提高内存管理的效率
1 2 3 4 5 6 7 8 9 10 11 12 #define page_folio(p) (_Generic((p), \ const struct page *: (const struct folio *)_compound_head(p), \ struct page *: (struct folio *)_compound_head(p)))
PAGE_TYPE_OPS page_type 操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #define FOLIO_TYPE_OPS(lname, fname) \ static __always_inline bool folio_test_##fname(const struct folio *folio) \ { \ return data_race(folio->page.page_type >> 24) == PGTY_##lname; \ } \ static __always_inline void __folio_set_##fname(struct folio *folio) \ { \ if (folio_test_##fname(folio)) \ return; \ VM_BUG_ON_FOLIO(data_race(folio->page.page_type) != UINT_MAX, \ folio); \ folio->page.page_type = (unsigned int)PGTY_##lname << 24; \ } \ static __always_inline void __folio_clear_##fname(struct folio *folio) \ { \ if (folio->page.page_type == UINT_MAX) \ return; \ VM_BUG_ON_FOLIO(!folio_test_##fname(folio), folio); \ folio->page.page_type = UINT_MAX; \ } #define PAGE_TYPE_OPS(uname, lname, fname) \ FOLIO_TYPE_OPS(lname, fname) \ static __always_inline int Page##uname(const struct page *page) \ { \ return data_race(page->page_type >> 24 ) == PGTY_##lname; \ } \ static __always_inline void __SetPage##uname(struct page *page) \ { \ if (Page##uname(page)) \ return; \ VM_BUG_ON_PAGE(data_race(page->page_type) != UINT_MAX, page); \ page->page_type = (unsigned int )PGTY_##lname << 24; \ } \ static __always_inline void __ClearPage##uname(struct page *page) \ { \ if (page->page_type == UINT_MAX) \ return; \ VM_BUG_ON_PAGE(!Page##uname(page), page); \ page->page_type = UINT_MAX; \ } PAGE_TYPE_OPS(Buddy, buddy, buddy)
mm/internal.h find_buddy_page_pfn 查找伙伴页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 static inline bool page_is_buddy (struct page *page, struct page *buddy, unsigned int order) { if (!page_is_guard(buddy) && !PageBuddy(buddy)) return false ; if (buddy_order(buddy) != order) return false ; if (page_zone_id(page) != page_zone_id(buddy)) return false ; VM_BUG_ON_PAGE(page_count(buddy) != 0 , buddy); return true ; } static inline unsigned long __find_buddy_pfn(unsigned long page_pfn, unsigned int order) { return page_pfn ^ (1 << order); } static inline struct page *find_buddy_page_pfn (struct page *page, unsigned long pfn, unsigned int order, unsigned long *buddy_pfn) { unsigned long __buddy_pfn = __find_buddy_pfn(pfn, order); struct page *buddy ; buddy = page + (__buddy_pfn - pfn); if (buddy_pfn) *buddy_pfn = __buddy_pfn; if (page_is_buddy(page, buddy, order)) return buddy; return NULL ; }
__alloc_frozen_pages 分配冻结页面 1 2 #define __alloc_frozen_pages(...) \ alloc_hooks(__alloc_frozen_pages_noprof(__VA_ARGS__))
include/linux/page_ref.h page_ref_add_unless 尝试增加页面(struct page)的引用计数,但仅在引用计数不等于指定的值 u 时执行操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static inline bool page_ref_add_unless (struct page *page, int nr, int u) { bool ret = false ; rcu_read_lock(); if (page_count_writable(page, u)) ret = atomic_add_unless(&page->_refcount, nr, u); rcu_read_unlock(); if (page_ref_tracepoint_active(page_ref_mod_unless)) __page_ref_mod_unless(page, nr, ret); return ret; }
folio_ref_add_unless 尝试增加 folio(页面的抽象表示)的引用计数 1 2 3 4 5 6 7 static inline bool folio_ref_add_unless (struct folio *folio, int nr, int u) { return page_ref_add_unless(&folio->page, nr, u); }
folio_try_get 尝试增加 folio(页面的抽象表示)的引用计数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 static inline bool folio_try_get (struct folio *folio) { return folio_ref_add_unless(folio, 1 , 0 ); }
page_ref_dec_and_test 减少页面(struct page)的引用计数,并检查是否已减少到零 1 2 3 4 5 6 7 8 static inline int page_ref_dec_and_test (struct page *page) { int ret = atomic_dec_and_test(&page->_refcount); if (page_ref_tracepoint_active(page_ref_mod_and_test)) __page_ref_mod_and_test(page, -1 , ret); return ret; }
include/linux/mm.h folio_put 减少 folio(页面的抽象表示)的引用计数
当引用计数减少到零时,页面的内存会被释放回页面分配器(page allocator),以供其他内存分配使用。该函数在 Linux 内核中用于管理页面的生命周期,确保内存资源的高效回收。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static inline int put_page_testzero (struct page *page) { VM_BUG_ON_PAGE(page_ref_count(page) == 0 , page); return page_ref_dec_and_test(page); } static inline int folio_put_testzero (struct folio *folio) { return put_page_testzero(&folio->page); } static inline void folio_put (struct folio *folio) { if (folio_put_testzero(folio)) __folio_put(folio); }
include/linux/pagemap.h wake_page_match 检查等待页面队列中的页面是否与给定的键匹配 1 2 3 4 5 6 7 8 9 10 11 12 static inline bool wake_page_match (struct wait_page_queue *wait_page, struct wait_page_key *key) { if (wait_page->folio != key->folio) return false ; key->page_match = 1 ; if (wait_page->bit_nr != key->bit_nr) return false ; return true ; }
mm/page_alloc.c zone_pcp_init 每个 CPU 的页面集区域初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 __meminit void zone_pcp_init (struct zone *zone) { zone->per_cpu_pageset = &boot_pageset; zone->per_cpu_zonestats = &boot_zonestats; zone->pageset_high_min = BOOT_PAGESET_HIGH; zone->pageset_high_max = BOOT_PAGESET_HIGH; zone->pageset_batch = BOOT_PAGESET_BATCH; if (populated_zone(zone)) pr_debug(" %s zone: %lu pages, LIFO batch:%u\n" , zone->name, zone->present_pages, zone_batchsize(zone)); }
set_pfnblock_flags_mask 为 pageblock_nr_pages 个页面块设置请求的标志组 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 void set_pfnblock_flags_mask (struct page *page, unsigned long flags, unsigned long pfn, unsigned long mask) { unsigned long *bitmap; unsigned long bitidx, word_bitidx; unsigned long word; BUILD_BUG_ON(NR_PAGEBLOCK_BITS != 4 ); BUILD_BUG_ON(MIGRATE_TYPES > (1 << PB_migratetype_bits)); bitmap = get_pageblock_bitmap(page, pfn); bitidx = pfn_to_bitidx(page, pfn); word_bitidx = bitidx / BITS_PER_LONG; bitidx &= (BITS_PER_LONG-1 ); VM_BUG_ON_PAGE(!zone_spans_pfn(page_zone(page), pfn), page); mask <<= bitidx; flags <<= bitidx; word = READ_ONCE(bitmap[word_bitidx]); do { } while (!try_cmpxchg(&bitmap[word_bitidx], &word, (word & ~mask) | flags)); }
set_pageblock_migratetype 设置页面块的迁移类型 1 2 3 4 5 6 7 8 9 void set_pageblock_migratetype (struct page *page, int migratetype) { if (unlikely(page_group_by_mobility_disabled && migratetype < MIGRATE_PCPTYPES)) migratetype = MIGRATE_UNMOVABLE; set_pfnblock_flags_mask(page, (unsigned long )migratetype, page_to_pfn(page), MIGRATETYPE_MASK); }
__build_all_zonelists 初始化 zonelists 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 static int build_zonerefs_node (pg_data_t *pgdat, struct zoneref *zonerefs) { struct zone *zone ; enum zone_type zone_type = MAX_NR_ZONES; int nr_zones = 0 ; do { zone_type--; zone = pgdat->node_zones + zone_type; if (populated_zone(zone)) { zoneref_set_zone(zone, &zonerefs[nr_zones++]); check_highest_zone(zone_type); } } while (zone_type); return nr_zones; } static void build_zonelists (pg_data_t *pgdat) { struct zoneref *zonerefs ; int nr_zones; zonerefs = pgdat->node_zonelists[ZONELIST_FALLBACK]._zonerefs; nr_zones = build_zonerefs_node(pgdat, zonerefs); zonerefs += nr_zones; zonerefs->zone = NULL ; zonerefs->zone_idx = 0 ; } static void __build_all_zonelists(void *data){ int nid; int __maybe_unused cpu; pg_data_t *self = data; unsigned long flags; write_seqlock_irqsave(&zonelist_update_seq, flags); printk_deferred_enter(); if (self && !node_online(self->node_id)) { build_zonelists(self); } else { for_each_node(nid) { pg_data_t *pgdat = NODE_DATA(nid); build_zonelists(pgdat); } } printk_deferred_exit(); write_sequnlock_irqrestore(&zonelist_update_seq, flags); }
per_cpu_pages_init 初始化每个 CPU 的页面集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static void per_cpu_pages_init (struct per_cpu_pages *pcp, struct per_cpu_zonestat *pzstats) { int pindex; memset (pcp, 0 , sizeof (*pcp)); memset (pzstats, 0 , sizeof (*pzstats)); spin_lock_init(&pcp->lock); for (pindex = 0 ; pindex < NR_PCP_LISTS; pindex++) INIT_LIST_HEAD(&pcp->lists[pindex]); pcp->high_min = BOOT_PAGESET_HIGH; pcp->high_max = BOOT_PAGESET_HIGH; pcp->batch = BOOT_PAGESET_BATCH; pcp->free_count = 0 ; }
build_all_zonelists_init 初始化所有 zonelists 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 static noinline void __initbuild_all_zonelists_init (void ) { int cpu; __build_all_zonelists(NULL ); for_each_possible_cpu(cpu) per_cpu_pages_init(&per_cpu(boot_pageset, cpu), &per_cpu(boot_zonestats, cpu)); mminit_verify_zonelist(); cpuset_init_current_mems_allowed(); }
build_all_zonelists 初始化 zonelist 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 void __ref build_all_zonelists (pg_data_t *pgdat) { unsigned long vm_total_pages; if (system_state == SYSTEM_BOOTING) { build_all_zonelists_init(); } else { __build_all_zonelists(pgdat); } vm_total_pages = nr_free_zone_pages(gfp_zone(GFP_HIGHUSER_MOVABLE)); if (vm_total_pages < (pageblock_nr_pages * MIGRATE_TYPES)) page_group_by_mobility_disabled = 1 ; else page_group_by_mobility_disabled = 0 ; pr_info("Built %u zonelists, mobility grouping %s. Total pages: %ld\n" , nr_online_nodes, str_off_on(page_group_by_mobility_disabled), vm_total_pages); #ifdef CONFIG_NUMA pr_info("Policy zone: %s\n" , zone_names[policy_zone]); #endif }
free_pages_prepare 准备释放页面
用于在释放物理内存页之前执行一系列的检查和准备工作
对即将释放的内存页进行检查、标记清理和初始化,以确保这些页可以安全地重新纳入内存管理系统。它处理了多种特殊情况,例如复合页(compound pages)、硬件内存错误(HWPoison)、内存锁定(mlocked)等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 __always_inline bool free_pages_prepare (struct page *page, unsigned int order) { int bad = 0 ; bool skip_kasan_poison = should_skip_kasan_poison(page); bool init = want_init_on_free(); bool compound = PageCompound(page); struct folio *folio = page_folio(page); VM_BUG_ON_PAGE(PageTail(page), page); VM_BUG_ON_PAGE(compound && compound_order(page) != order, page); page->flags &= ~PAGE_FLAGS_CHECK_AT_PREP; return true ; }
__del_page_from_free_list 从空闲列表中删除页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static inline void __del_page_from_free_list(struct page *page, struct zone *zone, unsigned int order, int migratetype) { int nr_pages = 1 << order; VM_WARN_ONCE(get_pageblock_migratetype(page) != migratetype, "page type is %lu, passed migratetype is %d (nr=%d)\n" , get_pageblock_migratetype(page), migratetype, nr_pages); if (page_reported(page)) __ClearPageReported(page); list_del(&page->buddy_list); __ClearPageBuddy(page); set_page_private(page, 0 ); zone->free_area[order].nr_free--; if (order >= pageblock_order && !is_migrate_isolate(migratetype)) __mod_zone_page_state(zone, NR_FREE_PAGES_BLOCKS, -nr_pages); }
__add_to_free_list 将页面添加到空闲列表
将页面块添加到伙伴系统(Buddy System)的空闲列表中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static inline void __add_to_free_list(struct page *page, struct zone *zone, unsigned int order, int migratetype, bool tail) { struct free_area *area = &zone->free_area[order]; int nr_pages = 1 << order; VM_WARN_ONCE(get_pageblock_migratetype(page) != migratetype, "page type is %lu, passed migratetype is %d (nr=%d)\n" , get_pageblock_migratetype(page), migratetype, nr_pages); if (tail) list_add_tail(&page->buddy_list, &area->free_list[migratetype]); else list_add(&page->buddy_list, &area->free_list[migratetype]); area->nr_free++; if (order >= pageblock_order && !is_migrate_isolate(migratetype)) __mod_zone_page_state(zone, NR_FREE_PAGES_BLOCKS, nr_pages); }
set_buddy_order 设置页面的伙伴顺序 1 2 3 4 5 6 7 8 9 10 static inline void set_page_private (struct page *page, unsigned long private) { page->private = private; } static inline void set_buddy_order (struct page *page, unsigned int order) { set_page_private(page, order); __SetPageBuddy(page); }
__free_one_page 释放页面
实现了一个用于伙伴系统(buddy system)分配器的页面释放函数 __free_one_page。伙伴系统是一种高效的内存管理机制,通过将内存划分为不同大小的块(以 2 的幂次为单位),并在释放时合并相邻的空闲块来减少内存碎片
将一个页面块释放回伙伴系统,并根据需要合并相邻的空闲块(称为“伙伴”)以形成更大的块。它还负责更新内存区域的元数据和迁移类型(migratetype),并通知相关子系统
伙伴系统的基本概念
伙伴关系:
在伙伴系统中,每个内存块都有一个“伙伴”,即与其大小相同且地址连续的块。如果两个伙伴块都空闲,它们可以合并为一个更大的块。
分配与释放:
当分配一个小块时,可能需要将一个大块拆分为多个小块。当释放一个块时,如果其伙伴也空闲,则触发合并操作。
页面标记:
每个页面块的大小(order)记录在 page_private(page) 字段中。
空闲页面块通过 PageBuddy 标志标记
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 static inline void account_freepages (struct zone *zone, int nr_pages, int migratetype) { lockdep_assert_held(&zone->lock); if (is_migrate_isolate(migratetype)) return ; __mod_zone_page_state(zone, NR_FREE_PAGES, nr_pages); if (is_migrate_cma(migratetype)) __mod_zone_page_state(zone, NR_FREE_CMA_PAGES, nr_pages); else if (is_migrate_highatomic(migratetype)) WRITE_ONCE(zone->nr_free_highatomic, zone->nr_free_highatomic + nr_pages); } static inline void __free_one_page(struct page *page, unsigned long pfn, struct zone *zone, unsigned int order, int migratetype, fpi_t fpi_flags) { struct capture_control *capc = task_capc(zone); unsigned long buddy_pfn = 0 ; unsigned long combined_pfn; struct page *buddy ; bool to_tail; VM_BUG_ON(!zone_is_initialized(zone)); VM_BUG_ON_PAGE(page->flags & PAGE_FLAGS_CHECK_AT_PREP, page); VM_BUG_ON(migratetype == -1 ); VM_BUG_ON_PAGE(pfn & ((1 << order) - 1 ), page); VM_BUG_ON_PAGE(bad_range(zone, page), page); account_freepages(zone, 1 << order, migratetype); while (order < MAX_PAGE_ORDER) { int buddy_mt = migratetype; buddy = find_buddy_page_pfn(page, pfn, order, &buddy_pfn); if (!buddy) goto done_merging; if (unlikely(order >= pageblock_order)) { buddy_mt = get_pfnblock_migratetype(buddy, buddy_pfn); if (migratetype != buddy_mt && (!migratetype_is_mergeable(migratetype) || !migratetype_is_mergeable(buddy_mt))) goto done_merging; } if (page_is_guard(buddy)) clear_page_guard(zone, buddy, order); else __del_page_from_free_list(buddy, zone, order, buddy_mt); if (unlikely(buddy_mt != migratetype)) { set_pageblock_migratetype(buddy, migratetype); } combined_pfn = buddy_pfn & pfn; page = page + (combined_pfn - pfn); pfn = combined_pfn; order++; } done_merging: set_buddy_order(page, order); if (fpi_flags & FPI_TO_TAIL) to_tail = true ; else if (is_shuffle_order(order)) to_tail = shuffle_pick_tail(); else to_tail = buddy_merge_likely(pfn, buddy_pfn, page, order); __add_to_free_list(page, zone, order, migratetype, to_tail); if (!(fpi_flags & FPI_SKIP_REPORT_NOTIFY)) page_reporting_notify_free(order); }
split_large_buddy 将多块自由页面拆分为单独的页面块。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static void split_large_buddy (struct zone *zone, struct page *page, unsigned long pfn, int order, fpi_t fpi) { unsigned long end = pfn + (1 << order); VM_WARN_ON_ONCE(!IS_ALIGNED(pfn, 1 << order)); VM_WARN_ON_ONCE(PageBuddy(page)); if (order > pageblock_order) order = pageblock_order; do { int mt = get_pfnblock_migratetype(page, pfn); __free_one_page(page, pfn, zone, order, mt, fpi); pfn += 1 << order; if (pfn == end) break ; page = pfn_to_page(pfn); } while (1 ); }
free_one_page 将指定的内存页块释放到其所属的内存区域(zone)中
将指定的内存页块释放到其所属的内存区域(zone)中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 static void add_page_to_zone_llist (struct zone *zone, struct page *page, unsigned int order) { page->order = order; llist_add(&page->pcp_llist, &zone->trylock_free_pages); } static void free_one_page (struct zone *zone, struct page *page, unsigned long pfn, unsigned int order, fpi_t fpi_flags) { struct llist_head *llhead ; unsigned long flags; if (!spin_trylock_irqsave(&zone->lock, flags)) { if (unlikely(fpi_flags & FPI_TRYLOCK)) { add_page_to_zone_llist(zone, page, order); return ; } spin_lock_irqsave(&zone->lock, flags); } llhead = &zone->trylock_free_pages; if (unlikely(!llist_empty(llhead) && !(fpi_flags & FPI_TRYLOCK))) { struct llist_node *llnode ; struct page *p , *tmp ; llnode = llist_del_all(llhead); llist_for_each_entry_safe(p, tmp, llnode, pcp_llist) { unsigned int p_order = p->order; split_large_buddy(zone, p, page_to_pfn(p), p_order, fpi_flags); __count_vm_events(PGFREE, 1 << p_order); } } split_large_buddy(zone, page, pfn, order, fpi_flags); spin_unlock_irqrestore(&zone->lock, flags); __count_vm_events(PGFREE, 1 << order); }
__free_pages_ok 释放页面 1 2 3 4 5 6 7 8 9 static void __free_pages_ok(struct page *page, unsigned int order, fpi_t fpi_flags) { unsigned long pfn = page_to_pfn(page); struct zone *zone = page_zone(page); if (free_pages_prepare(page, order)) free_one_page(zone, page, pfn, order, fpi_flags); }
__free_pages_core 释放页面
释放从 page 开始的一组物理内存页,并根据内存上下文(context)执行不同的初始化和管理操作。它通过设置页的引用计数、清除标志位以及更新内存管理元数据,将这些页重新标记为可用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 void __meminit __free_pages_core(struct page *page, unsigned int order, enum meminit_context context) { unsigned int nr_pages = 1 << order; struct page *p = page; unsigned int loop; if (IS_ENABLED(CONFIG_MEMORY_HOTPLUG) && unlikely(context == MEMINIT_HOTPLUG)) { } else { for (loop = 0 ; loop < nr_pages; loop++, p++) { __ClearPageReserved(p); set_page_count(p, 0 ); } atomic_long_add(nr_pages, &page_zone(page)->managed_pages); } if (page_contains_unaccepted(page, order)) { } __free_pages_ok(page, order, FPI_TO_TAIL); }
prepare_alloc_pages 准备分配页 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static inline bool prepare_alloc_pages (gfp_t gfp_mask, unsigned int order, int preferred_nid, nodemask_t *nodemask, struct alloc_context *ac, gfp_t *alloc_gfp, unsigned int *alloc_flags) { ac->highest_zoneidx = gfp_zone(gfp_mask); ac->zonelist = node_zonelist(preferred_nid, gfp_mask); ac->nodemask = nodemask; ac->migratetype = gfp_migratetype(gfp_mask); ac->spread_dirty_pages = (gfp_mask & __GFP_WRITE); ac->preferred_zoneref = first_zones_zonelist(ac->zonelist, ac->highest_zoneidx, ac->nodemask); return true ; }
alloc_flags_nofragment 确定内存分配时是否需要设置 ALLOC_NOFRAGMENT 标志,以避免内存碎片化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 int defrag_mode;static inline unsigned int alloc_flags_nofragment (struct zone *zone, gfp_t gfp_mask) { unsigned int alloc_flags; alloc_flags = (__force int ) (gfp_mask & __GFP_KSWAPD_RECLAIM); if (defrag_mode) { alloc_flags |= ALLOC_NOFRAGMENT; return alloc_flags; } return alloc_flags; }
__zone_watermark_ok 检查内存区域(zone)是否满足分配需求的水位线条件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 bool __zone_watermark_ok(struct zone *z, unsigned int order, unsigned long mark, int highest_zoneidx, unsigned int alloc_flags, long free_pages) { long min = mark; int o; free_pages -= __zone_watermark_unusable_free(z, order, alloc_flags); if (unlikely(alloc_flags & ALLOC_RESERVES)) { if (alloc_flags & ALLOC_MIN_RESERVE) { min -= min / 2 ; if (alloc_flags & ALLOC_NON_BLOCK) min -= min / 4 ; } if (alloc_flags & ALLOC_OOM) min -= min / 2 ; } if (free_pages <= min + z->lowmem_reserve[highest_zoneidx]) return false ; if (!order) return true ; for (o = order; o < NR_PAGE_ORDERS; o++) { struct free_area *area = &z->free_area[o]; int mt; if (!area->nr_free) continue ; for (mt = 0 ; mt < MIGRATE_PCPTYPES; mt++) { if (!free_area_empty(area, mt)) return true ; } #ifdef CONFIG_CMA if ((alloc_flags & ALLOC_CMA) && !free_area_empty(area, MIGRATE_CMA)) { return true ; } #endif if ((alloc_flags & (ALLOC_HIGHATOMIC|ALLOC_OOM)) && !free_area_empty(area, MIGRATE_HIGHATOMIC)) { return true ; } } return false ; }
zone_watermark_fast 快速检查区域水位线
通过快速路径检查当前内存区域是否有足够的空闲页面满足分配需求。如果快速路径检查失败,则需要更复杂的逻辑来计算保留页面和其他条件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 static inline unsigned long zone_page_state (struct zone *zone, enum zone_stat_item item) { long x = atomic_long_read(&zone->vm_stat[item]); return x; } static inline bool zone_watermark_fast (struct zone *z, unsigned int order, unsigned long mark, int highest_zoneidx, unsigned int alloc_flags, gfp_t gfp_mask) { long free_pages; free_pages = zone_page_state(z, NR_FREE_PAGES); if (!order) { long usable_free; long reserved; usable_free = free_pages; reserved = __zone_watermark_unusable_free(z, 0 , alloc_flags); usable_free -= min(usable_free, reserved); if (usable_free > mark + z->lowmem_reserve[highest_zoneidx]) return true ; } if (__zone_watermark_ok(z, order, mark, highest_zoneidx, alloc_flags, free_pages)) return true ; if (unlikely(!order && (alloc_flags & ALLOC_MIN_RESERVE) && z->watermark_boost && ((alloc_flags & ALLOC_WMARK_MASK) == WMARK_MIN))) { mark = z->_watermark[WMARK_MIN]; return __zone_watermark_ok(z, order, mark, highest_zoneidx, alloc_flags, free_pages); } return false ; }
pcp_lock 每个 CPU 的页面缓存列表锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 #if defined(CONFIG_SMP) || defined(CONFIG_PREEMPT_RT) #define pcp_trylock_prepare(flags) do { } while (0) #define pcp_trylock_finish(flag) do { } while (0) #else #define pcp_trylock_prepare(flags) local_irq_save(flags) #define pcp_trylock_finish(flags) local_irq_restore(flags) #endif #ifndef CONFIG_PREEMPT_RT #define pcpu_task_pin() preempt_disable() #define pcpu_task_unpin() preempt_enable() #else #define pcpu_task_pin() migrate_disable() #define pcpu_task_unpin() migrate_enable() #endif #define pcpu_spin_lock(type, member, ptr) \ ({ \ type *_ret; \ pcpu_task_pin(); \ _ret = this_cpu_ptr(ptr); \ spin_lock(&_ret->member); \ _ret; \ }) #define pcpu_spin_trylock(type, member, ptr) \ ({ \ type *_ret; \ pcpu_task_pin(); \ _ret = this_cpu_ptr(ptr); \ if (!spin_trylock(&_ret->member)) { \ pcpu_task_unpin(); \ _ret = NULL; \ } \ _ret; \ }) #define pcpu_spin_unlock(member, ptr) \ ({ \ spin_unlock(&ptr->member); \ pcpu_task_unpin(); \ }) #define pcp_spin_lock(ptr) \ pcpu_spin_lock(struct per_cpu_pages, lock, ptr) #define pcp_spin_trylock(ptr) \ pcpu_spin_trylock(struct per_cpu_pages, lock, ptr) #define pcp_spin_unlock(ptr) \ pcpu_spin_unlock(lock, ptr)
expand 用于将较大的页面块拆分为较小的页面块 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 static inline unsigned int expand (struct zone *zone, struct page *page, int low, int high, int migratetype) { unsigned int size = 1 << high; unsigned int nr_added = 0 ; while (high > low) { high--; size >>= 1 ; VM_BUG_ON_PAGE(bad_range(zone, &page[size]), &page[size]); __add_to_free_list(&page[size], zone, high, migratetype, false ); set_buddy_order(&page[size], high); nr_added += size; } return nr_added; }
page_del_and_expand 将页面从空闲列表中删除并扩展 1 2 3 4 5 6 7 8 9 10 static __always_inline void page_del_and_expand (struct zone *zone, struct page *page, int low, int high, int migratetype) { int nr_pages = 1 << high; __del_page_from_free_list(page, zone, high, migratetype); nr_pages -= expand(zone, page, low, high, migratetype); account_freepages(zone, -nr_pages, migratetype); }
__rmqueue_smallest 从伙伴系统中移除最小的可用页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 static inline struct page *get_page_from_free_area (struct free_area *area, int migratetype) { return list_first_entry_or_null(&area->free_list[migratetype], struct page, buddy_list); } static __always_inlinestruct page *__rmqueue_smallest (struct zone *zone , unsigned int order , int migratetype ) { unsigned int current_order; struct free_area *area ; struct page *page ; for (current_order = order; current_order < NR_PAGE_ORDERS; ++current_order) { area = &(zone->free_area[current_order]); page = get_page_from_free_area(area, migratetype); if (!page) continue ; page_del_and_expand(zone, page, order, current_order, migratetype); trace_mm_page_alloc_zone_locked(page, order, migratetype, pcp_allowed_order(order) && migratetype < MIGRATE_PCPTYPES); return page; } return NULL ; }
__rmqueue 从伙伴系统(buddy allocator)中移除一个页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 static __always_inline struct page *__rmqueue (struct zone *zone , unsigned int order , int migratetype , unsigned int alloc_flags , enum rmqueue_mode *mode ) { struct page *page ; switch (*mode) { case RMQUEUE_NORMAL: page = __rmqueue_smallest(zone, order, migratetype); if (page) return page; fallthrough; case RMQUEUE_CMA: if (alloc_flags & ALLOC_CMA) { page = __rmqueue_cma_fallback(zone, order); if (page) { *mode = RMQUEUE_CMA; return page; } } fallthrough; case RMQUEUE_CLAIM: page = __rmqueue_claim(zone, order, migratetype, alloc_flags); if (page) { *mode = RMQUEUE_NORMAL; return page; } fallthrough; case RMQUEUE_STEAL: if (!(alloc_flags & ALLOC_NOFRAGMENT)) { page = __rmqueue_steal(zone, order, migratetype); if (page) { *mode = RMQUEUE_STEAL; return page; } } } return NULL ; }
rmqueue_bulk 从 Buddy 分配器中获取指定数量的元素 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static int rmqueue_bulk (struct zone *zone, unsigned int order, unsigned long count, struct list_head *list , int migratetype, unsigned int alloc_flags) { enum rmqueue_mode rmqm = RMQUEUE_NORMAL; unsigned long flags; int i; if (unlikely(alloc_flags & ALLOC_TRYLOCK)) { if (!spin_trylock_irqsave(&zone->lock, flags)) return 0 ; } else { spin_lock_irqsave(&zone->lock, flags); } for (i = 0 ; i < count; ++i) { struct page *page = __rmqueue(zone, order, migratetype, alloc_flags, &rmqm); if (unlikely(page == NULL )) break ; list_add_tail(&page->pcp_list, list ); } spin_unlock_irqrestore(&zone->lock, flags); return i; }
nr_pcp_alloc 计算每个 CPU 的页面缓存列表中需要分配的页面数量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 static int nr_pcp_alloc (struct per_cpu_pages *pcp, struct zone *zone, int order) { int high, base_batch, batch, max_nr_alloc; int high_max, high_min; base_batch = READ_ONCE(pcp->batch); high_min = READ_ONCE(pcp->high_min); high_max = READ_ONCE(pcp->high_max); high = pcp->high = clamp(pcp->high, high_min, high_max); if (unlikely(high < base_batch)) return 1 ; if (order) batch = base_batch; else batch = (base_batch << pcp->alloc_factor); if (high_min != high_max && !test_bit(ZONE_BELOW_HIGH, &zone->flags)) high = pcp->high = min(high + batch, high_max); if (!order) { max_nr_alloc = max(high - pcp->count - base_batch, base_batch); if (batch <= max_nr_alloc && pcp->alloc_factor < CONFIG_PCP_BATCH_SCALE_MAX) pcp->alloc_factor++; batch = min(batch, max_nr_alloc); } if (batch > 1 ) batch = max(batch >> order, 2 ); return batch; }
__rmqueue_pcplist 从每个 CPU 的页面缓存列表中移除页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static inline struct page *__rmqueue_pcplist (struct zone *zone , unsigned int order , int migratetype , unsigned int alloc_flags , struct per_cpu_pages *pcp , struct list_head *list ) { struct page *page ; do { if (list_empty(list )) { int batch = nr_pcp_alloc(pcp, zone, order); int alloced; alloced = rmqueue_bulk(zone, order, batch, list , migratetype, alloc_flags); pcp->count += alloced << order; if (unlikely(list_empty(list ))) return NULL ; } page = list_first_entry(list , struct page, pcp_list); list_del(&page->pcp_list); pcp->count -= 1 << order; } while (check_new_pages(page, order)); return page; }
rmqueue_pcplist 从每个 CPU 的页面缓存列表中尝试分配页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 static struct page *rmqueue_pcplist (struct zone *preferred_zone, struct zone *zone, unsigned int order, int migratetype, unsigned int alloc_flags) { struct per_cpu_pages *pcp ; struct list_head *list ; struct page *page ; unsigned long __maybe_unused UP_flags; pcp_trylock_prepare(UP_flags); pcp = pcp_spin_trylock(zone->per_cpu_pageset); if (!pcp) { pcp_trylock_finish(UP_flags); return NULL ; } pcp->free_count >>= 1 ; list = &pcp->lists[order_to_pindex(migratetype, order)]; page = __rmqueue_pcplist(zone, order, migratetype, alloc_flags, pcp, list ); pcp_spin_unlock(pcp); pcp_trylock_finish(UP_flags); if (page) { __count_zid_vm_events(PGALLOC, page_zonenum(page), 1 << order); zone_statistics(preferred_zone, zone, 1 ); } return page; }
rmqueue_buddy 从伙伴系统中分配页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 static __always_inlinestruct page *rmqueue_buddy (struct zone *preferred_zone, struct zone *zone, unsigned int order, unsigned int alloc_flags, int migratetype) { struct page *page ; unsigned long flags; do { page = NULL ; if (unlikely(alloc_flags & ALLOC_TRYLOCK)) { if (!spin_trylock_irqsave(&zone->lock, flags)) return NULL ; } else { spin_lock_irqsave(&zone->lock, flags); } if (alloc_flags & ALLOC_HIGHATOMIC) page = __rmqueue_smallest(zone, order, MIGRATE_HIGHATOMIC); if (!page) { enum rmqueue_mode rmqm = RMQUEUE_NORMAL; page = __rmqueue(zone, order, migratetype, alloc_flags, &rmqm); if (!page && (alloc_flags & (ALLOC_OOM|ALLOC_NON_BLOCK))) page = __rmqueue_smallest(zone, order, MIGRATE_HIGHATOMIC); if (!page) { spin_unlock_irqrestore(&zone->lock, flags); return NULL ; } } spin_unlock_irqrestore(&zone->lock, flags); } while (check_new_pages(page, order)); __count_zid_vm_events(PGALLOC, page_zonenum(page), 1 << order); zone_statistics(preferred_zone, zone, 1 ); return page; }
rmqueue 从空闲列表中获取页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 static inline bool pcp_allowed_order (unsigned int order) { if (order <= PAGE_ALLOC_COSTLY_ORDER) return true ; return false ; } __no_sanitize_memory static inline struct page *rmqueue (struct zone *preferred_zone, struct zone *zone, unsigned int order, gfp_t gfp_flags, unsigned int alloc_flags, int migratetype) { struct page *page ; if (likely(pcp_allowed_order(order))) { page = rmqueue_pcplist(preferred_zone, zone, order, migratetype, alloc_flags); if (likely(page)) goto out; } page = rmqueue_buddy(preferred_zone, zone, order, alloc_flags, migratetype); out: if ((alloc_flags & ALLOC_KSWAPD) && unlikely(test_bit(ZONE_BOOSTED_WATERMARK, &zone->flags))) { clear_bit(ZONE_BOOSTED_WATERMARK, &zone->flags); wakeup_kswapd(zone, 0 , 0 , zone_idx(zone)); } VM_BUG_ON_PAGE(page && bad_range(zone, page), page); return page; }
post_alloc_hook 分配后钩子函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static inline void set_page_pfmemalloc (struct page *page) { page->lru.next = (void *)BIT(1 ); } static inline void clear_page_pfmemalloc (struct page *page) { page->lru.next = NULL ; } inline void post_alloc_hook (struct page *page, unsigned int order, gfp_t gfp_flags) { bool init = !want_init_on_free() && want_init_on_alloc(gfp_flags) && !should_skip_init(gfp_flags); bool zero_tags = init && (gfp_flags & __GFP_ZEROTAGS); int i; set_page_private(page, 0 ); if (zero_tags) { for (i = 0 ; i != 1 << order; ++i) tag_clear_highpage(page + i); init = false ; } if (init) kernel_init_pages(page, 1 << order); }
prep_new_page 准备新分配的页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static void prep_new_page (struct page *page, unsigned int order, gfp_t gfp_flags, unsigned int alloc_flags) { post_alloc_hook(page, order, gfp_flags); if (order && (gfp_flags & __GFP_COMP)) prep_compound_page(page, order); if (alloc_flags & ALLOC_NO_WATERMARKS) set_page_pfmemalloc(page); else clear_page_pfmemalloc(page); }
get_page_from_freelist 从空闲列表中获取页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 static struct page *get_page_from_freelist (gfp_t gfp_mask, unsigned int order, int alloc_flags, const struct alloc_context *ac) { struct zoneref *z ; struct zone *zone ; struct pglist_data *last_pgdat = NULL ; bool last_pgdat_dirty_ok = false ; bool no_fallback; retry: no_fallback = alloc_flags & ALLOC_NOFRAGMENT; z = ac->preferred_zoneref; for_next_zone_zonelist_nodemask(zone, z, ac->highest_zoneidx, ac->nodemask) { struct page *page ; unsigned long mark; if (ac->spread_dirty_pages) { if (last_pgdat != zone->zone_pgdat) { last_pgdat = zone->zone_pgdat; last_pgdat_dirty_ok = node_dirty_ok(zone->zone_pgdat); } if (!last_pgdat_dirty_ok) continue ; } if (test_bit(ZONE_BELOW_HIGH, &zone->flags)) goto check_alloc_wmark; mark = high_wmark_pages(zone); if (zone_watermark_fast(zone, order, mark, ac->highest_zoneidx, alloc_flags, gfp_mask)) goto try_this_zone; else set_bit(ZONE_BELOW_HIGH, &zone->flags); check_alloc_wmark: mark = wmark_pages(zone, alloc_flags & ALLOC_WMARK_MASK); if (!zone_watermark_fast(zone, order, mark, ac->highest_zoneidx, alloc_flags, gfp_mask)) { int ret; BUILD_BUG_ON(ALLOC_NO_WATERMARKS < NR_WMARK); if (alloc_flags & ALLOC_NO_WATERMARKS) goto try_this_zone; if (!node_reclaim_enabled() || !zone_allows_reclaim(zonelist_zone(ac->preferred_zoneref), zone)) continue ; ret = node_reclaim(zone->zone_pgdat, gfp_mask, order); switch (ret) { case NODE_RECLAIM_NOSCAN: continue ; case NODE_RECLAIM_FULL: continue ; default : if (zone_watermark_ok(zone, order, mark, ac->highest_zoneidx, alloc_flags)) goto try_this_zone; continue ; } } try_this_zone: page = rmqueue(zonelist_zone(ac->preferred_zoneref), zone, order, gfp_mask, alloc_flags, ac->migratetype); if (page) { prep_new_page(page, order, gfp_mask, alloc_flags); if (unlikely(alloc_flags & ALLOC_HIGHATOMIC)) reserve_highatomic_pageblock(page, order, zone); return page; } } return NULL ; }
__alloc_pages_slowpath 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 tatic inline struct page * __alloc_pages_slowpath (gfp_t gfp_mask , unsigned int order , struct alloc_context *ac ) { bool can_direct_reclaim = gfp_mask & __GFP_DIRECT_RECLAIM; bool can_compact = gfp_compaction_allowed(gfp_mask); bool nofail = gfp_mask & __GFP_NOFAIL; const bool costly_order = order > PAGE_ALLOC_COSTLY_ORDER; struct page *page = NULL ; unsigned int alloc_flags; unsigned long did_some_progress; enum compact_priority compact_priority ; enum compact_result compact_result ; int compaction_retries; int no_progress_loops; unsigned int cpuset_mems_cookie; unsigned int zonelist_iter_cookie; int reserve_flags; if (unlikely(nofail)) { WARN_ON_ONCE(order > 1 ); WARN_ON_ONCE(!can_direct_reclaim); WARN_ON_ONCE(current->flags & PF_MEMALLOC); } restart: compaction_retries = 0 ; no_progress_loops = 0 ; compact_result = COMPACT_SKIPPED; compact_priority = DEF_COMPACT_PRIORITY; cpuset_mems_cookie = read_mems_allowed_begin(); zonelist_iter_cookie = zonelist_iter_begin(); alloc_flags = gfp_to_alloc_flags(gfp_mask, order); ac->preferred_zoneref = first_zones_zonelist(ac->zonelist, ac->highest_zoneidx, ac->nodemask); if (!zonelist_zone(ac->preferred_zoneref)) goto nopage; if (cpusets_insane_config() && (gfp_mask & __GFP_HARDWALL)) { struct zoneref *z = first_zones_zonelist(ac->zonelist, ac->highest_zoneidx, &cpuset_current_mems_allowed); if (!zonelist_zone(z)) goto nopage; } if (alloc_flags & ALLOC_KSWAPD) wake_all_kswapds(order, gfp_mask, ac); page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac); if (page) goto got_pg; if (can_direct_reclaim && can_compact && (costly_order || (order > 0 && ac->migratetype != MIGRATE_MOVABLE)) && !gfp_pfmemalloc_allowed(gfp_mask)) { page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac, INIT_COMPACT_PRIORITY, &compact_result); if (page) goto got_pg; if (costly_order && (gfp_mask & __GFP_NORETRY)) { if (compact_result == COMPACT_SKIPPED || compact_result == COMPACT_DEFERRED) goto nopage; compact_priority = INIT_COMPACT_PRIORITY; } } retry: if (check_retry_cpuset(cpuset_mems_cookie, ac) || check_retry_zonelist(zonelist_iter_cookie)) goto restart; if (alloc_flags & ALLOC_KSWAPD) wake_all_kswapds(order, gfp_mask, ac); reserve_flags = __gfp_pfmemalloc_flags(gfp_mask); if (reserve_flags) alloc_flags = gfp_to_alloc_flags_cma(gfp_mask, reserve_flags) | (alloc_flags & ALLOC_KSWAPD); if (!(alloc_flags & ALLOC_CPUSET) || reserve_flags) { ac->nodemask = NULL ; ac->preferred_zoneref = first_zones_zonelist(ac->zonelist, ac->highest_zoneidx, ac->nodemask); } page = get_page_from_freelist(gfp_mask, order, alloc_flags, ac); if (page) goto got_pg; if (!can_direct_reclaim) goto nopage; if (current->flags & PF_MEMALLOC) goto nopage; page = __alloc_pages_direct_reclaim(gfp_mask, order, alloc_flags, ac, &did_some_progress); if (page) goto got_pg; page = __alloc_pages_direct_compact(gfp_mask, order, alloc_flags, ac, compact_priority, &compact_result); if (page) goto got_pg; if (gfp_mask & __GFP_NORETRY) goto nopage; if (costly_order && (!can_compact || !(gfp_mask & __GFP_RETRY_MAYFAIL))) goto nopage; if (should_reclaim_retry(gfp_mask, order, ac, alloc_flags, did_some_progress > 0 , &no_progress_loops)) goto retry; if (did_some_progress > 0 && can_compact && should_compact_retry(ac, order, alloc_flags, compact_result, &compact_priority, &compaction_retries)) goto retry; if (defrag_mode && (alloc_flags & ALLOC_NOFRAGMENT)) { alloc_flags &= ~ALLOC_NOFRAGMENT; goto retry; } if (check_retry_cpuset(cpuset_mems_cookie, ac) || check_retry_zonelist(zonelist_iter_cookie)) goto restart; page = __alloc_pages_may_oom(gfp_mask, order, ac, &did_some_progress); if (page) goto got_pg; if (tsk_is_oom_victim(current) && (alloc_flags & ALLOC_OOM || (gfp_mask & __GFP_NOMEMALLOC))) goto nopage; if (did_some_progress) { no_progress_loops = 0 ; goto retry; } nopage: if (check_retry_cpuset(cpuset_mems_cookie, ac) || check_retry_zonelist(zonelist_iter_cookie)) goto restart; if (unlikely(nofail)) { if (!can_direct_reclaim) goto fail; page = __alloc_pages_cpuset_fallback(gfp_mask, order, ALLOC_MIN_RESERVE, ac); if (page) goto got_pg; cond_resched(); goto retry; } fail: warn_alloc(gfp_mask, ac->nodemask, "page allocation failure: order:%u" , order); got_pg: return page; }
__alloc_frozen_pages_noprof 分配冻结页面 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 static inline gfp_t current_gfp_context (gfp_t flags) { unsigned int pflags = READ_ONCE(current->flags); if (unlikely(pflags & (PF_MEMALLOC_NOIO | PF_MEMALLOC_NOFS | PF_MEMALLOC_PIN))) { if (pflags & PF_MEMALLOC_NOIO) flags &= ~(__GFP_IO | __GFP_FS); else if (pflags & PF_MEMALLOC_NOFS) flags &= ~__GFP_FS; if (pflags & PF_MEMALLOC_PIN) flags &= ~__GFP_MOVABLE; } return flags; } struct page *__alloc_frozen_pages_noprof (gfp_t gfp , unsigned int order , int preferred_nid , nodemask_t *nodemask ) { struct page *page ; unsigned int alloc_flags = ALLOC_WMARK_LOW; gfp_t alloc_gfp; struct alloc_context ac = { }; if (WARN_ON_ONCE_GFP(order > MAX_PAGE_ORDER, gfp)) return NULL ; gfp &= gfp_allowed_mask; gfp = current_gfp_context(gfp); alloc_gfp = gfp; if (!prepare_alloc_pages(gfp, order, preferred_nid, nodemask, &ac, &alloc_gfp, &alloc_flags)) return NULL ; alloc_flags |= alloc_flags_nofragment(zonelist_zone(ac.preferred_zoneref), gfp); page = get_page_from_freelist(alloc_gfp, order, alloc_flags, &ac); if (likely(page)) goto out; alloc_gfp = gfp; ac.spread_dirty_pages = false ; ac.nodemask = nodemask; page = __alloc_pages_slowpath(alloc_gfp, order, &ac); out: return page; }
zone_set_pageset_high_and_batch 根据区域的大小,为区域的所有每 CPU 页面集计算并设置新的高值和批处理值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 static void zone_set_pageset_high_and_batch (struct zone *zone, int cpu_online) { int new_high_min, new_high_max, new_batch; new_batch = max(1 , zone_batchsize(zone)); if (percpu_pagelist_high_fraction) { new_high_min = zone_highsize(zone, new_batch, cpu_online, percpu_pagelist_high_fraction); new_high_max = new_high_min; } else { new_high_min = zone_highsize(zone, new_batch, cpu_online, 0 ); new_high_max = zone_highsize(zone, new_batch, cpu_online, MIN_PERCPU_PAGELIST_HIGH_FRACTION); } if (zone->pageset_high_min == new_high_min && zone->pageset_high_max == new_high_max && zone->pageset_batch == new_batch) return ; zone->pageset_high_min = new_high_min; zone->pageset_high_max = new_high_max; zone->pageset_batch = new_batch; __zone_set_pageset_high_and_batch(zone, new_high_min, new_high_max, new_batch); }
setup_zone_pageset 设置每个区域的页面集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void __meminit setup_zone_pageset (struct zone *zone) { int cpu; if (sizeof (struct per_cpu_zonestat) > 0 ) zone->per_cpu_zonestats = alloc_percpu(struct per_cpu_zonestat); zone->per_cpu_pageset = alloc_percpu(struct per_cpu_pages); for_each_possible_cpu(cpu) { struct per_cpu_pages *pcp ; struct per_cpu_zonestat *pzstats ; pcp = per_cpu_ptr(zone->per_cpu_pageset, cpu); pzstats = per_cpu_ptr(zone->per_cpu_zonestats, cpu); per_cpu_pages_init(pcp, pzstats); } zone_set_pageset_high_and_batch(zone, 0 ); }
setup_per_cpu_pageset 设置每个 CPU 的页面集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 void __init setup_per_cpu_pageset (void ) { struct pglist_data *pgdat ; struct zone *zone ; int __maybe_unused cpu; for_each_populated_zone(zone) setup_zone_pageset(zone); for_each_online_pgdat(pgdat) pgdat->per_cpu_nodestats = alloc_percpu(struct per_cpu_nodestat); }
sysctl_lowmem_reserve_ratio: 定义内存区域之间保护比率的内核参数 此代码片段定义了一个名为sysctl_lowmem_reserve_ratio
的全局数组。这个数组是Linux内核内存管理的一个关键可调参数 (tunable) , 它为setup_per_zone_lowmem_reserve
函数提供了核心的”保护比率”。数组中的每一个值都规定了, 当一个来自较高内存区域(如ZONE_NORMAL
)的内存分配请求试图在较低内存区域(如ZONE_DMA
)中分配内存时, 应该在该较低区域中保留多少内存, 以防止其被完全耗尽。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 static int sysctl_lowmem_reserve_ratio[MAX_NR_ZONES] = {#ifdef CONFIG_ZONE_DMA [ZONE_DMA] = 256 , #endif #ifdef CONFIG_ZONE_DMA32 [ZONE_DMA32] = 256 , #endif [ZONE_NORMAL] = 32 , #ifdef CONFIG_HIGHMEM [ZONE_HIGHMEM] = 0 , #endif [ZONE_MOVABLE] = 0 , };
calculate_totalreserve_pages: 计算内核保留的总页数 此函数的核心作用是计算一个全局的内核参数 totalreserve_pages
。这个值代表了系统中所有内存区域(Zone)的”保留页数”总和, 它综合了每个区域的高水位标记 (high_watermark
) 和为防止低层内存被高层内存分配耗尽而设置的低内存保留区 (lowmem_reserve
)。这个总保留页数是内核衡量整体内存压力的一个关键指标, 主要供OOM (Out-Of-Memory) Killer用以判断系统是否真正处于危险的低内存状态。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 static void calculate_totalreserve_pages (void ) { struct pglist_data *pgdat ; unsigned long reserve_pages = 0 ; enum zone_type i , j ; for_each_online_pgdat(pgdat) { pgdat->totalreserve_pages = 0 ; for (i = 0 ; i < MAX_NR_ZONES; i++) { struct zone *zone = pgdat->node_zones + i; long max = 0 ; unsigned long managed_pages = zone_managed_pages(zone); for (j = i; j < MAX_NR_ZONES; j++) { if (zone->lowmem_reserve[j] > max) max = zone->lowmem_reserve[j]; } max += high_wmark_pages(zone); if (max > managed_pages) max = managed_pages; pgdat->totalreserve_pages += max; reserve_pages += max; } } totalreserve_pages = reserve_pages; trace_mm_calculate_totalreserve_pages(totalreserve_pages); }
calculate_min_free_kbytes: 计算内核应保留的最小空闲内存 此函数的作用是为Linux内核计算一个至关重要的参数:min_free_kbytes
。这个参数定义了内核将尽力维持的系统最小空闲内存量, 它是一个安全基线, 用于防止系统在内存压力下完全耗尽内存而崩溃。此函数采用非线性的平方根公式进行计算, 以便在小内存和大内存系统上都能得出一个合理的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 void calculate_min_free_kbytes (void ) { unsigned long lowmem_kbytes; int new_min_free_kbytes; lowmem_kbytes = nr_free_buffer_pages() * (PAGE_SIZE >> 10 ); new_min_free_kbytes = int_sqrt(lowmem_kbytes * 16 ); if (new_min_free_kbytes > user_min_free_kbytes) min_free_kbytes = clamp(new_min_free_kbytes, 128 , 262144 ); else pr_warn("min_free_kbytes is not updated to %d because user defined value %d is preferred\n" , new_min_free_kbytes, user_min_free_kbytes); }
setup_per_zone_wmarks: 为每个内存区域(Zone)设置水位标记 此函数的核心作用是为内核中的每一个内存区域(Zone)计算并设置其min
、low
、和high
三个”水位标记” (watermarks)。这些水位标记是内存管理子系统的关键阈值。当一个区域的空闲内存数量下降到不同的水位线以下时, 内核会触发相应的对策, 从最温和的后台回收, 到最激进的阻塞分配请求, 从而确保系统在内存压力下仍能保持稳定, 防止因内存耗尽而崩溃。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 static void __setup_per_zone_wmarks(void ){ unsigned long pages_min = min_free_kbytes >> (PAGE_SHIFT - 10 ); unsigned long lowmem_pages = 0 ; struct zone *zone ; unsigned long flags; for_each_zone(zone) { if (!is_highmem(zone) && zone_idx(zone) != ZONE_MOVABLE) lowmem_pages += zone_managed_pages(zone); } for_each_zone(zone) { u64 tmp; spin_lock_irqsave(&zone->lock, flags); tmp = (u64)pages_min * zone_managed_pages(zone); tmp = div64_ul(tmp, lowmem_pages); if (is_highmem(zone) || zone_idx(zone) == ZONE_MOVABLE) { unsigned long min_pages; min_pages = zone_managed_pages(zone) / 1024 ; min_pages = clamp(min_pages, SWAP_CLUSTER_MAX, 128UL ); zone->_watermark[WMARK_MIN] = min_pages; } else { zone->_watermark[WMARK_MIN] = tmp; } tmp = max_t (u64, tmp >> 2 , mult_frac(zone_managed_pages(zone), watermark_scale_factor, 10000 )); zone->watermark_boost = 0 ; zone->_watermark[WMARK_LOW] = min_wmark_pages(zone) + tmp; zone->_watermark[WMARK_HIGH] = low_wmark_pages(zone) + tmp; zone->_watermark[WMARK_PROMO] = high_wmark_pages(zone) + tmp; trace_mm_setup_per_zone_wmarks(zone); spin_unlock_irqrestore(&zone->lock, flags); } calculate_totalreserve_pages(); } void setup_per_zone_wmarks (void ) { struct zone *zone ; static DEFINE_SPINLOCK (lock) ; spin_lock(&lock); __setup_per_zone_wmarks(); spin_unlock(&lock); for_each_zone(zone) zone_pcp_update(zone, 0 ); }
setup_per_zone_lowmem_reserve: 设置每个内存区域的低内存保留区 此函数的核心作用是为内核中的每个内存区域(Zone)设置一个保护机制, 称为”低内存保留区” (lowmem_reserve
)。这个机制的目的是防止来自较高内存区域的分配请求, 耗尽较低内存区域的内存 。它通过在每个较低的区域中”保留”一部分页面, 使这些页面对于那些本可以由较高区域满足的分配请求变得不可用, 从而确保即使在内存压力下, 每个区域也能为真正需要它的分配请求保留一些空间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 static void setup_per_zone_lowmem_reserve (void ) { struct pglist_data *pgdat ; enum zone_type i , j ; for_each_online_pgdat(pgdat) { for (i = 0 ; i < MAX_NR_ZONES - 1 ; i++) { struct zone *zone = &pgdat->node_zones[i]; int ratio = sysctl_lowmem_reserve_ratio[i]; bool clear = !ratio || !zone_managed_pages(zone); unsigned long managed_pages = 0 ; for (j = i + 1 ; j < MAX_NR_ZONES; j++) { struct zone *upper_zone = &pgdat->node_zones[j]; managed_pages += zone_managed_pages(upper_zone); if (clear) zone->lowmem_reserve[j] = 0 ; else zone->lowmem_reserve[j] = managed_pages / ratio; trace_mm_setup_per_zone_lowmem_reserve(zone, upper_zone, zone->lowmem_reserve[j]); } } } calculate_totalreserve_pages(); }
init_per_zone_wmark_min: 初始化每个内存区域的水位标记 此函数的核心作用是根据系统中可用的总内存量, 计算并设置内核内存管理子系统的关键阈值——“水位标记” (watermarks
)。这些水位标记是内存压力的指示器, 当空闲内存量下降到不同水位时, 会触发内核采取不同的行动 (如启动页面回收、阻塞分配请求等), 以确保系统的稳定运行, 防止因内存耗尽而崩溃。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 int __meminit init_per_zone_wmark_min (void ) { calculate_min_free_kbytes(); setup_per_zone_wmarks(); refresh_zone_stat_thresholds(); setup_per_zone_lowmem_reserve(); #ifdef CONFIG_NUMA setup_min_unmapped_ratio(); setup_min_slab_ratio(); #endif khugepaged_min_free_kbytes_update(); return 0 ; } postcore_initcall(init_per_zone_wmark_min)