发布时间:2026/6/15 17:07:14
Linux memory compaction内存压缩触发条件 Linux Memory Compaction 触发条件mm/compaction.c 中的 compact_zone() 和 isolate_migratepages() 是内存压缩的核心路径但触发这些函数的入口点散布在整个内存管理子系统中。理解触发条件是诊断碎片化问题的前提。c// mm/compaction.cenum compact_result {COMPACT_NOT_NEEDED, // 无需压缩COMPACT_SKIPPED, // 因水印/同步原因跳过COMPACT_DEFERRED, // 被退避延迟COMPACT_CONTINUE, // 需要继续扫描COMPACT_SUCCESS, // 迁移回收成功COMPACT_PARTIAL_SKIPPED, // 部分跳过COMPACT_COMPLETE, // 扫描完成但未满足需求COMPACT_NO_SUITABLE_PAGE, // 无可迁移页};触发入口try_to_compact_pages()这是所有直接内存压缩的汇聚点定义在 mm/compact.c:2726。调用栈的典型路径是 __alloc_pages_slowpath() - __alloc_pages_direct_compact() - try_to_compact_pages()。关键参数是 enum compact_priority它决定了压缩的激进程度和耗时上限。c// include/linux/compaction.henum compact_priority {COMPACT_PRIO_SYNC_FULL, // 全同步可等待写回COMPACT_PRIO_SYNC_LIGHT, // 轻量同步不等待写回COMPACT_PRIO_ASYNC, // 异步模式仅扫描MIGRATE_ASYNCCOMPACT_PRIO_INTERRUPTIBLE, // 可中断的同步用于用户触发};优先级越低数值越大越异步在初始分配尝试中使用 COMPACT_PRIO_ASYNC。当异步失败且 __GFP_DIRECT_RECLAIM 已设置时才升级到 COMPACT_PRIO_SYNC_LIGHT 或 COMPACT_PRIO_SYNC_FULL。这种优先级递增策略直接影响延迟异步压缩在单个 zone 上不超过 HPAGE_PDEF_NR 页的扫描量而同步压缩会扫完整个 zone。触发条件一THP 分配的同步压缩mm/huge_memory.c 中khugepaged 内核线程负责后台透明大页折叠。但首次分配路径完全不同——alloc_hugepage_direct_ksm() 和 __handle_mm_fault() 中的 do_huge_pmd_anonymous_page() 在 gfp 掩码中附加 __GFP_COMPACT_MASKc// mm/huge_memory.cstatic inline gfp_t alloc_hugepage_gfp_flags(gfp_t gfp, struct vm_area_struct *vma){if (vma_is_anonymous(vma) || vma_is_shmem(vma))return gfp | __GFP_COMPACT_MASK;return gfp;}这里 __GFP_COMPACT_MASK 展开为 (__GFP_COMPACT | __GFP_NORETRY | __GFP_NOWARN)。关键语义是THP 分配必然触发直接内存压缩但不会触发直接回收。__GFP_NORETRY 确保不会引起 oom killer 或长时间的 direct reclaim。这意味着当页面碎片化严重时THP 分配在 compaction 失败后快速返回 fallback 到 4K 页不会给压缩太多机会在碎片化严重的系统中 THP 命中率会持续走低。触发条件二khugepaged 后台折叠khugepaged 是独立于直接分配路径的扫描线程。其主循环在 khugepaged_scan_mm_slot() 中扫描进程地址空间对符合条件的 VMA 调用 khugepaged_scan_pmd() 进行 collapse_huge_page()。该函数会调用 __collapse_huge_page_isolate() 来锁定和隔离目标页随后调用 __collapse_huge_page_copy() 完成页复制。c// mm/khugepaged.cstatic void collapse_huge_page(struct mm_struct *mm,unsigned long address,struct page **hpage,int node, int referenced){// ...result __collapse_huge_page_isolate(vma, address, pte);if (unlikely(result))goto out;// ...result khugepaged_scan_pmd(mm, vma, address, hpage, node);}khugepaged 的扫描频率由 khugepaged_scan_sleep_millisecs默认 10000 ms和 khugepaged_alloc_sleep_millisecs默认 10000 ms控制。Critical pointkhugepaged 在 collapse 前调用 __alloc_hugepage() 分配一个 order-9 的 中间 页如果此时系统内存不足该分配同样会触发 try_to_compact_pages()然后才进入实际的 PMD 映射构建。这个中间分配在内存压力下会递归压实但此时进程持有 mmap_lock 的写锁如果 compaction 的 direct_scan 耗时过长会造成同一进程组中其他线程的严重延迟——这在数据库等重度多线程场景中是一个已知问题。触发条件三水印驱动的 kcompactd 唤醒kcompactd 是每个内存节点独立的内核线程per-node定义在 mm/compaction.c。其唤醒路径在 __alloc_pages_slowpath() 中当 zone 的水印低于 __GFP_KSWAPD_RECLAIM 阈值并且分配无法满足时c// mm/page_alloc.cstatic inline bool zone_watermark_fast(struct zone *z, unsigned int order,unsigned long mark, int highest_zoneidx,gfp_t gfp_mask){long free_pages zone_page_state(z, NR_FREE_PAGES);long cma_pages 0;// 快速路径检查 O(1)if (free_pages min z-lowmem_reserve[highest_zoneidx])return false;// ...}当快速水印检查失败后进入 __alloc_pages_slowpath()在调用 __alloc_pages_direct_compact() 之前会先检查是否需要唤醒 kcompactdc// mm/page_alloc.cstatic inline void wake_all_compacters(void){struct zone *zone;struct zoneref *z;struct zonelist *zonelist node_zonelist(first_online_node, gfp_mask);for_each_zone_zonelist_nodemask(zone, z, zonelist, ac-highest_zoneidx,ac-nodemask) {if (compact_needs_retry(ac, zone))wakeup_kcompactd(zone, order, ac-nodemask);}}kcompactd 在 kcompactd_do_work() 中使用 COMPACT_PRIO_SYNC_LIGHT 优先级运行压缩——这意味着它不会等待页面的 writeback 完成但会尝试所有 MIGRATE_ASYNC 标志的页块。kcompactd 与 kswapd 协作的一个典型边界场景kswapd 回收的页可能正好与 compaction 要迁移的页冲突。kcompactd 通过 zone-compact_considered 和 zone-compact_defer_shift 实现退避机制防止在碎片化严重但每次都失败的区域上做无用功。c// mm/compaction.cstatic void kcompactd_do_work(pg_data_t *pgdat){// ...for (zone pgdat-node_zones; zone pgdat-node_zones MAX_NR_ZONES; zone) {// 检查是否需要压缩if (compaction_deferred(zone, cc.order))continue;// 执行压缩status compact_zone(cc, capc);// 根据结果决定退避if (status COMPACT_COMPLETE || status COMPACT_NO_SUITABLE_PAGE)defer_compaction(zone, cc.order);else if (status COMPACT_PARTIAL_SKIPPED)reset_defer_compaction(zone, cc.order);}// ...}defer_compaction 将 zone-compact_considered 归零并增加 zone-compact_defer_shift最大退避到 2^6 即 64 次扫描周期才重试一次。这个非线性退避在碎片化刚出现时避免了频繁的无效唤醒但代价是如果碎片化突然缓解例如其他进程释放了大块连续内存kcompactd 仍然需要等待退避期结束才能发现。触发条件四内存热插拔的显式压缩内存热移除路径 memory_block_offline() 在移除一个内存块前调用 soft_offline_and_free() 或内核直接调用 alloc_contig_range()。这些路径在 mm/memory_hotplug.c 和 mm/page_alloc.c 中显式调用 compactionc// mm/page_alloc.cint alloc_contig_range(unsigned long start, unsigned long end,unsigned migratetype, gfp_t gfp_mask){// ...ret __alloc_contig_migrate_range(cc, start, end);if (ret)goto done;// ...}在 __alloc_contig_migrate_range() 内compaction 运行在 COMPACT_PRIO_SYNC_FULL 优先级——因为是显式操作调用者可以容忍延迟。这里有一个重要的锁竞争isolate_freepages() 会持有 zone-lock而 CONFIG_COMPACTION 下的 migrate_pages() 涉及 lru_lock 和 page 锁。如果被迁移的页面在 mmap 路径中被同时访问do_pte_write 和 compaction 之间可能在 page-_mapcount 上产生竞争导致 migrate_page_copy() 丢失访问计数更新。触发条件五fragmentation index 和 proactive compaction/proc/sys/vm/compaction_proactiveness 控制 proactive compaction 的激进程度。内核在 kcompactd 循环中根据 fragmentation index 判断是否需要主动压缩c// mm/vmstat.cint fragmentation_index(struct zone *zone, unsigned int order){unsigned long flags zone-flags;if (!(flags ZONE_RECLAIM_ACTIVE))return 0;return (zone-present_pages - zone-free_area[order].nr_free) * 100 /max(zone-present_pages, 1UL);}当 fragmentation_index 超过 sysctl_compaction_proactiveness范围 0-100默认 20kcompactd 会被额外唤醒执行背景压缩。这个阈值直接影响了系统在低负载时的 CPU 消耗设为 100 意味着只有在极端碎片化时才压缩适合延迟敏感型负载设为 0 则完全禁用主动压缩仅在 direct compaction 和显式请求时触发。但 proactive compaction 存在一个隐患它在 kcompactd 上下文中扫描 LRU 页进行隔离时会修改 lruvec与 kswapd 的扫描路径产生干扰。如果两个内核线程同时扫描同一个 lruvecisolate_migratepages_block() 中的 spin_trylock 会频繁返回失败导致实际压缩吞吐量大幅下降。c// mm/compaction.c: isolate_migratepages_block()static int isolate_migratepages_block(struct compact_control *cc,unsigned long blockpfn, unsigned long end_pfn){// ...if (!compact_lock_irqsave(pgdat-lru_lock, flags, cc))return 0;// ...// 遍历 PTE 时检查 page_count 和 page_mappingpage pfn_to_page(low_pfn);if (__PageMovable(page))goto isolate;if (!get_page_unless_zero(page))goto isolate_fail;if (!TestClearPageLRU(page))goto isolate_fail_put;// ...}上面的代码片段展示了一个关键竞态get_page_unless_zero() 之后、TestClearPageLRU() 之前的窗口期。如果另一个 CPU 正在做 page reclaim该页可能被隔离到 swap cache 并从 LRU 移除。TestClearPageLRU 失败后 compact 无法继续但 get_page_unless_zero 已经增加了引用计数必须通过 put_page 释放否则造成内存泄漏。compaction 的失败处理路径 isolate_fail_put 正确执行了 put_page但在 CONFIG_DEBUG_VM 下会触发 VM_BUG_ON_PAGE(!PageLRU(page)) —— 由于 LRU 标志已被 reclaim 线程清除可能在调试内核中误触发断言。为此内核引入了 __isolate_lru_page_prepare() 做预检查c// mm/compaction.cstatic __always_inline int __isolate_lru_page_prepare(struct page *page){if (PageLRU(page) (__PageMovable(page) || !page_count(page)))return -EBUSY;if (unlikely(PageUnevictable(page)))return -EBUSY;return 0;}触发条件六memory cgroup 限界引发的压缩当 cgroup 的内存上限memory.max被触发时mem_cgroup_force_empty() - try_charge() - mem_cgroup_reclaim() 路径中compaction 作为回收策略的一部分被调用。在 mm/memcontrol.c 中mem_cgroup_soft_limit_reclaim() 直接调用 try_to_compact_pages()其传入的 gfp_mask 包含 __GFP_NORETRY 和 __GFP_COMPACT。这里一个不常被注意的细节是cgroup 维度的 compaction 作用于 cgroup 所在节点的所有 zone而不是单独一个 cgroup 的页面——这意味着进程 A 在 cgroup X 中分配内存因 cgroup 软限触发的 compaction 可能迁移进程 Bcgroup Y 中的页面因为 compaction 的 isolate_migratepages 只按 PFN 扫描不检查 page_cgroup。这在多租户场景中是一个隔离性隐患cgroup memory.low 保护对此完全无效。触发条件七sync_compact 用户空间接口通过 /proc/sys/vm/compact_memory 写入任意值触发 __sysfs 路径调用 compact_nodes()c// kernel/sysctl.cstatic int sysctl_compaction_handler(struct ctl_table *table, int write,void __user *buffer, size_t *length, loff_t *ppos){if (write)compact_nodes();return 0;}compact_nodes() 在所有节点上执行 COMPACT_PRIO_SYNC_FULL 压缩这是一个阻塞操作会遍历每个 zone 的每个页块。对于大内存系统TB 级此操作耗时可达分钟级别且期间持有 zone-lru_lock 和 zone-lock 的自旋锁变体会导致所有分配路径的短暂停顿。因此生产环境通过 cron 定期写入 compact_memory 的做法应当谨慎并发写入 compact_memory 时compact_nodes() 使用互斥锁 kcompactd_run 来串行化但同一时刻其他内核线程的 direct compaction 不会等待这个互斥锁——它们会穿透并和 compact_nodes() 并行执行导致 CPU 在 page migration 的两个 memcpy 竞争 cache line。触发条件八CMA 区域的隔离与压缩Contiguous Memory Allocator (CMA) 使用的页在分配时必须从 MIGRATE_MOVABLE 区域隔离出来。cma_alloc() - alloc_contig_range() 路径调用 compaction 来腾出连续空间其优先级固定为 COMPACT_PRIO_SYNC_FULL。与普通 compaction 不同CMA 区域的 compaction 使用 cc-mode MIGRATE_SYNC 强制同步模式即使传入的是异步请求也自动升级为同步——这是为了避免 CMA 分配因异步压缩失败而回退到不可靠的路径。c// mm/compaction.cstatic enum compact_result __compact_fallback(struct compact_control *cc){if (cc-mode MIGRATE_SYNC || cc-mode MIGRATE_SYNC_LIGHT)return COMPACT_CONTINUE;// CMA area always syncif (cc-alloc_contig)return COMPACT_CONTINUE;return COMPACT_SKIPPED;}CMA compaction 的一个典型问题是 deadlock 场景如果 cma_alloc 在持有 cma_mutex 时触发 compaction而 compaction 尝试迁移的某个页面正在被一个持有 mmap_lock 且等待 CMA 分配的进程使用就形成了 ABBA 死锁。内核通过 __gfp_reclaim 掩码中的 PF_MEMALLOC 标志来避免cma_alloc 设置 PF_MEMALLOC 使当前线程不会被计入内存回收水位确保 compaction 不会因等待自身而停顿。但这又引入了新的问题——PF_MEMALLOC 下的分配可能突破 memory.min 保护在 cgroup 环境下绕过限流。最后所有触发条件的共同归约点是 compact_zone()。该函数的执行时间由扫描页面数量决定c// mm/compaction.cstatic unsigned long compact_zone(struct compact_control *cc,struct capture_control *capc){// ...while ((ret compact_finished(cc)) COMPACT_CONTINUE) {unsigned long nr_migrate, nr_freepages;int err;// 隔离空闲页isolate_freepages(cc);// 隔离迁移页nr_migrate isolate_migratepages(cc);// 执行迁移err migrate_pages(cc-migratepages, compaction_alloc,NULL, cc-mode, cc-mode MIGRATE_SYNC ?MIGRATE_SYNC : MIGRATE_ASYNC);// ...if (err) {putback_movable_pages(cc-migratepages);cc-nr_migratepages 0;}}// ...}性能的关键观察点compact_zone() 的每次迭代都遍历 freelist 和 LRU list在碎片化严重时 freelist 被拆散成大量小块zone-free_area[order] 的遍历开销随碎片程度线性增长。当 concurrent direct compaction 在多个 CPU 上运行时zone-lock 的争用急剧上升。这在 SPECjbb 等有大量并发短生命周期分配的基准测试中已被观察到导致吞吐量下降 15% 以上。内核 5.15 引入了 pageblock_skip 缓存来减少重复扫描但 cache 的失效竞争即多个 kcompactd 实例同时更新 zone-compact_cached_free_pfn仍然存在。c// mm/compaction.cstatic bool suitable_migration_target(struct compact_control *cc,struct page *page){if (cc-ignore_skip_hint)return true;// 缓存跳过位图检查if (get_pageblock_skip(page))return false;return true;}skip bit 缓存的更新和读取没有原子保护——在 compact_zone() 的扫描过程中另一个 kcompactd 线程可能在相邻 zone 完成压缩后重置了 skip bit而当前扫描线程仍使用过期的缓存数据多扫描了数十个 pageblock。这个读-修改-写窗口是已知的 non-fatal 但是影响性能的因素。内核社区对此的修复方案引入 zone-compact_skip_mutex因为锁粒度过大被 Linus 拒绝目前仍依赖调用方在 scan_control 中自行判断是否需要重扫。

