arm64/mm:优化 unmap_hotplug TLB flush — 减少内存热插拔路径的 TLB 广播开销

arm64 内存子系统 · 内存热移除 vmemmap 拆除 · TLB 失效优化(块项单页刷 vs 全 TLB 广播)

💡 一句话总结

在内存热移除场景下,arm64 拆除 vmemmap 映射时每清掉一个 2MB/1GB 的块级映射项(block entry),此前被 commit 48478b9f7913 改成按块大小去刷 TLB;在不支持 TLB 范围无效化(FEAT_TLBIRANGE,ARMv8.4+)的 CPU 上,这个“大范围刷”会越过内核阈值、退化成对全部 CPU 的一次广播式全 TLB 刷(flush_tlb_all())。而一个块级映射项在页表/TLB 里本就只占一条条目,只需对块内任意一页做一次失效即可将其逐出——本补丁把 vmemmap 路径的块级 flush 从块大小(PMD_SIZE / PUD_SIZE)收窄回单页(PAGE_SIZE),在保留线性映射路径“批量刷”收益的同时,消除了非 range 支持 CPU 上的全 TLB 广播风暴。本补丁未提供基准(修复型优化);上下文数据:被修 commit 48478b9f7913 曾自报批量刷使 2GB 内存热移除的 unmap_hotplug_range() 耗时降 97%(作者自报、未独立验证)。

📋 补丁基本信息

项目内容
补丁类型优化(性能 / 回归修复)——主导为性能优化,同时修复 48478b9f7913 引入的 TLB 失效回归
状态状态(Merged)· 合入版本:Linux 7.2(git describe --contains 显示首个含此 commit 的 tag 为 v7.2-rc3)
当前版本主线单版本(无公开 lore 补丁系列可回溯,见“方案演进”数据源说明)· 合入版链接
版本演进首版,暂无演进(修复型 commit,直接合入主线;回溯未及多版本系列)
作者机构Anshuman Khandual(Arm)<anshuman.khandual@arm.com>
提交日期2026-06-26(committed,v7.2-rc3 周期内合入)
改动范围arch/arm64/mm/mmu.c,+9/-2 行,1 文件
核心函数unmap_hotplug_pmd_range() / unmap_hotplug_pud_range() / flush_tlb_kernel_range()
原始链接git.kernel.org(规范 commit 链接)
Fixes / 报告Fixes: 48478b9f7913(batched TLB flush in unmap_hotplug_range)· Reported-by: Ben Hutchings · Closes: lore 报告帖
Review / 合入Reviewed-by: David Hildenbrand (Arm) · Reviewed-by: Catalin Marinas(arm64 维护者)· Signed-off-by / 注释与 message 改写:Will Deacon(arm64 维护者)

📊 速览卡片

核心机制
块项单页刷
优化目标
去 TLB 广播
适用场景
内存热移除
特性等级
★★★
实测提升
未提供基准

特性等级依据:修复型优化、作者未给基准(提升幅度无法量化给高分);但落地零配置(纯内核改动、无特殊硬件要求)、回归风险低(回退到 48478b9f7913 之前的旧行为)、维护者已 Ack;局限是收益场景较窄(仅内存热移除路径 + 仅非 FEAT_TLBIRANGE 的 CPU 上收益显著)——综合 ★★★。

🎯 解决什么问题

