摘要:本文从一条 QEMU 启动命令出发,从源码层面拆解虚拟 SoC 的完整构建链路。文章依次剖析 MachineClass 的注册与查找、Machine 的 QOM 实例化、CPU 核心创建、MemoryRegion 内存布局、SoC 容器封装,以及 qdev 与 SysBus 协作下的设备挂载,并重点讲解 GIC 中断控制器如何连接外设与 CPU 的中断通路。通过 QOM 类型系统、qdev 设备框架和 MemoryRegion 地址空间三层机制,QEMU 将虚拟硬件逐级拼装成型,帮助读者建立从 Machine 到 Device 的整体认知,为深入 QEMU 源码与二次开发打下基础。
1. 引言:从一条启动命令说起
当我们执行 qemu-system-arm -M vexpress-a9 -cpu cortex-a9 -m 512M -kernel zImage 时,QEMU 究竟做了什么?一条看似简单的命令背后,隐藏着从 Machine 到 Device 的完整构建链路。本文将从源码层面拆解 QEMU 虚拟 SoC 的构建过程,帮助读者理解虚拟硬件是如何被"拼装"出来的。
QEMU 的虚拟 SoC 并非一个单一对象,而是由 Machine、CPU、内存、总线、外设等多个层次协同构成的整体。理解这条构建链路,是深入 QEMU 源码、进行设备模拟开发和系统级调试的基础。

