分配器与回收机制

共 20 题
📑 题目列表 20 题
#
★★★

1. Buddy 分配器(页分配器)的 order 与伙伴合并

解释 Buddy 分配器(页分配器)的 order 概念与伙伴(buddy)合并机制?

  • 理解 order 表示 2^order 页的连续块
  • 理解 buddy 按 order 分桶管理空闲页
  • 理解分配时的拆分与释放时的伙伴合并

Buddy(伙伴)分配器是内核页分配器,以连续物理页块为单位分配。order 表示块的大小,一个 order 的块包含 2^order 个连续页(order 0 为 1 页,order 9 为 512 页即 2MB)。Buddy 维护一系列按 order 分桶的空闲链表(free_list[order]),每桶存放大小为 2^order 页的连续块。分配时,从请求的 order 起,若该 order 桶为空则向上找更大的 order 桶,把大块"分裂"成两个伙伴(buddy)并逐级下放,直到拆出所需大小的块。释放时,若释放的块与相邻的伙伴(buddy)都空闲且可合并,则合并为更大的块,逐级向上合并,减少碎片。伙伴必须满足地址对齐规则(互为伙伴的地址只在某一位不同)。Buddy 通过"分裂分配 + 合并释放"保证大块连续内存的可用性。

Buddy 的核心是"以 2 的幂组织块 + 分裂/合并",这是 Linux 分配物理连续内存的基础。理解 order、伙伴规则与合并是掌握页分配器、THP 大页与碎片治理的起点。

#
★★★

2. per-CPU pageset(pcp list),页分配快速路径为何按 CPU 缓存部分空闲页,与 buddy 的批量填充/清空如何协同?

解释 per-CPU pageset(pcp list)为何按 CPU 缓存部分空闲页,以及它与 buddy 的批量填充/清空如何协同?

  • 理解 pcp list 减少锁竞争分配
  • 理解 pcp 与 buddy 的批量填充/清空
  • 理解 pcp 的批量阈值与高水位

per-CPU pageset(pcp list)是每个 CPU 独立维护的空闲页缓存,用于加速页分配并减少锁竞争:CPU 分配页时优先从自己的 pcp list 取,无需访问全局 buddy 锁,从而避免多核竞争。pcp 与 buddy 通过批量填充/清空协同:当 pcp list 空闲页不足时,从 buddy 批量取(批量填充)一批页到 pcp;当 pcp 页过多或达到高水位时,批量把多余页清空(批量清空)回 buddy。批量操作的粒度(batch)与 pcp 的 high/low 水位由 CPU 数、内存等动态调整,既保证分配速度又避免 pcp 占用过多内存。pcp 使用 zone 的本地分配(local node),配合 NUMA 保证本地性。这种"本地缓存 + 批量拉取"是性能与内存占用平衡的经典设计。

pcp 是"每 CPU 本地缓存 + 批量从 buddy 取还"的快速路径,核心是减少锁竞争与内存占用。理解它与 buddy 的批量协同,是掌握页分配性能与调优的关键。

#
★★★

3. overcommit_memory 的三种模式如何影响大块 malloc 与 OOM,Committed_AS 统计承诺内存的作用?

解释 overcommit_memory 三种模式如何影响大块 malloc 与 OOM,以及 Committed_AS 统计承诺内存的作用?

  • 理解三种模式(0=启发式、1=总是允许、2=禁止过量)
  • 理解各模式对 malloc 与 OOM 的影响
  • 理解 Committed_AS 与 CommitLimit 的作用

overcommit_memory 控制内核是否允许内存过量承诺(overcommit),三种模式:0(启发式,默认):允许合理 overcommit,内核做启发式判断拒绝明显不合理的分配;1(总是允许):从不拒绝 malloc,任何虚拟内存申请都成功,但实际使用超过物理+swap 时触发 OOM;2(禁止过量):不使用 overcommit,malloc 的虚拟提交总量受 CommitLimit 限制(CommitLimit = swap + RAM × overcommit_ratio/100,overcommit_ratio 默认 50,即默认约 swap + 50% RAM),超过则 malloc 失败(返回 ENOMEM)。对 malloc 的影响:模式 0/1 下大块 malloc 通常成功(虚拟地址),模式 2 下可能因提交超限而失败。对 OOM 的影响:模式 2 从源头拒绝过量分配减少 OOM;模式 1 允许过量,实际触达时 OOM。Committed_AS 是 /proc/meminfo 中当前已承诺(提交)的虚拟内存总量,与 CommitLimit 比较可判断是否接近 overcommit 上限。

