QEMU虚拟SoC是如何构建出来的?从Machine到Device完整解析

摘要:本文从一条 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 实例。

关键流程如下:

  1. 解析命令行参数machine_init_done 之前,QEMU 将 -M-cpu-m 等参数解析并填充到 MachineState 中。
  2. 创建 Machine 对象:通过 QOM 类型系统实例化 Machine,触发 instance_init 回调。
  3. 调用板级 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 构建逻辑可抽象为:

  1. 创建 GIC(通用中断控制器),作为 SoC 内部的中枢组件。
  2. 创建 CPU 核心,并将 CPU 的 IRQ 输出连接到 GIC 的输入。
  3. 创建 UART、Timer、SPI 控制器等内部外设,映射到 SoC 的地址空间。
  4. 将 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-treeinfo mtree 监控命令观察运行时的对象层级与内存映射。

本文是 QEMU 虚拟化系列的开篇,后续将持续更新更多实战内容,例如:深入剖析 QEMU 设备模型与中断控制器实现、基于 QEMU 的嵌入式系统调试技巧、以及如何为自定义硬件编写设备模拟插件等。如果你觉得这篇文章对你有帮助,欢迎点赞、收藏、关注,一键三连支持我持续创作,也欢迎在评论区留言交流你在 QEMU 开发中遇到的问题,我们一起探讨进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dr.kangder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值