背景 / 原始动机
本补丁是回归修复:它修的是 commit 48478b9f7913(“arm64/mm: Enable batched TLB flush in unmap_hotplug_range()”,2026-03) 顺带引入的性能回退。48478b9f7913 本身是一个“修复 + 优化”:当时内存热移除路径是“每清一个页表项就立刻做一次 TLB flush”,对线性映射中带 CONT 标记的块内存不满足架构约束(必须先清掉整个 contiguous 范围内的全部表项、再统一刷一次,否则后续对高地址的失效清不掉低地址的碎化 TLB 条目),且逐项刷很慢;它改为把线性映射路径的 TLB flush 批量推迟到整个拆除走完后的末尾,作者自报 unmap_hotplug_range() 在 2GB 内存热移除(KVM guest)中耗时降 97%(作者自报、未独立验证)。
但 48478b9f7913 在改动 unmap_hotplug_pmd_range()/unmap_hotplug_pud_range() 的 free_mapped(vmemmap)分支时,把块级表项的 flush 区间从单页顺手改成了块大小(PMD_SIZE/PUD_SIZE)——commit message 自述这是 “inadvertently introduced redundant TLB invalidation”(无意中引入冗余 TLB 失效),在不支持 range 失效的 CPU 上退化成全 TLB 广播。问题由 Ben Hutchings 报告(Reported-by / Closes 链接),本补丁据此修复。
系统层面:块级表项的 TLB 失效被“超额放大”成全 TLB 广播
问题在 arch/arm64/mm/mmu.c 的 unmap_hotplug_pmd_range() / unmap_hotplug_pud_range():当 vmemmap 路径(free_mapped=true)清掉一个 pmd_leaf()/pud_leaf() 块级表项后,调用 flush_tlb_kernel_range(addr, addr + PMD_SIZE)。而 flush_tlb_kernel_range()(arch/arm64/include/asm/tlbflush.h)先算 pages = (end-start) >> PAGE_SHIFT,再交给 __flush_tlb_range_limit_excess() 判定是否“超限”:
① 不支持 FEAT_TLBIRANGE 的 CPU:pages >= MAX_DVM_OPS 就全刷,而 MAX_DVM_OPS = PTRS_PER_PTE = 512(4KB 页配置)——刷一个 2MB PMD 块 = 512 页,正好 >=,于是退化成 flush_tlb_all()(全 TLB 广播);刷 1GB PUD 块 = 262144 页,同样全刷。
② 支持 FEAT_TLBIRANGE(ARMv8.4+)的 CPU:阈值是 pages > MAX_TLBI_RANGE_PAGES(约 209 万页),块大小刷不会触发全刷,但仍是 1 条 range op 对 1 条 block 条目的“超额”失效(失效区间远大于实际需要)。
核心错位在于:一个块级表项在 TLB 里只占一条 block 条目,无论刷 1 页还是刷整个 2MB/1GB,能被逐出的都是那同一条 block 条目——刷块大小是“只为清除一条条目,却把整段地址区间的 TLB 条目全部无效化”,本质是过度失效(over-invalidation)。
场景层面:大容量内存热移除时的跨核广播风暴
执行路径:memory hot remove(offline_pages() → memory_block_offline() → unmap_hotplug_range())拆除被移除内存的 vmemmap(struct page 数组的虚拟映射)与线性映射,逐层下降到最后一级。
高频触发场景:数据中心/云主机的物理内存热下线(DIMM 维护、按 QoS 归还物理页)、KVM/虚拟机内存气球回收、以及 ARM64 服务器常见的“整颗 NUMA 节点内存下线”。一次多 GB 的移除,vmemmap 里要清掉几千到几十万个块级表项。
为什么该场景踩中系统缺陷:每次清一个 PMD 块项,在非 FEAT_TLBIRANGE 的 CPU 上就触发一次 flush_tlb_all()——这条指令会向所有核广播并强制它们:DSB 等待、无效化整个 TLB(内核+用户全部地址空间)、再 ISB 同步。核数越多,广播与同步的开销越大;逐块重复几千上万次,就变成一次“广播风暴”,让整机所有核的地址翻译缓存被反复清空、后续访问靠重新走页表填充,热移除期间全局性能明显抖动。
受影响负载:内存热移除 / 热插拔(物理内存下线、虚拟机内存回收、DIMM 维护)· 为什么此特性解决此场景:把每个块项的“全 CPU 全 TLB 广播”降为“单条定向 VA 失效”,广播风暴消失,且线性映射路径的批量刷收益(+97% 的上下文数据)保持不变

🧩 核心机制