overcommit 是"虚拟内存与实际物理内存"解耦的关键。理解三种模式对 malloc 成功与否与 OOM 触发的影响,以及 Committed_AS 的监控作用,是排查内存分配与 OOM 的理论基础。

#
★★

4. CMA(Contiguous Memory Allocator)与大块连续内存申请

解释 CMA(Contiguous Memory Allocator)的作用,以及它如何支持大块连续内存申请?

  • 理解 CMA 预留内存池用于连续分配
  • 理解 CMA 的迁移型(MIGRATE_CMA)与按需迁移
  • 理解 CMA 的典型用途(DMA、GPU、摄像头)

CMA(Contiguous Memory Allocator)解决"需要大块连续物理内存但系统被碎片化"的问题。它通过内核参数预留一块内存池(CMA 区域),这些内存平时被当作可移动页(MIGRATE_CMA)供普通分配使用,当有设备需要大块连续内存(如 DMA、GPU、摄像头、视频解码)时,CMA 从该区域迁移走可移动页,聚合出连续块供设备使用。这样既保留了连续内存的可用性,又避免了持续占用造成浪费。CMA 分配可能触发页面迁移(cmalloc),在需要连续内存时临时把占用页搬走,因此有延迟开销。CMA 常与 dma-contiguous 框架配合,用于驱动需要物理连续内存的硬件。相比直接预留专用内存,CMA 更高效地利用内存。

CMA 是"预留池 + 按需迁移聚合"的连续内存方案,兼顾"平时可用"与"需要时连续"。理解它适用于需要物理连续内存的驱动场景,是掌握嵌入式/多媒体内存管理的关键。

#
★★

5. KMSAN(Kernel Memory Sanitizer)的未初始化内存检测

解释 KMSAN(Kernel Memory Sanitizer)的作用,以及它如何检测内核中的未初始化内存读取?

  • 理解 KMSAN 是内核的 MemorySanitizer
  • 理解影子内存追踪未初始化状态
  • 理解其检测用例与编译要求

KMSAN(Kernel Memory Sanitizer)是内核的未初始化内存检测工具,类似用户态的 MemorySanitizer。它通过编译器插桩(基于 Clang 的 -fsanitize=kernel-memory)为每个内存读写生成影子内存(shadow memory)追踪,记录每个字节是否被初始化。运行时会检查读取是否触及未初始化数据,一旦检测到未初始化读取,就报告位置、调用栈与产生该内存的源头。KMSAN 能检测内核代码中读取未初始化堆栈、堆或全局变量的 bug,这类问题通常表现为随机行为或安全漏洞(信息泄漏)。使用 KMSAN 需要 Clang 构建支持,且编译开销与运行开销较大,通常用于开发与测试阶段而非生产。与 KASAN(越界/UAF)不同,KMSAN 专门针对"未初始化读取"。

KMSAN 通过影子内存追踪每个字节的初始化状态,定位未初始化读取。理解它区别于 KASAN 的定位(未初始化 vs 越界/UAF),是掌握内核 sanitizer 工具链的关键。

#
★★

6. SLUB 的 per-CPU cache 与 partial list 工程取舍

解释 SLUB 分配器的 per-CPU cache 与 partial list 结构,以及它们的工程取舍?

  • 理解 SLUB 的 per-CPU freelist 快速路径
  • 理解 partial list 管理与 slow path
  • 理解简单性、性能与调试的取舍

SLUB 是内核默认的 slab 分配器(用于 kmalloc 与 kmem_cache 等小对象分配)。它的核心是 per-CPU freelist:每个 CPU 维护一个空闲对象链表,分配对象时直接从本 CPU freelist 取(fast path),无需锁,速度极快;释放时把对象放回本 CPU freelist。当 per-CPU freelist 不足时,从 slab 的 partial list(部分满的 slab 页链表)中取整页填充(slow path)。partial list 是一种平衡:既不必为每个对象维护复杂结构,又能高效复用页。SLUB 相比 SLAB 简化了数据结构(去掉了 CPU 数组、着色等复杂机制),以更简单、更快、更易调试为设计目标,同时通过嵌入式对象头支持调试选项。取舍上,SLUB 牺牲了 SLAB 的一些缓存着色优化,但换来了更低的复杂性与更好的多核扩展性。

