Zephyr qemu_riscv32 虚拟板实战:QEMU RISC-V 32 位模拟的 ELF 加载约定与设备加载器方案
2026/9/20 6:55:48 网站建设 项目流程

Zephyr qemu_riscv32 虚拟板实战:QEMU RISC-V 32 位模拟的 ELF 加载约定与设备加载器方案

【免费下载链接】zephyrPrimary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.项目地址: https://gitcode.com/GitHub_Trending/ze/zephyr

本篇以 Zephyr 仓库中的qemu_riscv32板级文档为核心,讲清三件事:QEMU RISC-Vvirt机器在-kernel加载 ELF 时"以最低加载地址而非 ELF 入口点启动"这一容易被忽视的启动约定、Zephyr 用CONFIG_QEMU_DEVICE_LOADER引入的-device loader规避方案,以及在该虚拟板上构建、运行和调试应用的标准流程。读完后,你能独立完成 Zephyr 在 RISC-V 32 位 QEMU 上的构建与运行,并在链接地址与入口点分离(如 ROM 前区预留 header/padding)的场景下正确选择启动参数。

板级定位与硬件模型

qemu_riscv32是 Zephyr 提供的 RISC-V 32 位架构仿真板配置(见 boards/qemu/riscv32/doc/index.rst)。它不绑定任何物理硬件,而是映射到 QEMU 的 RISC-Vvirt机器,用于在无真实设备的情况下跑 Zephyr 应用和内核测试。

板级元数据声明了可用的 SoC 变体(见 boards/qemu/riscv32/board.yml):

  • 默认变体qemu_virt_riscv32:单核,PLIC 中断控制器;
  • smp:多核 SMP 变体(设备树 qemu_riscv32_qemu_virt_riscv32_smp.dts);
  • aia-imsicaia-direct:分别对应 RISC-V 高级中断架构(AIA)的 IMSIC 与 Direct 两种投递模式,各有单核与 SMP 变体。

Kconfig 侧,BOARD_QEMU_RISCV32直接选中 SoC 配置SOC_QEMU_VIRT_RISCV32(见 boards/qemu/riscv32/Kconfig.qemu_riscv32)。基础设备树 boards/qemu/riscv32/qemu_riscv32.dts 引入qemu/virt-riscv32.dtsi、PLIC 中断与 virtio-net 网卡三个公共节点,并把zephyr,consolezephyr,shell-uart都指向uart0zephyr,sram指向ram0

QEMU 启动参数如何生成

仿真器的完整命令行由板级 CMake 逻辑拼出,入口是 boards/qemu/riscv32/board.cmake:

set(SUPPORTED_EMU_PLATFORMS qemu) include(${ZEPHYR_BASE}/boards/common/qemu_riscv.board.cmake) qemu_riscv_cpu_from_dt(qemu_riscv_cpu) qemu_riscv_binary_suffix(QEMU_BINARY_SUFFIX) set(QEMU_CPU_TYPE "${qemu_riscv_cpu}") if(CONFIG_RISCV_APLIC_MSI) set(QEMU_MACH virt,aia=aplic-imsic) elseif(CONFIG_RISCV_APLIC_DIRECT) set(QEMU_MACH virt,aia=aplic) else() set(QEMU_MACH virt) endif() set(QEMU_BOARD_FLAGS -machine ${QEMU_MACH} -bios none -m 256 -cpu ${qemu_riscv_cpu} )

要点如下:

  • -machine跟随中断架构开关:启用CONFIG_RISCV_APLIC_MSI时为virt,aia=aplic-imsic,启用CONFIG_RISCV_APLIC_DIRECT时为virt,aia=aplic,否则为普通virt。这与 board.yml 中aia-imsic/aia-direct变体一一对应。
  • -bios none-m 256:不加载任何固件镜像(Zephyr 直接以 S-mode 镜像启动),固定 256 MiB 内存。
  • -cpu参数从设备树推导:boards/common/qemu_riscv.board.cmake 中的qemu_riscv_cpu_from_dt()读取/cpus/cpu@0riscv,isa-baseriscv,isa-extensions属性,拼成rv32imac,...=on形式的 CPU 描述;若启用CONFIG_RISCV_PMP还会追加pmp=on,u=onqemu_riscv_binary_suffix()则按CONFIG_64BIT选择qemu-system-riscv32qemu-system-riscv64可执行文件。
  • runner 指派:boards/common/qemu.board.cmake 将 flasher 与 debugger 都设为qemu,因此west flash实际就是启动 QEMU 运行镜像,调试器即 QEMU 的 GDB stub。

ELF 加载约定:为什么 PC 起点可能不是入口点

这是qemu_riscv32文档中最值得掌握的一段技术背景(boards/qemu/riscv32/doc/index.rst)。QEMU 的 RISC-Vvirt机器镜像了 OpenSBIfw_dynamicfw_jumpfw_payload固件以及 Berkeley Boot Loader(BBL)的启动行为,QEMU 源码中的原注释如下:

/* * NB: Use low address not ELF entry point to ensure that the fw_dynamic * behaviour when loading an ELF matches the fw_payload, fw_jump and BBL * behaviour, as well as fw_dynamic with a raw binary, all of which jump to * the (expected) load address load address. This allows kernels to have * separate SBI and ELF entry points (used by FreeBSD, for example). */

换句话说:当 ELF 通过-kernel传给 QEMU 时,vCPU 的程序计数器被置为"镜像加载的最低地址",而不是 ELF 头里的e_entry字段。这样做的目的是保持 raw binary 与 ELF 两种启动路径行为一致,并允许内核暴露一个与 ELF 入口点不同的 SBI 入口(FreeBSD 就是这种情况)。

