4、虚拟化基础概念:虚拟机(VM)、虚拟CPU(vCPU)、虚拟内存、虚拟中断控制器(vGIC/vAPIC)
好,咱们今天聊点实在的。
虚拟化这个词,圈外人听着高大上,圈内人天天打交道。但说实话,真正把几个核心概念吃透的人,并不多。我见过不少工程师,能跑起来虚拟机,但一问到 vCPU 怎么调度、虚拟中断怎么路由,就开始含糊了。
这章咱们就把这几个基础概念掰开揉碎。你搞懂了它们,后面看 QNX 的 Hypervisor 代码,会轻松很多。
4.1 虚拟机(VM)—— 它到底是个啥?
虚拟机,英文叫 Virtual Machine,简称 VM。说白了,它就是一台用软件模拟出来的“假电脑”。
你想想看,一台物理服务器上跑着 Linux,现在你想再跑一个 Windows,怎么办?传统做法是再加一台机器。虚拟化就不一样了——你在 Linux 上装个 Hypervisor,然后创建一台 VM,给它分配 CPU、内存、硬盘,然后在这个 VM 里装 Windows。
从 Windows 的角度看,它以为自己独占了一台完整的电脑。它有自己的 BIOS、自己的内核、自己的驱动。它根本不知道隔壁还住着一个 Linux 哥们儿。
我在项目中遇到过一种情况:客户要求在一台 QNX 机器上同时运行一个实时控制系统和一个 Android 上层应用。实时系统不能被打扰,Android 又要流畅。怎么办?两台 VM,各跑各的,互不干扰。这就是虚拟化的典型场景。
核心要点:VM 是 Hypervisor 管理下的一个独立执行环境。它包含了一整套虚拟硬件资源:vCPU、虚拟内存、虚拟中断控制器、虚拟定时器等等。每个 VM 之间是隔离的,一个 VM 崩了,不影响其他 VM。
4.2 虚拟 CPU(vCPU)—— 时间片里的“假核”
vCPU,全称 Virtual CPU。它是 VM 看到的“CPU 核”。
但物理机上只有那么几个核,比如 4 核 8 线程。你创建了 4 个 VM,每个 VM 配了 2 个 vCPU,总共需要 8 个 vCPU。物理机只有 4 个核,怎么分?
答案是:分时复用。
Hypervisor 就像一个调度员。它把物理 CPU 的时间切成很多小片,轮流分配给不同的 vCPU。某个时刻,物理核 0 在跑 VM1 的 vCPU0,下一个时刻可能就在跑 VM2 的 vCPU1。
我个人习惯把 vCPU 理解成“虚拟的线程”。它本身不干活,它只是代表 VM 向 Hypervisor 申请物理 CPU 时间的一个凭证。
避坑指南:我曾经在一个项目中,给每个 VM 分配的 vCPU 数量超过了物理核数。结果呢?上下文切换开销巨大,实时性一塌糊涂。记住:vCPU 总数不要超过物理核数的 2 倍,否则调度开销会让你怀疑人生。
在 QNX 的 Hypervisor 中,vCPU 的调度策略是可以配置的。你可以给某个 VM 分配固定的 CPU 时间片,保证它的实时性。比如这样:
// 伪代码示例:配置 vCPU 的调度权重
vm_config_t vm_cfg;
vm_cfg.vcpu_count = 2;
vm_cfg.vcpu[0].weight = 80; // 这个 vCPU 占 80% 的物理核时间
vm_cfg.vcpu[1].weight = 20; // 这个 vCPU 占 20%
嗯,这里要注意:权重不是绝对的百分比,而是相对优先级。Hypervisor 会根据所有 VM 的权重来动态分配时间片。
4.3 虚拟内存 —— 地址翻译的“三层跳转”
虚拟内存这个概念,搞过操作系统的都不陌生。但在虚拟化场景下,它多了一层。
正常情况(非虚拟化):
- 应用程序看到的是虚拟地址(VA)
- MMU 把 VA 翻译成物理地址(PA)
虚拟化情况:
- VM 里的应用程序看到的是客户虚拟地址(GVA)
- VM 里的 OS 把它翻译成客户物理地址(GPA)
- Hypervisor 再把 GPA 翻译成真正的机器物理地址(HPA)
你看,多了一层。这就是所谓的“两阶段地址翻译”(Two-Stage Address Translation)。
为什么需要这样?因为 VM 里的 OS 以为自己管理着全部内存,但实际上 Hypervisor 在背后做了手脚。VM 看到的“物理内存”是假的,真正的物理内存由 Hypervisor 统一分配。
技术细节:ARM 架构下,这个机制叫 Stage-2 MMU。x86 下叫 EPT(Extended Page Tables)。它们的作用是一样的:让 Hypervisor 能够拦截并重定向 VM 的内存访问。
我建议你在设计系统时,一定要算好内存开销。每个 VM 的虚拟内存映射表本身也要占内存。VM 越多,映射表越大。我曾经见过一个项目,开了 8 个 VM,结果光页表就吃了 2GB 内存,吓人。
4.4 虚拟中断控制器(vGIC / vAPIC)—— 中断的“二房东”
中断,是嵌入式系统的灵魂。没有中断,你连键盘敲一下都没反应。
在虚拟化环境下,中断的处理变得复杂了。因为物理中断来了,Hypervisor 要先“截胡”,然后决定:这个中断是给 VM1 的,还是 VM2 的?还是我自己留着用?
虚拟中断控制器就是干这个的。
- vGIC:ARM 架构下的虚拟通用中断控制器(Virtual Generic Interrupt Controller)
- vAPIC:x86 架构下的虚拟高级可编程中断控制器(Virtual Advanced Programmable Interrupt Controller)
它们的作用是一样的:给 VM 提供一个“假的中断控制器”,让 VM 里的 OS 以为自己在直接管理硬件中断。
举个例子:
物理网卡收到一个数据包,产生了一个物理中断。Hypervisor 先抓到它,然后查表:这个网卡属于 VM1。于是 Hypervisor 把中断信息写入 VM1 的 vGIC 寄存器,然后注入一个虚拟中断给 VM1。VM1 里的驱动看到这个虚拟中断,就像看到真实中断一样,开始处理数据包。
注意:中断注入是有延迟的。物理中断来了,Hypervisor 要切换上下文、查表、写寄存器,最后才能通知 VM。这个延迟在实时系统中可能是致命的。我曾经调试过一个系统,因为中断注入延迟太大,导致电机控制周期超时。最后解决方案是:把实时任务直接跑在 Hypervisor 层,不经过 VM。
在 QNX 中,你可以通过配置来优化中断性能:
// 配置 vGIC 中断直通(Passthrough)
vm_irq_config_t irq_cfg;
irq_cfg.irq_num = 67; // 物理中断号
irq_cfg.target_vm = 1; // 直接给 VM1
irq_cfg.passthrough = true; // 绕过 Hypervisor 调度
中断直通的意思是:这个物理中断不经过 Hypervisor 的调度逻辑,直接路由到目标 VM。性能会好很多,但代价是 Hypervisor 失去了对这个中断的控制权。
4.5 四个概念的关系 —— 一张图说清楚
咱们用个表格总结一下:
| 概念 | 一句话解释 | 我踩过的坑 |
|---|---|---|
| VM | 一台假的物理机 | VM 数量太多,管理开销爆炸 |
| vCPU | VM 看到的 CPU 核 | vCPU 总数超过物理核 2 倍,调度崩了 |
| 虚拟内存 | 两层地址翻译,VM 以为自己在管内存 | 页表太大,内存不够用 |
| vGIC/vAPIC | 虚拟的中断控制器,Hypervisor 当“二房东” | 中断注入延迟导致实时任务超时 |
这四个概念,是虚拟化的基石。你搞懂了它们,后面学 QNX Hypervisor 的配置、调度、内存管理,就会顺很多。
下一章,咱们会深入 QNX 的 Hypervisor 架构,看看它到底是怎么把这些概念落地的。到时候我会拿一个实际的项目案例出来,咱们一起拆解。