SLUB 的"per-CPU freelist 快速路径 + partial list 慢速路径"是性能与简单的平衡。理解它取代 SLAB 的原因(简单、快、可扩展)是掌握内核对象分配器演化的关键。

#
★★

7. Slab 分配器与 SLOB、SLAB、SLUB 三代演化

解释 Slab 分配器的发展脉络,以及 SLOB、SLAB、SLUB 三代的演化与差异?

  • 理解 SLOB 面向小内存嵌入式
  • 理解 SLAB 面向缓存与着色
  • 理解 SLUB 简化为每 CPU freelist

Slab 分配器用于内核对小对象(如结构体、inode、dentry)的高效分配与缓存复用。三代:SLOB(Simple List of Blocks)是最简单的实现,用一个链表管理块,内存占用小、代码少,适合内存极小的嵌入式系统,但性能差。SLAB 是经典实现,引入 slab 缓存、着色(coloring)、per-CPU 数组,为对象缓存与硬件缓存优化,但结构复杂、内存开销大。SLUB 是当前默认,去掉了 SLAB 的复杂着色与 CPU 数组,改用 per-CPU freelist + partial list,更简单、更快、更易调试,且内存占用更小。演化趋势是从"简单(SLOB)"到"复杂优化(SLAB)"再到"简单高效(SLUB)",即用简洁结构换取性能与可维护性。SLUB 通过 kmem_cache 管理对象,支持 slabinfo 观察。

三代 slab 反映了"复杂度与性能平衡"的演进:SLAB 过度优化、SLUB 回归简洁。理解差异有助于理解内核对象分配与 /proc/slabinfo 观测。

#
★★

8. kmalloc 与 vmalloc 的语义差异(连续物理内存 vs 虚拟连续)

说明 kmalloc 与 vmalloc 在分配语义上的差异,以及各自的适用场景?

  • 理解 kmalloc 分配物理连续内存但虚拟不必连续
  • 理解 vmalloc 分配虚拟连续但物理可离散
  • 理解两者在性能与可用场景上的差异

kmalloc 与 vmalloc 都是内核动态内存分配,但语义不同。kmalloc 分配物理连续的内存(在 ZONE_NORMAL 等),虚拟地址也连续,但分配的内存物理连续、大小受限,依赖 buddy 分配器,仅适合较小的块(一般限制在几 MB 内,与 GFP 与 order 有关),分配速度快、适合需要物理连续或频繁访问的场景(如 DMA、设备结构)。vmalloc 分配虚拟地址连续、但物理页可离散的内存,通过页表映射把分散的物理页组合成连续虚拟区,适合大块分配(如大缓冲区、模块),因为无需物理连续,但引入页表开销与 TLB miss,访问性能略低,且不能用于需要物理连续的地方(如 DMA)。语义差异的本质:kmalloc 保证物理连续,vmalloc 只保证虚拟连续。选择:需要物理连续或小对象用 kmalloc,需要大块虚拟连续且物理可离散用 vmalloc。

二者差异是"物理连续性 vs 虚拟连续性"。kmalloc 快且物理连续,vmalloc 灵活支持大块但开销大。理解语义才能正确选型(如 DMA 必须 kmalloc)。

#
★★

9. 页分配器的水位(watermark),min、low、high 的含义

解释页分配器的水位(watermark)min、low、high 的含义与作用?

  • 理解三个水位的数值关系
  • 理解水位如何触发 kswapd 与 direct reclaim
  • 理解 watermark 与内存压力

