下面详细解析 Linux KVM RISC-V 中kvm_riscv_nacl_enable()的原理、源码和在H-ext 仿真中的意义。
一、NACL 是什么(前置理解)
NACL = SBI Nested Acceleration Extension(EID0x4E41434C "NACL")
它只在嵌套虚拟化场景有意义:当 KVM(L1 Hypervisor)自己作为 Guest 跑在另一个 L0 Hypervisor 上时,H-ext CSR 访问 /HFENCE/HLV/HSV原本要 VM-Exit 到 L0 逐条模拟。NACL 通过Per-CPU Shared Memory(共享内存页) 让 L1 KVM 批量写入 H-ext CSR 值和 HFENCE 请求,再一次性调 SBI 同步,减少 trap 次数。
⚠️如果你的仿真器是直接跑 KVM 在裸硬件/H-ext 上(非嵌套),OpenSBI 通常不实现 NACL,
kvm_riscv_nacl_available()为 false,kvm_riscv_nacl_enable()直接 return 0,无任何副作用。
二、核心数据结构(arch/riscv/kvm/nacl.c / include/asm/kvm_nacl.h)
/* 每 CPU 一个 NACL 上下文 */ struct kvm_riscv_nacl { void *shmem; /* 共享内存虚拟地址(页) */ phys_addr_t shmem_phys; /* 共享内存物理地址(页对齐)*/ }; DEFINE_PER_CPU(struct kvm_riscv_nacl, kvm_riscv_nacl); /* 静态键 —— 若 OpenSBI 不支持 NACL 则 jump 走,零开销 */ DEFINE_STATIC_KEY_FALSE(kvm_riscv_nacl_available); DEFINE_STATIC_KEY_FALSE(kvm_riscv_nacl_sync_csr_available); DEFINE_STATIC_KEY_FALSE(kvm_riscv_nacl_sync_hfence_available); DEFINE_STATIC_KEY_FALSE(kvm_riscv_nacl_sync_sret_available); DEFINE_STATIC_KEY_FALSE(kvm_riscv_nacl_autoswap_csr_available);共享内存布局(SBI 规范):
Offset 0x0000 — 0x0FFF : Scratch Space(HFENCE 队列 / SRET 参数 / Autoswap) Offset 0x1000 — end : CSR Space(1024 × XLEN,索引 = ((csr&0xc00)>>2)|(csr&0xff))三、kvm_riscv_nacl_enable() 源码解析
来自arch/riscv/kvm/nacl.c:
int kvm_riscv_nacl_enable(void) { struct kvm_riscv_nacl *nacl; /* 若 OpenSBI 未声明支持 NACL → 直接返回,不影响非嵌套场景 */ if (!kvm_riscv_nacl_available()) return 0; nacl = this_cpu_ptr(&kvm_riscv_nacl); /* * SBI 调用:SBI_EXT_NACL, SBI_EXT_NACL_SET_SHMEM * 参数:shmem_phys = 本 CPU NACL 共享内存物理地址 * flags = 0(保留) * 作用:告诉 OpenSBI(L0):"此 CPU 的 NACL shmem 现在是这张物理页" * OpenSBI 此后可从 shmem CSR Space 读取 H-ext CSR 批量值 * 并在 SYNC_CSR / SYNC_HFENCE / SYNC_SRET 时应用 */ struct sbiret ret = sbi_ecall(SBI_EXT_NACL, SBI_EXT_NACL_SET_SHMEM, nacl->shmem_phys, 0, 0, 0, 0, 0); return sbi_err_map_linux_errno(ret.error); }对应disable:
void kvm_riscv_nacl_disable(void) { if (!kvm_riscv_nacl_available()) return; sbi_ecall(SBI_EXT_NACL, SBI_EXT_NACL_SET_SHMEM, SBI_SHMEM_DISABLE, SBI_SHMEM_DISABLE, 0, 0, 0, 0); }四、初始化时机(kvm_init → 之前看的 main.c)
static int __init riscv_kvm_init(void) { ... rc = kvm_riscv_nacl_init(); // 分配 per-CPU shmem 页,探测 SBI NACL if (rc && rc != -ENODEV) // -ENODEV = OpenSBI 不支持 NACL,忽略 return rc; ... } int kvm_arch_enable_virtualization_cpu(void) { rc = kvm_riscv_nacl_enable(); // 每 CPU online 时绑定 shmem 到当前 CPU if (rc) return rc; csr_write(CSR_HEDELEG, ...); // 之后才是 HEDELEG/HIDELEG/HCOUNTEREN ... }kvm_riscv_nacl_init():分配页 +sbi_probe_extension(SBI_EXT_NACL)+SBI_EXT_NACL_PROBE_FEATURE→ 设 static keykvm_riscv_nacl_enable():在每 CPU 上调SET_SHMEM,激活本 CPU 的 NACL 通道
五、NACL enable 后对 vcpu_run 的影响(原理)
启用后,KVM vcpu world-switch 不再逐条csr_write(CSR_HGATP, ...)/csr_write(CSR_HSTATUS, ...),改为:
写 H-ext CSR 到 NACL shmem CSR Space(标记 dirty bitmap)
调
SBI_EXT_NACL_SYNC_CSR(-1UL)→ L0 批量应用 HGATP/HSTATUS/HEDELEG…若有 HFENCE → 填 HFENCE queue →
SBI_EXT_NACL_SYNC_HFENCE若有
SBI_NACL_FEAT_SYNC_SRET→ 填 GPR + SRET 参数 →SBI_EXT_NACL_SYNC_SRET原子完成"CSR apply + HFENCE + SRET"
效果:嵌套虚拟化时 L1 KVM world-switch 的 VM-Exit 数大幅下降。
六、对 H-ext 仿真器的影响
场景 | NACL 影响 |
|---|---|
裸机跑 KVM( lkvm 场景,OpenSBI 无 NACL) |
|
OpenSBI 假声明支持 NACL 但仿真器不实现 |
|
未来做嵌套虚拟化(KVM-on-KVM) | 需仿真器实现 SBI NACL:分配 shmem、解析 SYNC_CSR/SYNC_HFENCE/SYNC_SRET |
七、一句话总结
kvm_riscv_nacl_enable()是 KVM 对每个 CPU 调 SBI NACLSET_SHMEM,把预分配的 Per-CPU 共享内存页注册给 OpenSBI,用于嵌套虚拟化时批量同步 H-ext CSR / HFENCE / SRET,减少 VM-Exit。非嵌套场景(裸 H-ext 跑 KVM)OpenSBI 不实现 NACL,kvm_riscv_nacl_available()为 false,该函数直接 return 0,对功能无任何影响。若仿真器错误暴露 NACL 却不实现会导致 KVM 加载失败。
如果你想确认你环境是否走了 NACL 分支,搜 dmesg:
dmesg | grep -i "nested acceleration"有输出 = NACL 激活(需仿真器支持);无输出 = 正常非嵌套,可忽略 NACL 完全。