Zephyr 在 ACRN 虚拟化环境中的运行实战:预启动 VM 构建、XML 配置与 EFI 引导完整指南
本文以 Zephyr 仓库中 boards/acrn/ 板级支持为核心,系统讲解如何让 Zephyr 作为 x86 ACRN 管理型虚拟化器(hypervisor)下的“预启动(pre-launched)”实时 VM 运行:从选择 acrn 系列板级目标并构建 Zephyr 原始镜像,到手工编辑 ACRN 的 hybrid.xml 完成内存布局与 SMP CPU 亲和性配置,再到制作 GRUB + EFI 启动介质并在控制台上看到 Hello World! acrn 的完整实战流程。读完本文,你将掌握 Zephyr 与 ACRN hypervisor 的对接参数(kern_load_addr、kern_entry_addr、cpu_affinity)的确定方法,以及该板级目标在仓库中的 Kconfig、devicetree 与 defconfig 的实际构成。
ACRN Hybrid 场景与“预启动”VM
ACRN(Project ACRN)是一款面向 x86 的管理型虚拟化器,Zephyr 可以在其下作为 guest 运行。与“先启动 Linux SOS VM、再由其拉起实时 VM”的两级模式不同,ACRN 的 Hybrid 场景支持 Zephyr 以所谓 "pre-launched"(预启动)模式 直接运行:Zephyr 在启动时直接访问 ACRN hypervisor,不经过 SOS VM。这也是原文档指出的最贴近真实用户场景的模式——因为 Zephyr 的实时性与功能安全特性不会受到其他 VM 的干扰,Zephyr 镜像在 ACRN 中会被标记为 vm_type 为 SAFETY_VM 的类型。
该板级支持的入口文档位于 boards/acrn/acrn/doc/index.rst,由板级索引页 boards/acrn/index.rst 通过 toctree(**/* 通配)聚合。
板级目标定义:acrn / acrn_ehl_crb / acrn_adl_crb
仓库中 ACRN 板级支持定义了三个目标,均可在 board.yml 中确认:
| 板级名称 | 全称 | 说明 |
|---|---|---|
acrn | ACRN hypervisor | 通用 ACRN 目标 |
acrn_ehl_crb | ACRN on EHL hypervisor | Intel Elkhart Lake(EHL)参考板 CRB |
acrn_adl_crb | ACRN Hypervisor on ADL | Intel Alder Lake(ADL)参考板 CRB |
三者的 Kconfig 组织方式一致:顶层 Kconfig 定义公共选项并强制选择 64 位 x86:
config ACRN_COMMON
bool
default y if BOARD_ACRN || BOARD_ACRN_EHL_CRB || BOARD_ACRN_ADL_CRB
select CPU_HAS_FPU
select X86_64
各具体板级(如 Kconfig.acrn、Kconfig.acrn_ehl_crb、Kconfig.acrn_adl_crb)则各自声明 config BOARD_* 并 select SOC_ATOM,即把这三个目标都挂到 Intel Atom 系列 SoC 描述之下。board.cmake 中仅配置了 misc-flasher 作为默认刷写器,因为该板级的实际“部署”是烧录 EFI 启动介质,而非串口/调试器直连:
board_set_flasher_ifnset(misc-flasher)
board_finalize_runner_args(misc-flasher)
从源码结构看,板级还附带一个专门的 C 文件 board_acrn.c,内容只有 4 行——它声明了各逻辑 CPU 的本地 APIC ID 映射表:
/* Local APIC IDs for each logical CPU */
uint8_t x86_cpu_loapics[] = { 0x00, 0x02, 0x04, 0x06 };
这个 0x00/0x02/0x04/0x06 的偶数递增序列与下文 ACRN 侧把 SMP CPU 固定到物理核 0、1、2、3 的配置相互对应,是 Zephyr 侧 SMP 能跑通的前提之一。
步骤一:构建 Zephyr 应用
按照常规流程构建即可,选择 ACRN 板级目标(原文档以 EHL CRB 为例,它是该场景的建议平台之一):
west build -b acrn_ehl_crb samples/hello_world
构建完成后有两点必须注意,这也是整个对接中最容易踩坑的地方:
- 检查
build/zephyr/.config的 kconfig 输出。后续配置 ACRN 时需要引用其中的CONFIG_MP_MAX_NUM_CPUS等值,这一点在配置 CPU 亲和性时会用到。 - 部署产物是
build/zephyr/zephyr.bin(raw memory image),而不是zephyr.elf。与其他 x86 目标不同,ACRN 不认识 ELF,它只会把一段连续数据区加载进内存并跳转到指定地址,所以不能使用 ELF 镜像。
之所以强调“加载地址 + 入口地址”这两个值,是因为它们必须在 ACRN 配置中手工填写,而它们的来源分别是:
-
加载地址:必须与 include/zephyr/arch/x86/memory.ld 中的
LOCORE_BASE符号一致:#define LOCORE_BASE 0x1000 #define LOCORE_SIZE (0x10000 - LOCORE_BASE) ... LOCORE (wx) : ORIGIN = LOCORE_BASE, LENGTH = LOCORE_SIZE当前该值为
0x1000,与 ACRN 默认配置恰好一致。 -
入口地址:从构建出的
zephyr.elf中用 binutils 提取:$ objdump -f build/zephyr/zephyr.elf build/zephyr/zephyr.elf: file format elf64-x86-64 architecture: i386:x86-64, flags 0x00000012: EXEC_P, HAS_SYMS start address 0x0000000000001000默认情况下入口地址也是
0x1000,但原文档特别提醒:这并非在所有配置下恒真,未来也可能变化,因此每次换配置后都应重新确认,而不是照抄0x1000。
板级 defconfig 与 devicetree 说明了什么
从仓库中的默认配置可以读出该板级的运行时画像。通用目标 acrn_defconfig:
CONFIG_PIC_DISABLE=y
CONFIG_LOAPIC=y
CONFIG_APIC_TSC_DEADLINE_TIMER=y
CONFIG_X2APIC=y
CONFIG_CONSOLE=y
CONFIG_SERIAL=y
CONFIG_UART_CONSOLE=y
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=25000000
CONFIG_BUILD_OUTPUT_BIN=y
CONFIG_SHELL_BACKEND_SERIAL_INTERRUPT_DRIVEN=n
CONFIG_KERNEL_VM_SIZE=0x1000000
而 EHL CRB 目标 acrn_ehl_crb_defconfig 在此基础上多了几项关键差异:
CONFIG_SMP=y
CONFIG_X2APIC=y
CONFIG_PCIE=y
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=1900000000
CONFIG_APIC_TIMER_IRQ_PRIORITY=1
CONFIG_SMP=y:印证了 EHL CRB 场景默认按多核构建,这正是必须在 ACRN 侧配置cpu_affinity的原因;- 系统时钟从通用的 25 MHz 提升到 1.9 GHz,对应真实平台 CPU 频率;
CONFIG_BUILD_OUTPUT_BIN=y:与“必须交付zephyr.binraw 镜像”的要求互相呼应。
对应的 acrn.dts 则描述了 guest 视角的硬件模型:
/ {
model = "ACRN";
compatible = "acrn";
cpus {
cpu@1 {
device_type = "cpu";
compatible = "intel,x86_64";
d-cache-line-size = <64>;
reg = <1>;
};
};
chosen {
zephyr,sram = &dram0;
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
};
};
&uart0 {
status = "okay";
current-speed = <115200>;
};
其中 DT_DRAM_SIZE 定义为 8 MB(DT_SIZE_K(8192))、DT_DRAM_BASE 为 0,并通过 #include <intel/atom.dtsi> 复用 Intel Atom 的 SoC 设备树片段。从结构上看,Zephyr 侧只显式声明了 cpu@1(加上继承自 atom.dtsi 的 cpu@0),即 guest 内可见两核,与后文把 Zephyr 固定到物理 CPU 0/1 的推荐做法一致;console 与 shell 均走 uart0(115200 波特率)。
步骤二:获取并配置 ACRN
首先获取 ACRN hypervisor 源码:
git clone https://github.com/projectacrn/acrn-hypervisor
版本上建议使用 v2.5.1 或更新版本,因为新版本对 Zephyr 侧 SMP 的支持更好。
ACRN 和 Zephyr 都偏好构建期配置而非运行时探测,但两者的配置形态差异很大:Zephyr 使用可合并的 kconfig defconfig 与 devicetree include 等小单元,而 ACRN 是单个大型 XML 文件。你需要选择与硬件匹配的主机配置(本例为 ehl-crb-b),然后编辑 misc/config_tools/data/<platform>/hybrid.xml,按以下三部分修改:
1. 让 Zephyr 成为 <vm id="0">
在 <vm> 声明列表中,确认 Zephyr 镜像的 id 属性为 0。这样启动 ACRN 时只需要携带一个 VM 镜像,避免为了启动而把庞大的 Linux 镜像拷贝进启动文件系统。在已测试过的配置下,Zephyr 的 <vm_type> 固定为 SAFETY_VM。
2. 配置 Zephyr 内存布局
找到 <vm id="0">...<os_config> 段,使其形如:
<os_config>
<name>Zephyr</name>
<kern_type>KERNEL_ZEPHYR</kern_type>
<kern_mod>Zephyr_RawImage</kern_mod>
<ramdisk_mod/>
<bootargs></bootargs>
<kern_load_addr>0x1000</kern_load_addr>
<kern_entry_addr>0x1000</kern_entry_addr>
</os_config>
两个地址参数的前文对应关系再强调一次:
<kern_load_addr>必须匹配memory.ld中的LOCORE_BASE(当前为0x1000);<kern_entry_addr>必须匹配objdump -f zephyr.elf输出的start address。
注意 <kern_mod>Zephyr_RawImage</kern_mod> 这一标签——它后面会与 GRUB 的 module2 行配合,把 raw 镜像按这个名字传给 ACRN(见步骤四)。
3. 配置 CPU 亲和性(SMP 关键点)
Zephyr 默认按 SMP 构建,而 ACRN 默认配置只给 guest 一个 CPU。做法是:在 Zephyr 的 .config 中查 CONFIG_MP_MAX_NUM_CPUS,然后在 <vm id="0"> 的 <cpu_affinity> 里给出同样数量的 <pcpu_id>:
<vm id="0">
<vm_type>SAFETY_VM</vm_type>
<name>ACRN PRE-LAUNCHED VM0</name>
<guest_flags>
<guest_flag>0</guest_flag>
</guest_flags>
<cpu_affinity>
<pcpu_id>0</pcpu_id>
<pcpu_id>1</pcpu_id>
</cpu_affinity>
...
<clos>
<vcpu_clos>0</vcpu_clos>
<vcpu_clos>0</vcpu_clos>
</clos>
...
</vm>
这一步是硬性要求:不做此修改,ACRN hypervisor 与 Zephyr 应用都无法成功启动(仅限 SMP 构建);若确定只用单核运行 Zephyr,则可以跳过。
同时必须调整其他 VM 的占用,避免物理核冲突。例如把 SOS VM 挪到 CPU2/CPU3:
<vm id="1">
<vm_type>SOS_VM</vm_type>
<name>ACRN SOS VM</name>
<guest_flags>
<guest_flag>0</guest_flag>
</guest_flags>
<cpu_affinity>
<pcpu_id>2</pcpu_id>
<pcpu_id>3</pcpu_id>
</cpu_affinity>
<clos>
<vcpu_clos>0</vcpu_clos>
<vcpu_clos>0</vcpu_clos>
</clos>
...
</vm>
<pcpu_id> 是宿主机上的物理 CPU 索引。多 guest 共存时不应让这些分配相互重叠;本例只用 0/1 两个核做测试即可。原文档还指出 ehl-crb-b 有 4 个物理 CPU,把 0–3 全部分给 Zephyr 也能工作,但那样就没有空闲核留给其他 guest 独占了。Zephyr 侧 board_acrn.c 中 x86_cpu_loapics[] = {0x00, 0x02, 0x04, 0x06} 恰好为最多 4 个逻辑核预留了本地 APIC ID,与上述“4 核全给 Zephyr”的上限相符。
4. 构建 ACRN
配置完成后构建:
$ make -j BOARD=ehl-crb-b SCENARIO=hybrid
所需产物只有一个:build/hypervisor/acrn.bin(ACRN multiboot 镜像)。
步骤三:组装 EFI 启动介质
ACRN 通过 GNU GRUB 引导,GRUB 又由 EFI 固件启动,三层都需要配置正确。
获取 GRUB
需要与硬件匹配的 GRUB EFI 二进制。多数情况下从源码构建或从某个 Linux 发行版拷贝即可;但在某些硬件上需要为特定平台打补丁才能工作——原文档对此的忠告颇具实践味道:去问问上一个成功测试的人,直接拷贝一份能用的二进制往往最快。
创建 EFI 启动分区
把启动介质(例如挂在 Linux 上的 /dev/sdb,具体设备视硬件而定)接上,创建一个足够大的 EFI 引导分区(类型码 0xEF)。原文档给出了一条把命令序列直接喂给 fdisk 的写法,也可以手工敲入同样的按键序列:
# for i in n p 1 "" "" t ef w; do echo $i; done | fdisk /dev/sdb
...
<大量 fdisk 输出>
随后在新分区上创建 FAT 文件系统并挂载:
# mkfs.vfat -n ACRN_ZEPHYR /dev/sdb1
# mkdir -p /mnt/acrn
# mount /dev/sdb1 /mnt/acrn
拷贝镜像并配置 GRUB
ACRN 自身没有运行时文件系统,它的 guest VM(即 zephyr.bin)是作为 GRUB 的 "multiboot" module 传入的,因此必须依赖 GRUB 的文件系统驱动。三个文件(GRUB、ACRN、Zephyr)都要放进启动介质的 /efi/boot 目录,且 GRUB 必须命名为 bootx64.efi 才能被固件识别为引导程序:
# mkdir -p /mnt/acrn/efi/boot
# cp $PATH_TO_GRUB_BINARY /mnt/acrn/efi/boot/bootx64.efi
# cp $ZEPHYR_BASE/build/zephyr/zephyr.bin /mnt/acrn/efi/boot/
# cp $PATH_TO_ACRN/build/hypervisor/acrn.bin /mnt/acrn/efi/boot/
启动时 GRUB 会读取 efi/boot/grub.cfg 作为运行时配置。它需要把 acrn.bin 作为 boot target 加载,并把 zephyr.bin 作为第一个 module 传入(因为 Zephyr 被配置为 <vm id="0">)。原文档给出的这份最小配置在绝大多数硬件上都能工作(hd0 几乎总指 GRUB 所在的启动文件系统),无需折腾 GRUB 插件、菜单或超时:
# cat > /mnt/acrn/efi/boot/grub.cfg<<EOF
set root='hd0,msdos1'
multiboot2 /efi/boot/acrn.bin
module2 /efi/boot/zephyr.bin Zephyr_RawImage
boot
EOF
其中 module2 /efi/boot/zephyr.bin Zephyr_RawImage 行尾的 Zephyr_RawImage 与 XML 里 <kern_mod>Zephyr_RawImage</kern_mod> 的取值一一对应,是 GRUB 与 ACRN 之间的“镜像名契约”。完成后卸载并同步:
# umount /dev/sdb1
# sync
步骤四:启动并验证
一切正常时,用该 EFI 介质启动硬件后,你会得到:一个运行中的 ACRN、一个运行中的 Zephyr(默认即为 prelaunched VM),以及一个可用的 ACRN 控制台命令行。
在 ACRN 控制台用 vm_console 命令查看 Zephyr(VM 0)的输出:
ACRN:>vm_console 0
----- Entering VM 0 Shell -----
*** Booting Zephyr OS build v2.6.0-rc1-324-g1a03783861ad ***
Hello World! acrn
看到 Hello World! acrn 即代表 Zephyr 已在 ACRN 预启动 VM 中成功运行。
适用前提与限制小结
结合原文档与仓库文件,把关键前提浓缩如下,便于核对:
- 适用平台:Intel EHL CRB(
acrn_ehl_crb,ACRN 主机配置ehl-crb-b)与 ADL CRB(acrn_adl_crb),以及通用acrn目标;三者均select SOC_ATOM,运行于X86_64; - ACRN 版本建议 v2.5.1+(SMP 支持);
- Zephyr 交付物必须是
zephyr.binraw 镜像,kern_load_addr对LOCORE_BASE(0x1000,见 include/zephyr/arch/x86/memory.ld),kern_entry_addr对objdump -f输出的 start address; - SMP 构建时必须手工配置
<cpu_affinity>并让其他 VM 让出对应物理核,否则 ACRN 与 Zephyr 均无法启动; - guest 内可见硬件由 acrn.dts 描述:8 MB DRAM、uart0/uart1(115200)、
intel,x86_64CPU 及 PCIE 控制器节点,运行时时钟源由 defconfig 的SYS_CLOCK_HW_CYCLES_PER_SEC决定(通用目标 25 MHz,EHL CRB 1.9 GHz)。
关于 ACRN 自身更多能力(构建与使用 SOS VM、其他 guest VM 等)的完整资料,属于 ACRN 上游文档范畴,本文仅覆盖 Zephyr 侧与该仓库板级支持直接相关的内容。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考