页分配器用水位(watermark)管理内存剩余量,区分三个阈值:min(最小水位)、low(低水位)、high(高水位),满足 min < low < high。当空闲内存:高于 high,内存充足,正常分配;低于 low,触发 kswapd 后台回收,把空闲内存恢复到 high;低于 min,kswapd 也来不及,分配进程触发 direct reclaim(同步回收)以满足分配,低于 min 时分配可能失败(除非可回收)。min 水位是分配必须满足的底线,UMA 上还保留一定的"不可分配"水位给紧急分配。这些水位由 /proc/sys/vm/watermark_scale_factor 等调节,并受 min_free_kbytes 影响。水位的意义:定义"何时后台回收(low)"与"何时必须同步回收(min)",从而平衡内存利用率与延迟尖刺。

水位是"回收时钟"的阈值:low 触发后台 kswapd,min 触发同步 direct reclaim。理解三个水位的关系是掌握内存回收时机与延迟尖刺来源的关键。

#
★★

10. SLAB 与 SLUB 的命中率差异

说明 SLAB 与 SLUB 分配器在命中率(cache hit)方面的差异及原因?

  • 理解 SLAB 的着色与 per-CPU 数组优化
  • 理解 SLUB 的 per-CPU freelist
  • 理解两者命中率差异的本质

SLAB 与 SLUB 都通过 per-CPU 缓存提升对象访问的局部性,但命中率表现有差异。SLAB 对每个 slab 缓存维护 per-CPU buffer(数组),并通过着色(coloring)把对象分散到不同 cache line,以缓解硬件缓存冲突,优化读写性能;SLAB 还维护空闲/部分/满的三类 slab 链表。SLUB 简化了结构,用 per-CPU freelist 直接分配对象,访问对象的局部性更好(对象在页内连续),但去掉了 SLAB 的着色机制。命中率差异:SLUB 的 per-CPU freelist 让同 CPU 分配的对象集中在同一/相邻 slab,硬件缓存命中率通常更高;SLAB 的着色在特定场景下能减少 cache 冲突。总体而言,SLUB 在多数场景命中率与性能优于 SLAB,且更简单,因此被选为默认。差异根因在于 SLUB 更贴合现代硬件(大缓存、NUMA)与简化管理。

命中率差异源于两者结构:SLAB 靠着色与 CPU 数组,SLUB 靠简洁的 per-CPU freelist 提升局部性。理解 SLUB 更优的原因(现代硬件 + 简洁)是掌握内核对象分配的关键。

#
★★

11. arm64 的页大小(4K、16K、64K)的工程取舍

说明 arm64 架构下 4K、16K、64K 页大小的工程取舍与适用场景?

  • 理解不同页大小的优缺点
  • 理解页大小与内存开销、TLB 命中
  • 理解 arm64 的页大小选择

arm64 支持 4K、16K、64K 三种固定页大小,选择影响内存与性能。页越大:TLB 覆盖范围越大(同样 TLB 条目能映射更多内存),减少 TLB miss,但页表占用与单页内部分配碎片(内存浪费)更大;页越小:内存利用率高、分配粒度灵活,但 TLB 覆盖小、页表开销大。4K 是最常见默认,与多数发行版一致,兼容性好、内存利用灵活,适合通用负载。16K 是折中,在 TLB 与内存效率间平衡,部分 Android/服务器用。64K 面向高性能、大内存、数据库等场景,TLB 覆盖极大、减少 TLB miss,适合大量连续访问内存的负载,但页表与碎片开销大,且与某些依赖 4K 的用户态/工具不兼容。工程取舍:追求 TLB 效率与大内存带宽选 64K,兼容性与灵活性选 4K,平衡选 16K。页大小需在编译/启动时确定,影响内核与用户空间 ABI。

页大小是"TLB 效率 vs 内存利用"的权衡。大页提升 TLB 覆盖但增加碎片与页表开销,理解取舍才能在 arm64 上为不同负载选页大小。

#
★★

12. kernel memory leak 调试(kmemleak)

说明 kmemleak 如何用于调试内核内存泄漏,以及它的工作方式?

  • 理解 kmemleak 检测未引用的已分配内存
  • 理解 kmemleak 的扫描与报告机制
  • 理解 kmemleak 的使用与限制