相关新闻

2026/6/15 17:07:14

Linux memcg_wb_domain脏页写回domain与cgroup关联

Linux memcg_wb_domain脏页写回domain与cgroup关联memcg_wb_domain是memory cgroup与writeback(回写)子系统之间的关键桥梁。在cgroup v2环境下,每个memory cgroup拥有独立的脏页写回domain,这使得内核可以基于cgroup粒度控制脏页比例、回写阈值和回写速…

2026/6/15 17:07:14

3个关键升级让魔兽争霸3在现代电脑上焕发新生

3个关键升级让魔兽争霸3在现代电脑上焕发新生 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 你是否还在为魔兽争霸3的卡顿、黑边、地图限制而烦恼&am…

2026/6/15 17:07:14

Linux may_open open_flag验证与O_TRUNC截断处理

Linux may_open是VFS层打开文件路径上的权限和标志验证关卡,位于fs/namei.c。它在do_dentry_open之前执行,负责对用户传入的open_flags做语义合规性检查,以及O_TRUNC截断条件的判定。任何不满足内核策略的flag组合都会在此被拒绝。 // fs/n…

2026/6/15 18:07:15

C语言进阶的书籍推荐

很多人看完学校教材、入门教程,只能写简单循环、分支,一碰到指针、动态内存、复杂工程代码就各种崩溃,代码漏洞多、看不懂底层逻辑。如果想要系统进阶 C 语言,业内公认三本经典进阶书籍,各司其职,一套补齐 …