一句话机制:清掉一个块级映射项后,把 TLB 失效区间从“块大小”(2MB/1GB)收窄到“一页”。因为块级映射项在 TLB 中只对应一条 block 条目,对块内任意一页做一次失效即可命中并逐出这条条目——“单页”既是充分的,也是最小的。

从系统层面看(两个逻辑点)
① 失效区间收窄到“最小充分值”:改动只发生在 if (free_mapped) 分支——即 vmemmap 路径(内存要被释放,必须先失效 TLB 再 free)。PMD/PUD 块项各自从 addr + PMD_SIZE / addr + PUD_SIZE 改回 addr + PAGE_SIZE,注释明确写了设计理由:“Invalidating a block entry requires just a single overlapping TLB invalidation, so limit the range of the flush to a single page.”(逐出块项只需一次与其重叠的单页失效)。这不改变任何 TLB 正确性——因为块条目是 1 条,任何落在块范围内的 VA 都能命中它。
② 与“批量刷”共存,不动线性映射路径:48478b9f7913 的批量刷只适用于线性映射(!free_mapped,无页释放,可以推迟到末尾统一刷),那部分代码本补丁未触碰;vmemmap 路径因“边清边释放”必须逐项刷,本补丁只是把这项刷的量级从“超额”拉回“精确”。两类路径的 TLB 语义都保持正确。
块级映射 TLB 失效 before/after:修复前按块大小刷触发全 TLB 广播(flush_tlb_all),修复后只刷一页命中单条 block 条目
图 1:块级映射(block entry)的 TLB 失效——修复前按块大小刷触发全 TLB 广播,修复后只刷一页命中单条 block 条目
来源:基于本地内核 git(commit ff4c5a0de1f2)真实 diff + arch/arm64/include/asm/tlbflush.h 阈值逻辑绘制
步骤操作目的
1pmd_clear(pmdp) / pud_clear(pudp) 清掉块级表项从页表中移除 2MB/1GB 映射
2flush_tlb_kernel_range(addr, addr + PAGE_SIZE)只失效 1 页(=1 条 TLBI 指令),命中并逐出那条 block 条目
3(对比修复前)addr + PMD_SIZE/PUD_SIZE按 512/262144 页计算,非 range CPU 上触发 flush_tlb_all() 全广播 ← 被消除的冗余
4free_hotplug_page_range() 释放 vmemmap 页失效完成后安全释放内存(vmemmap 路径语义不变)
关键代码片段:块项失效区间从块大小收窄为单页
@@ unmap_hotplug_pmd_range()
 			if (free_mapped) {
 				/* CONT blocks are not supported in the vmemmap */
 				WARN_ON(pmd_cont(pmd));
-				flush_tlb_kernel_range(addr, addr + PMD_SIZE);
+				/*
+				 * Invalidating a block entry requires just
+				 * a single overlapping TLB invalidation,
+				 * so limit the range of the flush to a single
+				 * page.
+				 */
+				flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
 				free_hotplug_page_range(pmd_page(pmd),
 							PMD_SIZE, altmap);
 			}