kmemleak 是内核的内存泄漏检测器,用于发现内核代码中分配后未释放且无引用的内存。它通过跟踪分配点(kmalloc、kmem_cache_alloc、vmalloc 等记录分配地址与调用栈),并周期性扫描内存中是否有指向这些已分配块的指针:若一个已分配块在扫描中从未被"引用"(没有指针指向它),则判定为潜在泄漏,报告分配地址、大小与分配调用栈。使用方法:启用 CONFIG_DEBUG_KMEMLEAK 与 kmemleak 内核参数,读 /sys/kernel/debug/kmemleak 触发扫描与报告,可用 "scan"、"clear"、"dump" 等命令。kmemleak 的局限:需要开启扫描,漏检在指针被零化或回收时,且会引入一定性能开销。它适合开发与测试阶段定位内核模块泄漏,是内核内存泄漏排查的利器。

kmemleak 通过"是否有引用指针"判断泄漏。理解其扫描机制与触发/报告方式,是排查内核驱动/模块内存泄漏的系统方法。

#
★★

13. memcg 的 memory.kmem.limit 与 kernel 内存控制

解释 memcg 的 memory.kmem.limit 与内核内存控制机制?

  • 理解 memory.kmem 控制器限制内核内存
  • 理解 kmem 与用户内存的区分
  • 理解 v1 与 v2 中 kmem 控制的差异

memcg(memory cgroup)除了限制用户态内存,还通过 kmem 控制器限制内核内存(kernel memory),如内核对象、页表、内核缓存等被某 cgroup 服务产生的消耗。memory.kmem.limit_in_bytes(v1)限制该 cgroup 的内核内存上限(v2 中内核内存统一计入 memory.max),防止内核对象无限增长耗尽主机内存。内核内存与用户内存的区别:用户内存是进程可 swap 的匿名/文件页,kmem 是内核为服务分配的内核对象(不可回收或难回收)。v1 中 kmem 需单独开启且限制独立;v2 中 kmem 与用户内存统一计入 memory.max 的总额(继承了 v2 的"内存+kernel 统一"设计),不再单独设 kmem 上限,简化了模型。理解 kmem 控制对防止内核内存泄漏拖垮宿主机、以及容器内存核算很重要。

kmem 控制决定"内核为 cgroup 分配的内存是否受限"。v1 独立 kmem 限制,v2 统一计入 memory.max。理解差异是容器内存治理与内核泄漏防护的关键。

#
★★

14. zone 的分层(DMA、NORMAL、HIGHMEM)与分配策略

解释内存 zone 的分层(DMA、NORMAL、HIGHMEM)以及对应的分配策略?

  • 理解 zone 按物理地址范围分层
  • 理解各 zone 的用途与限制
  • 理解分配时的 zone 选择策略

Linux 按物理地址把内存划分为多个 zone,用于满足不同硬件/分配的地址要求。经典的分层:ZONE_DMA(低地址,约 16MB,供需要物理低地址的 DMA 设备使用)、ZONE_NORMAL(可直接映射的内核常规内存,供内核与进程使用)、ZONE_HIGHMEM(高地址,不能直接映射,需临时映射访问,32 位系统常见)。在 64 位系统 HIGHMEM 通常不存在。此外还有 ZONE_DMA32(32 位 DMA 区域)、ZONE_MOVABLE(可移动区域,供 CMA/回收)。分配策略:分配时按 GFP 标志选择 zone,如 GFP_DMA 从 DMA 区分配,普通分配从 NORMAL 起,若 NORMAL 不足可回退到更低/可用 zone;zone 之间按 fallback 顺序降级。zone 的每个都维护独立的水位、buddy 与 pcp。理解 zone 分层是理解物理地址约束与分配失败的关键。

zone 按物理地址分层以满足不同硬件约束。理解 DMA/NORMAL/HIGHMEM 的用途与分配回退顺序,是分析物理内存布局与分配失败的核心。

#
★★

15. KASAN(Kernel Address Sanitizer)的越界与 UAF 检测

解释 KASAN(Kernel Address Sanitizer)如何检测内核的越界(out-of-bounds)与 UAF(use-after-free)问题?

  • 理解 KASAN 的 shadow memory 机制
  • 理解越界与 UAF 的检测原理
  • 理解 KASAN 的编译与运行开销