2. 核心概念:Machine、SoC 与 Device 的关系
在深入源码之前,先厘清三个关键概念:
- Machine:描述一块开发板或一台虚拟机的整体形态,包括 CPU 类型、内存大小、总线拓扑以及挂载的外设集合。它是 QEMU 启动时最先被实例化的顶层对象。
- SoC:System on Chip 的缩写,在 QEMU 中通常作为一个中间容器存在,封装 CPU 核心、中断控制器、定时器、串口等芯片内部组件。部分 Machine 直接由 SoC 对象构成。
- Device:具体的外设或内部组件,如 UART、GPIO、SD 控制器等。Device 通过总线挂载到 SoC 或 Machine 上,并通过 MemoryRegion 映射到地址空间。
三者关系可以概括为:Machine 包含 SoC,SoC 包含 Device,Device 通过总线互联并映射到系统地址空间。
3. 启动入口:MachineClass 的注册与查找
QEMU 的 Machine 采用类注册机制管理。每个 Machine 对应一个 MachineClass,通过类型系统注册到全局列表中。
以 vexpress-a9 为例,其定义位于 hw/arm/vexpress.c:
static void vexpress_a9_class_init(ObjectClass *oc, void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
mc->desc = "ARM Versatile Express for Cortex-A9";
mc->init = vexpress_a9_init;
mc->max_cpus = 4;
mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-a9");
}
static const TypeInfo vexpress_a9_info = {
.name = MACHINE_TYPE_NAME("vexpress-a9"),
.parent = TYPE_MACHINE,
.class_init = vexpress_a9_class_init,
};
static void vexpress_machine_init(void)
{
type_register_static(&vexpress_a9_info);
}
type_init(vexpress_machine_init);
当用户传入 -M vexpress-a9 时,QEMU 在 machine_class_by_name() 中遍历已注册的 MachineClass,匹配成功后调用其 init 回调,进入板级初始化流程。
4. Machine 的实例化:从 QOM 到板级初始化
Machine 的实例化遵循 QOM(QEMU Object Model)规范。QEMU 首先通过 machine_class_by_name() 找到对应的 MachineClass,然后调用 qdev_new() 或 object_new() 创建 Machine 实例。
关键流程如下:
- 解析命令行参数:
machine_init_done之前,QEMU 将-M、-cpu、-m等参数解析并填充到MachineState中。 - 创建 Machine 对象:通过 QOM 类型系统实例化 Machine,触发
instance_init回调。 - 调用板级 init 函数:执行
mc->init(machine),即vexpress_a9_init,开始构建具体硬件。
在 vexpress_a9_init 中,QEMU 依次完成 CPU 创建、内存初始化、SoC 实例化和外设挂载。这一函数是板级硬件的"总装车间"。
5. CPU 的创建:核心计算单元的构建
CPU 是 SoC 中最核心的计算单元。在 vexpress-a9 的 init 函数中,CPU 的创建通常通过 cpu_create() 或直接实例化 ARM CPU 对象完成。
static void vexpress_a9_init(MachineState *machine)
{
VexpressMachineState *vms = VEXPRESS_MACHINE(machine);
/* 创建 CPU 核心 */
for (unsigned int i = 0; i < machine->smp.cpus; i++) {
Object *cpu = object_new(machine->cpu_type);
object_property_set_int(cpu, "reset-cbar", vms->memmap[VE_CPU_BASE], NULL);
qdev_realize(DEVICE(cpu), NULL, &error_fatal);
}
}
每个 CPU 核心在实例化时会创建对应的 ARMCPU 对象,并初始化其内部状态,包括寄存器组、MMU、GIC 接口等。CPU 通过 sysbus_mmio_map() 将私有外设映射到系统地址空间。
6. 内存布局:MemoryRegion 与地址空间
虚拟 SoC 的地址空间由 MemoryRegion 树构成。QEMU 通过 memory_region_init_ram() 创建 RAM,通过 memory_region_add_subregion() 将子区域挂载到系统总线。
memory_region_init_ram(&machine->ram, NULL, "vexpress.ram", machine->ram_size, &error_fatal);
memory_region_add_subregion(sysmem, vms->memmap[VE_DRAM], &machine->ram);
vexpress-a9 的地址映射通过 vms->memmap 数组定义,每个条目描述外设的起始地址和大小。例如:
static const hwaddr vexpress_a9_memmap[] = {
[VE_DRAM] = 0x60000000,
[VE_UART0] = 0x10009000,
[VE_GIC] = 0x2c001000,
/* ... */
};
这种集中式的地址映射表是 QEMU 板级代码的常见模式,便于维护和扩展。
7. SoC 的封装:从裸 Machine 到 SoC 容器
较新的 QEMU 版本倾向于将 CPU 和内部外设封装为独立的 SoC 对象,Machine 只负责创建 SoC 并连接外部设备。以 vexpress-a9 为例,其 SoC 构建逻辑可抽象为:
- 创建 GIC(通用中断控制器),作为 SoC 内部的中枢组件。
- 创建 CPU 核心,并将 CPU 的 IRQ 输出连接到 GIC 的输入。
- 创建 UART、Timer、SPI 控制器等内部外设,映射到 SoC 的地址空间。
- 将 SoC 作为一个整体挂载到 Machine 的系统总线上。
SoC 对象通过 QOM 的 TYPE_SOC 基类实现,内部使用 BusState 管理子设备。这种分层设计让 Machine 代码更简洁,也便于复用同一 SoC 到不同开发板。
8. 设备挂载:qdev 与 SysBus 的协作
外设的挂载是 SoC 构建的最后一步。QEMU 使用 qdev 框架管理设备生命周期,使用 SysBus 实现设备与系统总线的连接。
一个典型外设的创建流程如下:
DeviceState *dev = qdev_new("pl011");
qdev_prop_set_chr(dev, "chardev", uart_chr);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, vms->memmap[VE_UART0]);
sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0, pic_irq);
其中:
qdev_new()创建设备对象。qdev_prop_set_chr()设置设备属性,如关联的字符设备。sysbus_realize_and_unref()完成设备实例化。sysbus_mmio_map()将设备的 MMIO 区域映射到系统地址空间。sysbus_connect_irq()连接设备的中断请求线。
9. 中断与总线:SoC 的"神经系统"
中断控制器是 SoC 内部的关键组件。在 vexpress-a9 中,GIC 负责收集各外设的中断请求并分发到 CPU 核心。
GIC 的创建与连接通常如下:
DeviceState *gicdev = qdev_new("arm_gic");
qdev_prop_set_uint32(gicdev, "num-cpu", smp_cpus);
qdev_prop_set_uint32(gicdev, "num-irq", 160);
sysbus_realize_and_unref(SYS_BUS_DEVICE(gicdev), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(gicdev), 0, vms->memmap[VE_GIC_DIST]);
sysbus_mmio_map(SYS_BUS_DEVICE(gicdev), 1, vms->memmap[VE_GIC_CPU]);
随后,每个外设的 IRQ 输出通过 qdev_connect_gpio_out() 连接到 GIC 的对应输入引脚。CPU 的 IRQ 输入则连接到 GIC 的输出。这样,一条从外设到 CPU 的中断通路就建立起来了。
10. 完整构建流程串联
综合以上步骤,QEMU 构建虚拟 SoC 的完整流程可以概括为:
flowchart TD
A[解析命令行参数] --> B[查找并实例化 MachineClass]
B --> C[调用板级 init 函数]
C --> D[创建 CPU 核心]
C --> E[初始化内存与地址空间]
C --> F[创建 SoC 容器]
F --> G[创建 GIC 中断控制器]
F --> H[创建内部外设 UART/Timer]
G --> I[连接外设 IRQ 到 GIC]
H --> I
I --> J[连接 GIC 输出到 CPU IRQ]
D --> J
J --> K[完成系统总线挂载]
K --> L[启动虚拟机]
从 Machine 到 Device,QEMU 通过 QOM 类型系统、qdev 设备框架和 MemoryRegion 地址空间三层机制,将虚拟硬件逐级拼装成型。理解这条链路,是进行 QEMU 二次开发和系统仿真的基础。
11. 总结与进阶方向
本文从一条启动命令出发,梳理了 QEMU 虚拟 SoC 从 Machine 到 Device 的完整构建过程。核心要点包括:
- Machine 是顶层容器,通过 MachineClass 注册与查找。
- SoC 封装 CPU 与内部外设,是复用的关键单元。
- Device 通过 qdev 和 SysBus 挂载,通过 MemoryRegion 映射地址。
- GIC 作为中断中枢,连接外设与 CPU 的中断通路。
进阶读者可以继续深入以下方向:阅读 hw/arm/vexpress.c 完整源码、研究 QOM 类型系统的实现细节、尝试为自定义外设编写 QEMU 设备模型,或通过 info qom-tree 和 info mtree 监控命令观察运行时的对象层级与内存映射。
本文是 QEMU 虚拟化系列的开篇,后续将持续更新更多实战内容,例如:深入剖析 QEMU 设备模型与中断控制器实现、基于 QEMU 的嵌入式系统调试技巧、以及如何为自定义硬件编写设备模拟插件等。如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,一键三连支持我持续创作,也欢迎在评论区留言交流你在 QEMU 开发中遇到的问题,我们一起探讨进步。

439

被折叠的 条评论
为什么被折叠?



