|
| 1 | +# libkrun 与 libkrunfw:轻量级虚拟化的技术解析 |
| 2 | + |
| 3 | +> 从 Linux/macOS 到 Windows,一个动态库如何让进程隔离变得触手可及 |
| 4 | +
|
| 5 | +--- |
| 6 | + |
| 7 | +## 背景:为什么需要轻量级虚拟化? |
| 8 | + |
| 9 | +容器技术(如 Docker)通过 Linux namespace 和 cgroup 实现进程隔离,但它们共享宿主机内核,存在内核逃逸的安全风险。传统虚拟机(如 QEMU/KVM)提供了强隔离,但启动慢、资源占用高,不适合容器场景。 |
| 10 | + |
| 11 | +**libkrun** 走了一条中间路线:用虚拟化技术提供内核级隔离,同时保持接近容器的轻量性。 |
| 12 | + |
| 13 | +--- |
| 14 | + |
| 15 | +## libkrun 是什么? |
| 16 | + |
| 17 | +libkrun 是一个**动态库形式的轻量级 VMM(Virtual Machine Monitor)**,它让任何程序只需链接这个库,就能获得基于硬件虚拟化的进程隔离能力。 |
| 18 | + |
| 19 | +``` |
| 20 | +应用程序 (crun / krunkit / muvm / a3s box) |
| 21 | + ↓ 链接 |
| 22 | + libkrun.so / libkrun.dylib |
| 23 | + ↓ 调用 |
| 24 | + KVM (Linux) / HVF (macOS) / WHPX (Windows) |
| 25 | + ↓ 运行 |
| 26 | + Guest VM(隔离的进程) |
| 27 | +``` |
| 28 | + |
| 29 | +它的 C API 极其简洁: |
| 30 | + |
| 31 | +```c |
| 32 | +// 创建一个 VM 上下文 |
| 33 | +uint32_t ctx = krun_create_ctx(); |
| 34 | + |
| 35 | +// 配置资源 |
| 36 | +krun_set_vm_config(ctx, 2, 512); // 2 vCPU,512MB 内存 |
| 37 | +krun_set_root(ctx, "/path/to/rootfs"); // 根文件系统 |
| 38 | +krun_set_exec(ctx, "/bin/sh", args, env); // 要运行的程序 |
| 39 | + |
| 40 | +// 启动 VM(阻塞直到 VM 退出) |
| 41 | +krun_start_enter(ctx); |
| 42 | +``` |
| 43 | +
|
| 44 | +### 核心组件 |
| 45 | +
|
| 46 | +libkrun 内部集成了一个完整的 VMM,包含: |
| 47 | +
|
| 48 | +| 组件 | 作用 | |
| 49 | +|------|------| |
| 50 | +| vCPU 管理 | 创建、运行、销毁虚拟 CPU | |
| 51 | +| 内存管理 | 分配 guest 物理内存 | |
| 52 | +| 设备模拟 | virtio 设备(console、fs、net、block 等)| |
| 53 | +| 中断控制器 | 模拟 APIC/GIC | |
| 54 | +| 引导加载 | 将内核加载到 guest 内存并启动 | |
| 55 | +
|
| 56 | +--- |
| 57 | +
|
| 58 | +## libkrunfw 是什么? |
| 59 | +
|
| 60 | +libkrun 需要一个 Linux 内核来运行 guest 进程。**libkrunfw** 就是这个内核的载体——它把一个经过专门配置的 Linux 内核打包成动态库。 |
| 61 | +
|
| 62 | +### 巧妙的设计 |
| 63 | +
|
| 64 | +传统方式需要从磁盘读取内核文件,而 libkrunfw 利用了动态链接器的特性: |
| 65 | +
|
| 66 | +``` |
| 67 | +libkrunfw.so.5 |
| 68 | +├── .data 段:内核二进制镜像(vmlinux) |
| 69 | +├── krunfw_get_kernel():返回内核镜像的指针和大小 |
| 70 | +└── krunfw_get_initrd():返回 initrd 的指针(TEE 变体) |
| 71 | +``` |
| 72 | +
|
| 73 | +当 libkrun 加载 libkrunfw 时,动态链接器直接将内核镜像映射到进程地址空间。libkrun 拿到指针后,直接将这段内存注入到 guest 的物理内存中,无需任何文件 I/O。 |
| 74 | +
|
| 75 | +```rust |
| 76 | +// libkrun 中加载 libkrunfw 的代码 |
| 77 | +static KRUNFW: LazyLock<Option<libloading::Library>> = |
| 78 | + LazyLock::new(|| unsafe { libloading::Library::new("libkrunfw.so.5").ok() }); |
| 79 | +
|
| 80 | +// 获取内核镜像指针 |
| 81 | +let get_kernel: Symbol<unsafe extern "C" fn(*mut u64, *mut u64, *mut size_t) -> *mut c_char> |
| 82 | + = krunfw.get(b"krunfw_get_kernel")?; |
| 83 | +``` |
| 84 | + |
| 85 | +### libkrunfw 中的内核有什么特别之处? |
| 86 | + |
| 87 | +libkrunfw 中的内核不是标准的发行版内核,它包含了专门的补丁: |
| 88 | + |
| 89 | +1. **TSI(Transparent Socket Impersonation)补丁**:让 guest 内核能够将 socket 调用透明地转发给 VMM 代理,实现无虚拟网卡的网络连接。 |
| 90 | +2. **最小化配置**:去掉了大量不需要的驱动和功能,减小体积,加快启动速度。 |
| 91 | +3. **CPU 数量限制**:`CONFIG_NR_CPUS=8`,针对轻量级场景优化。 |
| 92 | + |
| 93 | +### 多种变体 |
| 94 | + |
| 95 | +| 变体 | 库名 | 用途 | |
| 96 | +|------|------|------| |
| 97 | +| 标准版 | `libkrunfw.so.5` | 通用虚拟化 | |
| 98 | +| SEV 版 | `libkrunfw-sev.so.5` | AMD 内存加密 | |
| 99 | +| TDX 版 | `libkrunfw-tdx.so.5` | Intel 可信域扩展 | |
| 100 | + |
| 101 | +--- |
| 102 | + |
| 103 | +## 技术价值 |
| 104 | + |
| 105 | +### 1. 极低的使用门槛 |
| 106 | + |
| 107 | +不需要了解 KVM ioctl、QEMU 配置或任何虚拟化细节。一个 C 函数调用就能启动一个隔离的 VM。这让 [crun](https://github.com/containers/crun)、[krunkit](https://github.com/containers/krunkit)、[muvm](https://github.com/AsahiLinux/muvm) 等项目能够轻松获得虚拟化隔离能力。 |
| 108 | + |
| 109 | +### 2. 启动速度快 |
| 110 | + |
| 111 | +由于内核已经通过动态链接器映射到内存,省去了磁盘读取时间。加上内核本身的最小化配置,整个 VM 的启动时间可以控制在毫秒级别。 |
| 112 | + |
| 113 | +### 3. 强安全隔离 |
| 114 | + |
| 115 | +相比 namespace 隔离,libkrun 提供了硬件级别的隔离边界。即使 guest 内核存在漏洞,攻击者也需要突破 hypervisor 层才能影响宿主机。 |
| 116 | + |
| 117 | +### 4. TSI 网络的创新性 |
| 118 | + |
| 119 | +传统虚拟机需要虚拟网卡 + 用户态网络代理(如 passt/gvproxy)才能联网,配置复杂。TSI 通过在 guest 内核中拦截 socket 系统调用,将网络请求透明地转发给 VMM,无需任何虚拟网络设备,大幅简化了网络配置。 |
| 120 | + |
| 121 | +### 5. 跨平台能力 |
| 122 | + |
| 123 | +同一套 API,在 Linux 上用 KVM,在 macOS 上用 HVF,在 Windows 上用 WHPX,上层应用无需感知底层差异。 |
| 124 | + |
| 125 | +--- |
| 126 | + |
| 127 | +## 限制 |
| 128 | + |
| 129 | +### 技术限制 |
| 130 | + |
| 131 | +**1. 依赖定制内核** |
| 132 | +TSI 等核心功能需要 libkrunfw 中的定制内核,无法使用发行版标准内核。这意味着内核版本和功能由 libkrunfw 决定,用户无法自由选择。 |
| 133 | + |
| 134 | +**2. 工作负载兼容性有限** |
| 135 | +libkrun 的设计目标是运行单个进程,而非通用虚拟机。不支持: |
| 136 | +- 需要特殊内核模块的工作负载 |
| 137 | +- 需要 UEFI/BIOS 的操作系统安装(EFI 变体除外) |
| 138 | +- 需要 PCI 直通的场景 |
| 139 | + |
| 140 | +**3. CPU 数量上限** |
| 141 | +libkrunfw 内核限制最多 8 个 vCPU,不适合高并发计算密集型场景。 |
| 142 | + |
| 143 | +**4. TDX 变体的内存限制** |
| 144 | +Intel TDX 变体最多支持 3072MB 内存和 1 个 vCPU,限制较大。 |
| 145 | + |
| 146 | +### 安全模型限制 |
| 147 | + |
| 148 | +libkrun 的安全模型将 guest 和 VMM 视为同一安全上下文。VMM 能访问的宿主机资源,guest 理论上也能通过 VMM 访问。要实现真正的隔离,需要在宿主机层面(如 Linux namespace)对 VMM 进程本身进行限制。 |
| 149 | + |
| 150 | +### 平台限制 |
| 151 | + |
| 152 | +- **Windows 支持仍处于实验阶段**:WHPX 后端正在开发中 |
| 153 | +- **macOS 仅支持 ARM64**:x86_64 macOS 不支持(HVF 限制) |
| 154 | +- **Linux 需要 KVM 支持**:虚拟机或无 KVM 的环境无法使用 |
| 155 | + |
| 156 | +--- |
| 157 | + |
| 158 | +## WHPX:让 libkrun 在 Windows 上运行 |
| 159 | + |
| 160 | +### 什么是 WHPX? |
| 161 | + |
| 162 | +**Windows Hypervisor Platform (WHPX)** 是微软在 Windows 10 2004 版本引入的用户态虚拟化 API,通过 `WinHvPlatform.dll` 暴露给应用程序。它是 Hyper-V 的用户态接口,类似于 Linux 的 KVM 和 macOS 的 HVF。 |
| 163 | + |
| 164 | +``` |
| 165 | +Windows 应用程序 |
| 166 | + ↓ |
| 167 | +WinHvPlatform API (用户态) |
| 168 | + ↓ |
| 169 | +Hyper-V Hypervisor (内核态) |
| 170 | + ↓ |
| 171 | +硬件虚拟化 (Intel VT-x / AMD-V) |
| 172 | +``` |
| 173 | + |
| 174 | +### WHPX 核心 API |
| 175 | + |
| 176 | +| API | 作用 | |
| 177 | +|-----|------| |
| 178 | +| `WHvCreatePartition` | 创建虚拟机分区 | |
| 179 | +| `WHvSetupPartition` | 配置分区参数 | |
| 180 | +| `WHvMapGpaRange` | 映射 guest 物理内存 | |
| 181 | +| `WHvCreateVirtualProcessor` | 创建 vCPU | |
| 182 | +| `WHvRunVirtualProcessor` | 运行 vCPU 直到 VM exit | |
| 183 | +| `WHvGetVirtualProcessorRegisters` | 读取 vCPU 寄存器 | |
| 184 | +| `WHvSetVirtualProcessorRegisters` | 写入 vCPU 寄存器 | |
| 185 | +| `WHvDeleteVirtualProcessor` | 销毁 vCPU | |
| 186 | +| `WHvDeletePartition` | 销毁分区 | |
| 187 | + |
| 188 | +### VM Exit 处理机制 |
| 189 | + |
| 190 | +WHPX 采用**同步 VM exit** 模型:每次 guest 执行需要 VMM 介入的操作时,`WHvRunVirtualProcessor` 返回,VMM 处理后再次调用继续执行。 |
| 191 | + |
| 192 | +```rust |
| 193 | +// libkrun 中 WHPX vCPU 运行循环的核心逻辑 |
| 194 | +pub fn run(&mut self) -> io::Result<VcpuExit<'_>> { |
| 195 | + let mut exit_context = WHV_RUN_VP_EXIT_CONTEXT::default(); |
| 196 | + |
| 197 | + unsafe { |
| 198 | + WHvRunVirtualProcessor( |
| 199 | + self.partition, |
| 200 | + self.index, |
| 201 | + &mut exit_context as *mut _, |
| 202 | + size_of::<WHV_RUN_VP_EXIT_CONTEXT>() as u32, |
| 203 | + )?; |
| 204 | + } |
| 205 | + |
| 206 | + // 解析 exit 原因 |
| 207 | + match exit_context.ExitReason { |
| 208 | + WHV_RUN_VP_EXIT_REASON_MEMORY_ACCESS => { /* MMIO 处理 */ } |
| 209 | + WHV_RUN_VP_EXIT_REASON_X64_IO_PORT_ACCESS => { /* IO 端口处理 */ } |
| 210 | + WHV_RUN_VP_EXIT_REASON_X64_HALT => Ok(VcpuExit::Halted), |
| 211 | + WHV_RUN_VP_EXIT_REASON_CANCELED => Ok(VcpuExit::Shutdown), |
| 212 | + _ => Ok(VcpuExit::Shutdown), |
| 213 | + } |
| 214 | +} |
| 215 | +``` |
| 216 | + |
| 217 | +### libkrun WHPX 后端架构 |
| 218 | + |
| 219 | +为了在 Windows 上支持 libkrun,我们实现了以下架构: |
| 220 | + |
| 221 | +``` |
| 222 | +Vcpu::run() [vstate.rs] |
| 223 | + ↓ 循环调用 |
| 224 | +WhpxVcpu::run() [whpx_vcpu.rs] |
| 225 | + ↓ 调用 WHvRunVirtualProcessor |
| 226 | + ↓ 返回 VcpuExit 枚举 |
| 227 | +Vcpu::run_emulation() |
| 228 | + ↓ 根据 exit 类型分发 |
| 229 | + ├── MmioRead/MmioWrite → mmio_bus(设备模拟) |
| 230 | + ├── IoPortRead/IoPortWrite → mmio_bus(IO 端口设备) |
| 231 | + ├── Halted → VcpuEmulation::Halted(停止) |
| 232 | + └── Shutdown → VcpuEmulation::Stopped(停止) |
| 233 | +``` |
| 234 | + |
| 235 | +**VcpuExit 枚举**设计使用了 Rust 的生命周期参数,确保 MMIO/IO 数据缓冲区的内存安全: |
| 236 | + |
| 237 | +```rust |
| 238 | +pub enum VcpuExit<'a> { |
| 239 | + MmioRead(u64, &'a mut [u8]), // 地址 + 可变缓冲区(设备填充数据) |
| 240 | + MmioWrite(u64, &'a [u8]), // 地址 + 数据 |
| 241 | + IoPortRead(u16, &'a mut [u8]), // 端口 + 可变缓冲区 |
| 242 | + IoPortWrite(u16, &'a [u8]), // 端口 + 数据 |
| 243 | + Halted, |
| 244 | + Shutdown, |
| 245 | +} |
| 246 | +``` |
| 247 | + |
| 248 | +### 与 KVM/HVF 的对比 |
| 249 | + |
| 250 | +| 特性 | KVM (Linux) | HVF (macOS) | WHPX (Windows) | |
| 251 | +|------|-------------|-------------|----------------| |
| 252 | +| API 层次 | 内核 ioctl | 用户态框架 | 用户态 DLL | |
| 253 | +| 内存映射 | `KVM_SET_USER_MEMORY_REGION` | `hv_vm_map` | `WHvMapGpaRange` | |
| 254 | +| vCPU 运行 | `KVM_RUN` ioctl | `hv_vcpu_run` | `WHvRunVirtualProcessor` | |
| 255 | +| Exit 信息 | `kvm_run` 共享内存 | `hv_vcpu_exit_t` | `WHV_RUN_VP_EXIT_CONTEXT` | |
| 256 | +| 寄存器访问 | `KVM_GET/SET_REGS` | `hv_vcpu_get/set_reg` | `WHvGet/SetVirtualProcessorRegisters` | |
| 257 | +| 最低系统要求 | Linux + KVM 模块 | macOS 11+ ARM64 | Windows 10 2004+ + Hyper-V | |
| 258 | + |
| 259 | +### Windows 支持的意义 |
| 260 | + |
| 261 | +WHPX 后端的实现让 libkrun 真正成为跨平台的虚拟化库: |
| 262 | + |
| 263 | +1. **Windows 容器生态**:为 Windows 上的容器运行时(如 crun 的 Windows 版本)提供虚拟化隔离能力 |
| 264 | +2. **开发环境一致性**:开发者在 Windows 上也能使用与 Linux/macOS 相同的隔离机制 |
| 265 | +3. **a3s box 跨平台**:让 a3s box 能够在 Windows 上提供与其他平台一致的安全隔离体验 |
| 266 | + |
| 267 | +--- |
| 268 | + |
| 269 | +## 总结 |
| 270 | + |
| 271 | +libkrun 和 libkrunfw 共同构成了一套优雅的轻量级虚拟化方案: |
| 272 | + |
| 273 | +- **libkrunfw** 解决了"内核从哪来"的问题——把内核打包成动态库,利用链接器实现零开销加载 |
| 274 | +- **libkrun** 解决了"虚拟化怎么用"的问题——把复杂的 VMM 封装成简单的 C API |
| 275 | +- **WHPX 后端** 解决了"Windows 怎么办"的问题——通过 Windows Hypervisor Platform API 实现与 KVM/HVF 对等的虚拟化能力 |
| 276 | + |
| 277 | +这种设计哲学——**最小化、自包含、跨平台**——使得 libkrun 成为容器安全隔离领域的重要基础设施。 |
| 278 | + |
| 279 | +--- |
| 280 | + |
| 281 | +*本文基于 libkrun 源码分析,部分实现细节以代码为准。* |
0 commit comments