KASAN(Kernel Address Sanitizer)是内核的内存错误检测工具,基于编译器插桩(GCC/Clang 的 -fsanitize=kernel-address)与 shadow memory(影子内存)机制。它为每 8 字节内存维护 1 字节的影子内存,记录该地址的可访问性(是否可读/可写)。访问内存时,KASAN 生成的检查代码会查询影子内存,若访问越界(地址不在合法的分配范围内)或访问已释放(UAF)的 region,影子内存标记为不可访问,KASAN 随即报告越界位置、访问类型与调用栈。KASAN 能检测堆越界、栈越界、全局越界、悬垂指针(UAF)、double-free 等。它需要编译时插桩,运行开销大(内存与速度),用于开发与测试阶段。相比 KMSAN(未初始化),KASAN 专门针对越界与 UAF。

KASAN 通过影子内存 + 编译插桩在每次访问时检查合法性,定位越界与 UAF。理解它与 KMSAN 的分工,是掌握内核 sanitizer 的关键。

#
★★

16. kernel 的 page allocation failure 与 OOM 处理

解释内核的 page allocation failure 与 OOM 处理机制及其区别?

  • 理解 page allocation failure 是分配失败记录
  • 理解 OOM 是杀进程释放内存
  • 理解两者触发条件与处理流程

page allocation failure 与 OOM 都是内存不足的症状,但层面不同。page allocation failure:当分配请求(如 kmalloc 大块)在多次尝试(包括 direct reclaim、compaction)后仍无法满足,且该分配不允许无限等待(如可重试标志),内核会打印 "page allocation failure" 日志,返回分配失败(如 NULL),不杀进程。OOM(out of memory):当系统内存严重不足,且无法通过回收满足,且分配必须成功(如 GFP 不可失败),内核触发 OOM killer 选择一个进程杀掉释放内存。二者的区别:allocation failure 是对"无法满足"的分配请求返回失败(可能不致命),OOM 是"必须满足"时杀进程;触发取决于 GFP 标志与是否可重试。处理:面对 allocation failure 应检查内存碎片、降低 order 请求、排查泄漏;面对 OOM 应检查 overcommit、内存压力、调整 oom_score_adj。内核日志会区分两者并给出水位、内存状态。

核心区别是"分配失败(返回错误)"与"OOM(杀进程)"。理解两者的触发条件(GFP 可重试性)与各自处理,是内核内存故障排查的基础。

#
★★

17. GFP_ATOMIC 与原子上下文,为何中断/持自旋锁时只能用不睡眠的分配标志,与 GFP_KERNEL 的语义差异如何?

解释 GFP_ATOMIC 与原子上下文,说明为何中断/持自旋锁时只能用不睡眠的分配标志,以及它与 GFP_KERNEL 的语义差异?

  • 理解 GFP_ATOMIC 不睡眠的语义
  • 理解原子上下文(中断、自旋锁)不能睡眠
  • 理解 GFP_KERNEL 可睡眠的语义

GFP flags 决定分配路径是否允许睡眠/回收。GFP_KERNEL 是普通内核分配标志,允许睡眠(可调用回收、可阻塞),只能在进程上下文(可睡眠)使用;它能把内存换出、回收 page cache,因而更可能成功,但分配路径可能阻塞。GFP_ATOMIC 用于原子上下文(中断处理、持自旋锁、softirq 等),不允许睡眠,不能调用可能阻塞的回收函数,只能从内存的紧急预留(如 min watermark 预留)或直接可用的内存分配,失败率更高、分配更保守。原因:中断上下文无法调度,持自旋锁时睡眠会导致死锁(睡眠者永远无法被唤醒或持锁者无法释放),因此必须用不睡眠的 GFP_ATOMIC。语义差异核心:GFP_KERNEL 可睡眠、可回收、可能阻塞;GFP_ATOMIC 不睡眠、不可回收、只能快速失败。选择错误(原子上下文用 GFP_KERNEL)会导致内核 BUG/sleep-in-atomic 警告。

原子上下文不能睡眠是内核基本原则,GFP_ATOMIC 正是为此设计。理解它与 GFP_KERNEL 的睡眠/回收差异,以及错误使用(sleep in atomic)的危害,是内核编程的关键。

#

18. mempolicy 的 NUMA 内存分配