2026/6/15 18:07:15

如何3分钟永久激活IDM:完整操作指南与最佳实践

如何3分钟永久激活IDM:完整操作指南与最佳实践 【免费下载链接】IDM-Activation-Script-ZH IDM激活脚本汉化版 项目地址: https://gitcode.com/gh_mirrors/id/IDM-Activation-Script-ZH 还在为Internet Download Manager(IDM)的30天试…

2026/6/15 18:07:15

如何用Maccy剪贴板管理器提升你的macOS工作效率

如何用Maccy剪贴板管理器提升你的macOS工作效率 【免费下载链接】Maccy Lightweight clipboard manager for macOS 项目地址: https://gitcode.com/gh_mirrors/ma/Maccy Maccy是一款专为macOS设计的轻量级剪贴板管理器,能够智能记录您的复制历史,…

2026/6/15 18:07:15

Anthropic探讨AI自进化,四位科学家智源大会共商人类与AI共生之道

01 什么是AI自进化?大概一周前,正在筹备上市的Anthropic在官方博客更新文章探讨“AI自进化”,指出AI参与构建更强模型比预想更快。AI自进化并非新技术,科学家既恐惧又利用其能力。田渊栋创立的RSI获6.5亿美元融资。在智源大会上&a…

2026/6/15 17:07:14

Linux mlock锁定页面mlock_all与mlockall系统调用

mlock 与 mlockall:页面锁定机制的内核实现 mm/mlock.c 中 mlock 的入口点是 SYSCALL_DEFINE2(mlock),经过 do_mlock 到达核心函数 mlock_fixup。mlock_fixup 的原型为: c static int mlock_fixup(struct vm_area_struct *vma, struct vm_a…

2026/6/15 16:07:12

MuleSoft+LLM企业级AI编排:打通语义鸿沟与系统韧性

1. 项目概述:当企业级集成平台遇上大语言模型,不是叠加,而是重铸工作流 “AI Orchestration in Action: How MuleSoft and LLMs Fuel the Future of Enterprise AI”——这个标题里藏着一个正在发生的、静默却剧烈的范式转移。它说的不是“用…