@@ unmap_hotplug_pud_range()
 		if (pud_leaf(pud)) {
 			pud_clear(pudp);
 			if (free_mapped) {
-				flush_tlb_kernel_range(addr, addr + PUD_SIZE);
+				/* See comment in unmap_hotplug_pmd_range(). */
+				flush_tlb_kernel_range(addr, addr + PAGE_SIZE);
 				free_hotplug_page_range(pud_page(pud),
 							PUD_SIZE, altmap);
 			}

▲ 这段改动是机制成立的关键:flush_tlb_kernel_range() 的失效成本由区间内页数决定——2MB 区间(512 页)在非 FEAT_TLBIRANGE 的 CPU 上正好撞上 pages >= MAX_DVM_OPS(512) 的全刷阈值,退化成全 TLB 广播;而 1 页区间永远走单条定向 VALE1IS。因为块项是单条 block 条目,1 页失效的 TLB 正确性与 2MB 失效完全等价,这就是“只刷一页”成立的依据。PMD 与 PUD 两处对称修改,注释(Will Deacon 改写)把这条架构知识固化下来,避免后人再次踩坑。

📈 性能影响

提升角度(方法论分类)
主类:off-CPU 跨核同步开销(TLB 一致性广播)——flush_tlb_all() 是向全部 CPU 广播的全 TLB 失效(TLBI VMALLE1IS + DSB/ISB 同步),每个核在广播到达时都要暂停执行、清空整 TLB、等同步完成;而单页失效只是一条按 VA 的 VALE1IS,广播成本低一个量级且无需全表清空。按 Brendan Gregg 的 on-CPU vs off-CPU 视角,本补丁削减的是“内存热移除时各核的等待/同步时间”,属于 off-CPU 广播开销的减少。
次类:开销降低(消除冗余工作)——把“逐块全表广播”降为“逐块单条定向失效”,消除的是 48478b9f7913 顺带引入的过度失效(over-invalidation)冗余。
受益场景(解读(AI 分析))
从改动路径反推(框架 A:flush_tlb_kernel_range 阈值逻辑 → vmemmap 块项逐项刷 → 内存热移除):
① 非 FEAT_TLBIRANGE 的 ARM64 CPU 收益最大(ARMv8.0–8.3,如 Cortex-A72/A53、部分较老服务器核):修复前每清一个块项就全 TLB 广播一次,修复后降为单条定向失效,广播次数从“逐块一次”归零。
② 核数越多、移除容量越大,收益越显著:全 TLB 广播的成本随核数近似线性增长(要同步的核更多),且热移除容量越大、块项越多,重复次数越多。
③ 支持 FEAT_TLBIRANGE 的 CPU 收益有限:这类 CPU 上块大小刷本来也走 range op,不触发全刷;修复主要是减少 TLBI 操作数(1 条 range op vs 1 条单页 op)与失效区间,幅度较小。
内存热移除是管理员低频操作而非常驻热路径,因此本补丁的收益是“操作发生时避免全局抖动”,不是常驻吞吐提升。
实测数据
本补丁未提供基准。commit message 只描述问题(非 range 支持 CPU 上的不必要广播),无任何前/后性能数字。
上下文数据(与本补丁同属 unmap_hotplug_range 优化、非本补丁自身):被修 commit 48478b9f7913 自报 “The time spent executing unmap_hotplug_range() improved 97% measured over a 2GB memory hot removal in KVM guest”(作者自报、未独立验证)——那是线性映射批量刷的收益,本补丁在此基础上修复 vmemmap 路径的回归,但作者未单独量化。
逻辑分析(解读(AI 分析)):修复把每个块项的失效从“全 CPU 全 TLB 广播”(广播 + 全表清空 + 同步,成本随核数增长)降为“单条定向 VA 失效”,单次成本相差可达数量级;但整体收益需乘以热移除容量中的块项数量与系统核数,本报告不编造具体百分比。

🔄 方案演进

本补丁是单版本回归修复(无 v1→v2 系列演进),但其“成因 → 报告 → 修复”的脉络值得记录(基于本地内核 git 提交链,真实可查):

回归时间线(旧行为 → 批量刷 → 本修复)
bbd6ec605c0f(“arm64/mm: Enable memory hot remove”):引入 unmap_hotplug_*_range(),当时块级表项清掉后即做单页失效(“One TLBI should be sufficient here as the PMD_SIZE range is mapped with a single block entry.”)——本补丁修复回退到的正是这个旧行为。
48478b9f7913(2026-03-09,batched TLB flush):为修 CONT 块架构约束 + 提速,把线性映射路径改为批量刷;但在 vmemmap(free_mapped)分支误将块项失效区间从单页放大到块大小,引入回归。
Ben Hutchings 报告(lore 报告帖,Closes 链接):指出该回归。
ff4c5a0de1f2(本补丁,2026-06-26):vmemmap 块项失效区间收窄回单页,附扩大注释;经 Catalin Marinas、David Hildenbrand 审核,Will Deacon 改写注释与 commit message 后合入 v7.2-rc3。
设计权衡 / 讨论推进
从 commit message 的署名链可见讨论形态:Will Deacon 以 [will: Reword comments and commit message] 承接最终改写,说明 arm64 维护者在 review 中把“为什么只需一页”的架构理由固化成了注释(“require just a single overlapping TLB invalidation”),避免后人再犯“按块大小刷”的错误。
数据源说明:本环境 lore MCP 查询超时不可用,未能回溯该 commit 对应的补丁系列邮件线程(v1/v2 演进与完整 review 讨论无法独立确认)。上述演进/讨论信息来自本地内核 git(commit message 的 Fixes: / Reported-by: / Closes: / Reviewed-by / Signed-off-by 链 + git log 提交链),是合入主线的权威记录。

⚠️ 风险与局限

收益成立的前提
① 非 FEAT_TLBIRANGE 的 CPU 上收益才显著(ARMv8.0–8.3);支持 TLB range(ARMv8.4+,如 Neoverse N2/V1)的 CPU 上,块大小刷本就不触发全刷,本补丁只是把 1 条 range op 减为 1 条单页 op、失效区间收窄,提升幅度小。
② 仅内存热移除路径受益;常驻负载(无热移除操作)行为不变(no-op)。
③ 正确性前提:单页失效能逐出整条 block 条目,依赖“block 条目是 1 条、对块内任意 VA 命中”的 arm64 架构事实——这正是注释里固化的假设,对标准 block 映射恒成立。
生产落地影响
纯内核改动(arch/arm64/mm/mmu.c 两处常量),无 Kconfig 开关、无 sysctl、无默认值/迁移路径问题,随主线 7.2 发布即可用。行为回退到 48478b9f7913 之前的旧行为,对依赖“块级表项失效后立刻可释放 vmemmap 页”的组件完全兼容。运维可观测性:热移除期间若仍有全 TLB 广播,可从 perf stat 的 TLB/tlb 事件或 DVM 同步开销察觉;修复后应观察到逐块广播消失。
生态 / 兼容性
仅影响 arm64 架构,无用户态 ABI/工具链影响。与 48478b9f7913 的批量刷(线性映射路径)正交,不破坏该系列在 KVM guest 中测得的 97% 提升(作者自报,未独立验证)。与 vmem_altmap(设备内存的备用映射)路径共用 free_hotplug_page_range(),语义不变。
review 质疑(可追溯项)
lore MCP 本环境不可用,完整 review 讨论无法回溯。commit message 署名链显示 David Hildenbrand (Arm)、Catalin Marinas(arm64 维护者)均 Reviewed-by,Will Deacon(arm64 维护者)改写注释与 message——说明经维护者审核通过。未发现公开 NACK;修复方向(回退到旧行为)明确且低风险。
严重度:MINOR · 落地场景:最需要关注的是“不要在支持 FEAT_TLBIRANGE 的 CPU 上期待显著收益”;整体是低风险的回归修复,无配置门槛、无破坏性变化。

🔗 交叉引用

📌 关联工作 / 系列相关补丁
arm64/mm: Enable batched TLB flush in unmap_hotplug_range()(48478b9f7913) — 被本补丁修复的回归来源;自报 2GB 热移除 unmap_hotplug_range() 耗时降 97%(作者自报、未独立验证)
arm64/mm: Enable memory hot remove(bbd6ec605c0f) — 内存热移除路径的引入者,原始单页失效行为所在
Ben Hutchings 报告帖(lore,本补丁 Closes 链接) — 回归的独立报告来源
本补丁 ff4c5a0de1f2(git.kernel.org) — arm64/mm: Optimize TLB flush in unmap_hotplug_[pmd|pud]_range()
内核 cachetlb 文档(Documentation/core-api/cachetlb.rst) — flush_tlb_all / flush_tlb_kernel_range 语义与 TLB 失效 API 约定
⚠️ 免责声明

本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。