arm64 dsb 指令可以指定同步的 CPU 核范围吗
arm64 DSB 指令:能否指定 CPU 核范围?
直接结论
不能。 DSB 指令的操作数只有”共享域(shareability domain)”级别(SY / ST / ISH / ISHST / NSH / NSHST / OSH / OSHST),它没有、也不可能有 CPU 位图(cpumask)参数。要”只同步特定 CPU 核范围”,必须在软件层面借助 IPI(核间中断)机制实现,架构指令层面不存在单条指令做到这件事的途径。
一、架构层面:DSB 的语义边界
ARMv8 ARM 中 DSB 编码为 DSB <option>,option 只编码两类信息:
| 选项 | 作用域 | 语义 |
|---|---|---|
NSH / NSHST |
Non-shareable | 仅当前 PE 自身(不考虑与其它 PE 的共享关系) |
ISH / ISHST |
Inner Shareable | 等待本 PE 的内存访问对 Inner Shareable 域内所有 PE 可见 |
OSH / OSHST |
Outer Shareable | 等待对 Outer Shareable 域内所有 PE 可见 |
SY / ST |
Full system | 整个系统所有 PE 及观测组件 |
关键点:
- 域是系统静态划分的,由互连拓扑(如 CMN、CCN)、MPIDR 亲和层级、系统寄存器配置决定。典型例子:多 die(如鲲鹏 2P 双 die)平台上,每个 die 通常是一个 Inner Shareable 域。软件不能在运行期把”CPU 2、CPU 5、CPU 7”动态划成一个域。
dsb(ish)的语义是”当前 Inner Shareable 域内广播等待“——是”域内全体”,不是”域内子集”。这与 x86 的mfence/lock是”处理器序 + 全系统”模型不同,也与IPI + barrier的精确 CPU 子集模型不同。
内核中的宏定义印证了这一点(arch/arm64/include/asm/barrier.h:29):
#define dsb(opt) asm volatile("dsb " #opt : : : "memory")
opt 只能是编译期常量字符串,不可能携带运行时 CPU 列表。
二、软件层面:如何实现”指定 CPU 核范围”的同步
架构没有原生能力,软件有三种可落地的模式(按精确度从高到低):
模式 1:IPI + 目标 CPU 上执行屏障(最精确)
通用机制是 smp_call_function_many(mask, func, ...)(kernel/smp.c:912):
void smp_call_function_many(const struct cpumask *mask,
smp_call_func_t func, void *info, bool wait)
- 向
mask中每个 CPU 发送 RESCHED/CALL_FUNCTION IPI; - 目标 CPU 在中断上下文执行
func,func内可执行dsb(ish)、tlbi等; wait=true时调用方原子等待全部完成——这就是”精确到任意 CPU 子集的同步”。
典型应用场景:KVM 的 stage-2 页表维护。__kvm_tlb_flush_vmid_ipa_nsh(arch/arm64/kvm/hyp/nvhe/tlb.c:177)特意使用 NSH 变体的 TLBI,而不是 IS 变体——因为该操作已经通过 smp_call_function/kvm_call_hyp 把执行范围精确限定在持有该 VMID 的 CPU 集合上,再做 Inner Shareable 广播纯属浪费:
// arch/arm64/kvm/hyp/nvhe/tlb.c (节选语义)
// 操作范围 = 明确的一组 vCPU 所在的 pCPU
// TLBI 用 __tlbi(ipas2e1is/ipas2e1) 之后只需 dsb(nsh) 收尾
模式 2:TLBI 共享域广播 + DSB 域内等待(内核默认路径)
内核常规 TLB flush(如 flush_tlb_mm,arch/arm64/include/asm/tlbflush.h:376)走的是”TLBI 的 is 变体 + dsb(ish) 收尾”:
static inline void flush_tlb_mm(struct mm_struct *mm)
{
unsigned long asid;
dsb(ishst);
asid = __TLBI_VADDR(0, ASID(mm));
__tlbi(aside1is, asid); // inner shareable 广播
__tlbi_user(aside1is, asid);
__tlbi_sync_s1ish(mm); // dsb(ish) 等待广播完成
}
这里”广播给谁”由 TLBI 指令的 is 后缀 + 硬件共享域决定,软件只能选域粒度,不能选 CPU 位图。其正确性依据是:TLBI 广播域 ≥ 任何可能缓存该 ASID 的 PE 集合。TLBF_NOBROADCAST 标志存在时退化为 dsb(nshst)(tlbflush.h:576-579),说明内核在已知范围更窄时主动放弃域级广播,这正是模式 1/3 配合使用时的优化点。
模式 3:位图延迟执行(避免全系统同步的最佳实践)
arch/arm64/mm/context.c 的 ASID rollover 是”按 CPU 位图延迟”的教科书案例:
// arch/arm64/mm/context.c:120-133
static cpumask_t tlb_flush_pending;
static void flush_context(void)
{
...
/* Queue a TLB invalidation for each CPU to perform on next context-switch */
cpumask_setall(&tlb_flush_pending);
}
而每个 CPU 在 switch_mm 时自检(context.c:255):
if (cpumask_test_and_clear_cpu(cpu, &tlb_flush_pending))
... // 本 CPU 自己执行本地 TLB flush + dsb(nsh)
这个模式把”一次全系统同步”拆成”各 CPU 在各自方便的时刻(上下文切换)自行执行本地维护”,消除了同步等待的串行化代价——对于你关注的鲲鹏多 die 平台,这是比”每条指令都域级广播”更可扩展的路径。
三、总结对照
| 需求 | 架构指令方案 | 软件方案 | 精确度 |
|---|---|---|---|
| 同步全体 CPU | DSB SY / TLBI xxxxis |
— | 全系统 |
| 同步当前共享域 | DSB ISH / DSB OSH |
— | 域粒度(静态) |
| 同步指定任意 CPU 子集 | ❌ 不存在 | smp_call_function_many(mask, fn) + fn 内 dsb/tlbi |
CPU 位图粒度 |
| 减少全系统同步开销 | TLBI nsh 变体 |
位图延迟执行(tlb_flush_pending 模式) |
取决于设计 |
四、实践建议(面向鲲鹏/多 die 平台)
- 能不用全系统同步就不用:多 die 平台上
dsb(ish)在跨 die 场景代价显著,优先用TLBF_NOBROADCAST+ IPI 精确到 die 内 CPU 集合; - 判断域划分:通过
MPIDR_EL1亲和级别(Aff2/Aff1)和平台互连文档确认 Inner Shareable 域边界,这是决定ISHvsNSH选择的硬件依据; - OOT 补丁注意:若在 OOT 驱动中自造”多核同步”,不要假设
dsb(ish)覆盖了你想要的 CPU——它只覆盖当前域;跨域同步必须走 IPI(smp_call_function_many已处理跨域投递)。参考来源
本站内容均由 AI 基于公开知识辅助生成,仅供学习参考,请勿直接引用作为依据。作者不对信息的准确性、完整性及适用性作保证,亦不对因使用本站内容产生的任何损失承担责任。