解释 mempolicy(内存策略)在 NUMA 内存分配中的作用与类型?

  • 理解 mempolicy 控制 NUMA 节点选择
  • 理解各策略类型(default/bind/preferred/interleave)
  • 理解设置方式

mempolicy(mempolicy)是内核为进程控制 NUMA 内存分配节点选择的机制,通过 set_mempolicy/mbind 系统调用或 numactl 设置。主要类型:MPOL_DEFAULT(默认,按当前节点与内存分配上下文就近选择)、MPOL_BIND(强制只在指定节点分配,超限则失败或回收)、MPOL_PREFERRED(优先从偏好节点分配,其不足时回退其他节点)、MPOL_INTERLEAVE(在指定节点间交错分配,均衡带宽)、MPOL_LOCAL(严格本地节点分配)。mempolicy 影响页、page cache、栈、堆等内存的下落节点,从而决定访问局部性。mempolicy 与 CPU 亲和性(sched_setaffinity)配合,可保证"内存靠近 CPU"。NUMA 数据库等场景常通过 numactl --membind/--interleave 指定策略。理解 mempolicy 是 NUMA 内存调优的核心。

mempolicy 决定"内存分配在哪个节点",与 CPU 亲和共同决定 NUMA 局部性。理解各策略类型与设置方式,是 NUMA 性能调优的关键。

#

19. 内存碎片(fragmentation)与 compaction 策略

说明内存碎片(fragmentation)的类型与 compaction 策略如何缓解碎片?

  • 理解外部碎片与内部碎片
  • 理解 compaction 移动可移动页聚合连续块
  • 理解 compaction 的触发与代价

内存碎片分两类:内部碎片(分配的块大于请求导致块内浪费)与外部碎片(大量小的空闲块无法拼成连续大块)。外部碎片是 Linux 页分配的主要问题,尤其影响大块分配(高 order、THP)。compaction(内存规整)是缓解外部碎片的关键策略:它通过把可移动页(MIGRATE_MOVABLE,如用户页、page cache)从一片区域迁移到另一片,腾出连续的空闲块,从而满足大块分配。compaction 触发时机:分配时大块失败(同步 compaction)、后台 kcompactd 异步 compaction、或主动触发。为减少碎片,配合 MIGRATE_* 迁移类型分类(不可移动页尽量聚集,可移动页可被迁移)与 zone 分区。compaction 的代价是页面迁移的 CPU 与内存开销,若频繁触发会拖慢系统。THP 的 always 模式常因 compaction 失败而分配失败,或引发延迟。

外部碎片靠 compaction 迁移可移动页聚合,配合 MIGRATE_* 分类。理解碎片类型与 compaction 的触发/代价,是治理大页分配失败与延迟的关键。

#

20. 内核 mempool,内存紧张时如何保证原子上下文仍能分配到对象,与普通 kmalloc 的区别如何?

说明内核 mempool 在内存紧张时如何保证原子上下文仍能分配到对象,以及与普通 kmalloc 的区别?

  • 理解 mempool 预分配对象池
  • 理解 mempool 在内存紧张时的分配保证
  • 理解 mempool 与 kmalloc 的区别

mempool(memory pool)是内核提供的内存池机制,用于保证"即使内存紧张时也能分配到对象"(尤其原子上下文)。它在初始化时预分配一定数量的对象(元素)保存在池中,并记录分配函数。当内存充足时,mempool 优先从池中取对象,池空时退回普通分配(kmalloc);当内存紧张、普通分配失败时,mempool 会从池中预留的对象分配,从而保证原子上下文(如中断)也能获取对象,避免因内存不足而失败。与普通 kmalloc 的区别:kmalloc 在内存紧张时可能失败(尤其原子上下文),mempool 通过预分配池 + 预留元素保证在紧急情况下仍能提供对象,牺牲了部分内存占用(池中预留对象不参与正常回收),换取分配成功率的保证。mempool 常用于需要可靠分配的场景(如块设备 IO 请求、网络缓冲)。mempool 本身不保证对象内容的正确性,只保证"有对象可用"。

mempool 的"预分配池 + 紧急预留"是内存紧张时保证原子分配的手段。理解它与 kmalloc 的区别(保证成功率 vs 可能失败)是掌握内核可靠分配机制的关键。