对 Zephyr 而言,这个约定平时是"隐形"的:链接脚本把镜像入口点(CONFIG_KERNEL_ENTRY)放在 ROM 区最前端,加载地址与入口点恰好重合。但一旦两者分离就会出问题——例如 ROM 区在rom_start之前为 header 或 padding 预留了空间时,QEMU 会跳进这段预留空间的开头而不是CONFIG_KERNEL_ENTRY,Zephyr 根本不会执行。

CONFIG_QEMU_DEVICE_LOADER:用设备加载器规避该约定

Zephyr 的解决方案是 Kconfig 选项CONFIG_QEMU_DEVICE_LOADER。启用后,构建系统不再使用-kernel,而是为每个 CPU 生成一条-device loader,file=<elf>(共CONFIG_MP_MAX_NUM_CPUS条,每核一条)。与-kernel不同,QEMU 的通用 loader 设备尊重 ELF 的真实入口点,因此无论入口点位于 ROM 基址的什么位置,每个 vCPU 都会从CONFIG_KERNEL_ENTRY开始执行。

这一逻辑直接体现在 boards/qemu/riscv32/board.cmake 中:

if(CONFIG_QEMU_DEVICE_LOADER) set(QEMU_KERNEL_OPTION "") math(EXPR max_cpu_index "${CONFIG_MP_MAX_NUM_CPUS} - 1") foreach(cpu_num RANGE 0 ${max_cpu_index}) list(APPEND QEMU_KERNEL_OPTION "-device;loader,file=$<TARGET_FILE:${logical_target_for_zephyr_elf}>,cpu-num=${cpu_num}" ) endforeach() endif()

从源码结构看,这段循环把-kernel <elf>替换成了逐核的 loader 设备,cpu-num=N参数指定该 loader 绑定的 vCPU 编号;这也解释了为什么 SMP 变体必须按核数生成多条 loader 条目。

编程与运行:以 synchronization 示例为例

文档将"烧录"(Flashing)一节定义为:该板既然是仿真的,谈不上"烧写",但可以用该配置在 QEMU 中运行基础 Zephyr 应用和内核测试。文档给出的示例是synchronization样例(源码位于 samples/synchronization),构建目标为run(host-os: unix,board:qemu_riscv32)。

等价的 west 命令流程为:

# 构建并启动 QEMU 运行 west build -b qemu_riscv32 samples/synchronization west flash

由于 runner 已被指派为qemu(见 boards/common/qemu.board.cmake),west flash即直接以本文前面描述的-machine virt -bios none -m 256 -cpu ...参数启动 QEMU 并加载镜像。构建出的镜像会启动 synchronization 样例,串口控制台输出如下:

***** BOOTING ZEPHYR OS v1.8.99 - BUILD: Jun 27 2017 13:09:26 ***** threadA: Hello World from riscv32! threadB: Hello World from riscv32! threadA: Hello World from riscv32! threadB: Hello World from riscv32! threadA: Hello World from riscv32! threadB: Hello World from riscv32! threadA: Hello World from riscv32! threadB: Hello World from riscv32! threadA: Hello World from riscv32! threadB: Hello World from riscv32!

(输出引自 boards/qemu/riscv32/doc/index.rst 中记录的样例运行结果,版本号字样来自文档原文。)threadA 与 threadB 两个线程交替打印,正是该样例演示线程同步的预期表现。

退出 QEMU 的方式:按Ctrl+A后松开再按x

构建与运行的完整规范可参考 Zephyr 应用构建/运行文档(原文档通过build_an_applicationapplication_run两个 ref 指向doc/develop/下的对应章节,本文不再展开)。

调试

调试方面,文档指向application_debugging一节(位于仓库doc/develop/文档树)。结合板级配置,可以确认两点事实:

  • debugger runner 已指派为qemu(boards/common/qemu.board.cmake 第 4 行),即 QEMU 内置的 GDB 远程 stub 是默认调试通道;
  • 由于qemu_riscv32属仿真板,west build产出的 ELF(以及可用的调试符号文件)都在宿主机文件系统上,无需任何物理调试探针即可接入 GDB 流程。

小结

qemu_riscv32的价值在于提供了一个零硬件成本的 RISC-V 32 位验证环境,其板级文档中最具技术含量的部分是 ELF 加载约定的剖析:

  1. QEMUvirt机器经-kernel加载 ELF 时,PC 起点是镜像最低加载地址而非e_entry,该行为对齐 OpenSBI 各固件形态与 BBL;
  2. 只要链接布局保证CONFIG_KERNEL_ENTRY位于 ROM 区最前端(Zephyr 默认如此),该约定无副作用;
  3. 一旦入口点与加载地址分离(ROM 前区预留空间等场景),应启用CONFIG_QEMU_DEVICE_LOADER,让 board.cmake 生成的逐核-device loader条目按 ELF 真实入口点启动每个 vCPU;
  4. 日常开发只需west build -b qemu_riscv32 <app>+west flash,退出 QEMU 用Ctrl+A, x

关键文件索引:板级文档 boards/qemu/riscv32/doc/index.rst、构建逻辑 boards/qemu/riscv32/board.cmake、CPU 参数推导 boards/common/qemu_riscv.board.cmake、runner 指派 boards/common/qemu.board.cmake、板级元数据 boards/qemu/riscv32/board.yml、设备树 boards/qemu/riscv32/qemu_riscv32.dts。

【免费下载链接】zephyrPrimary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.项目地址: https://gitcode.com/GitHub_Trending/ze/zephyr

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询