ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

在QEMU上运行RT-Thread:零成本嵌入式开发与调试实战

在QEMU上运行RT-Thread:零成本嵌入式开发与调试实战 1. 为什么要在QEMU上跑RT-Thread如果你刚开始接触嵌入式实时操作系统或者想快速验证一个驱动、一个协议栈又或者想在不花钱买开发板的情况下学习RT-Thread那么用QEMU来模拟一个ARM开发环境绝对是个“真香”的选择。我刚开始接触RT-Thread时手头只有一块STM32的开发板每次修改代码、编译、烧录、调试一套流程下来少说也得一两分钟。这对于验证一个简单的想法或者排查一个偶现的bug来说效率实在太低。后来接触到QEMU发现它就像一个“软件定义”的万能开发板可以瞬间启动、随时复位调试起来也异常方便从此就成了我学习和原型验证的主力工具。简单来说QEMU是一个开源的硬件虚拟化模拟器它能模拟包括ARM、x86、RISC-V在内的多种CPU架构。而RT-Thread是一个开源的嵌入式实时操作系统以其丰富的组件和良好的可伸缩性著称。把RT-Thread运行在QEMU模拟的ARM平台上相当于我们拥有了一个“虚拟的ARM开发板”。这样做有几个核心好处零硬件成本不需要购买真实的物理开发板极速迭代编译后直接运行省去了烧录时间调试友好可以方便地使用GDB进行源码级单步调试这在排查复杂问题时是物理板卡难以比拟的环境纯净避免了因硬件接触不良、供电不稳等物理问题带来的干扰让你可以专注于软件逻辑本身。这次我们聚焦于模拟ARM Cortex-A9架构的vexpress-a9开发板。这是QEMU官方支持的一个经典ARM开发板模型也是RT-Thread官方BSP板级支持包中支持非常完善的一个。通过这个组合我们可以完整地体验RT-Thread的启动、线程调度、设备驱动、文件系统、网络协议栈等核心功能。2. 环境搭建从零开始准备工具链工欲善其事必先利其器。在QEMU里跑RT-Thread我们需要准备三样东西ARM交叉编译工具链、QEMU模拟器本身以及RT-Thread的源代码。这个过程在Linux环境下比如Ubuntu最为顺畅Windows下虽然也可以但可能会遇到一些路径或依赖的小问题。这里我以Ubuntu 22.04 LTS为例进行说明这也是大多数开发者首选的Linux发行版。2.1 安装ARM交叉编译工具链RT-Thread for ARM通常使用GNU Arm Embedded Toolchain也叫arm-none-eabi-gcc进行编译。这个工具链包含了针对裸机或嵌入式系统的编译器、链接器和调试器。在Ubuntu上你可以直接使用apt包管理器安装sudo apt update sudo apt install gcc-arm-none-eabi安装完成后在终端输入arm-none-eabi-gcc --version来验证是否安装成功。你应该能看到类似gcc version 10.3.1的输出。这里有个小细节需要注意不同版本的Ubuntu仓库中的工具链版本可能不同。对于RT-Thread来说主流版本如10.x都兼容。如果你从事的是更底层的开发或者对工具链版本有严格要求比如某些芯片厂商的SDK指定了特定版本也可以去ARM官网或Linaro官网下载预编译的二进制包并手动配置环境变量。注意网络上有些教程会提到安装gcc-arm-linux-gnueabi这是针对运行Linux操作系统的ARM设备的工具链带有glibc库。而arm-none-eabi是“bare-metal”工具链没有操作系统依赖用于编译RT-Thread这类RTOS或裸机程序我们一定要用这个。2.2 安装QEMU模拟器同样使用apt安装QEMU的系统模拟器特别是要安装支持ARM架构的组件sudo apt install qemu-system-arm这个包会安装qemu-system-arm这个可执行文件它是我们启动虚拟机的核心命令。安装后可以通过qemu-system-arm --version查看版本信息。2.3 获取RT-Thread源代码RT-Thread的源代码托管在Gitee和GitHub上。我们使用Git来克隆代码。建议克隆官方仓库这样能获得最新的BSP支持。git clone https://github.com/RT-Thread/rt-thread.git cd rt-thread进入rt-thread/bsp/qemu-vexpress-a9目录这就是我们本次实验的“主战场”。这个目录包含了针对QEMU vexpress-a9板子的所有特定配置、驱动和链接脚本。3. 编译与运行第一个“Hello, QEMU/RT-Thread”环境准备好了接下来就是激动人心的编译和运行时刻。整个过程非常标准化体现了RT-Thread构建系统scons的便捷性。3.1 使用scons进行编译RT-Thread使用scons作为构建工具。首先确保你已经在bsp/qemu-vexpress-a9目录下。配置环境变量可选但推荐虽然scons通常能自动找到arm-none-eabi-gcc但显式设置一下RTT_EXEC_PATH环境变量可以避免一些潜在问题。你可以将其添加到你的shell配置文件如~/.bashrc中或者临时设置export RTT_EXEC_PATH/usr/bin这里的路径是arm-none-eabi-gcc所在的目录/usr/bin。执行menuconfig进行配置这是RT-Thread一个非常强大的功能类似于Linux的make menuconfig可以图形化地配置内核、组件和硬件。scons --menuconfig首次运行会安装并启动pkgs软件包管理器和env工具。在弹出的菜单中你可以浏览和修改配置。例如你可以开启文件系统支持、使能网络协议栈、添加第三方软件包等。作为第一次运行我们保持默认配置即可直接退出并保存。生成工程并编译使用scons命令进行编译。scons如果一切顺利你会在当前目录下看到编译生成的rtthread.elfELF格式的可执行文件包含调试信息和rtthread.bin纯二进制镜像文件。编译输出的最后几行会总结生成了哪些文件。3.2 启动QEMU并加载RT-Thread编译成功后使用以下命令启动QEMUqemu-system-arm -M vexpress-a9 -kernel rtthread.elf -serial stdio -sd sd.bin我们来拆解一下这个命令的每个参数-M vexpress-a9指定要模拟的机器类型为vexpress-a9。-kernel rtthread.elf指定要加载的内核镜像文件。这里我们使用.elf文件因为它包含调试符号方便后续调试。使用.bin文件也可以启动。-serial stdio将虚拟机的第一个串口通常是UART0重定向到当前终端的标准输入输出。这样RT-Thread通过rt_kprintf打印的日志以及我们通过MSHRT-Thread的shell输入的命令都会在这个终端里进行。-sd sd.bin为虚拟机挂载一个虚拟的SD卡镜像文件sd.bin。这个文件在首次运行时需要创建可以使用dd命令创建一个空文件或者RT-Thread的构建脚本可能会帮你创建用于模拟存储设备后续可以在此之上构建文件系统如elm FatFs。执行命令后QEMU窗口会弹出一个图形化的虚拟机窗口但对我们运行RT-Thread来说基本用不到主要看终端你的终端会开始刷出RT-Thread的启动日志类似于\ | / - RT - Thread Operating System / | \ 5.0.0 build Apr 30 2024 2006 - 2024 Copyright by RT-Thread team lwIP-2.1.2 initialized! [I/sal.skt] Socket Abstraction Layer initialize success. [I/utest] utest is initialize success. [I/utest] total utest testcases num: (0) msh /当你看到msh /这个提示符时恭喜你RT-Thread已经在QEMU里成功运行起来了。这是一个功能完整的Shell你可以在这里输入命令例如list_thread来查看当前系统中所有线程的状态或者free查看内存使用情况。3.3 首次运行的常见问题与解决qemu-system-arm: command not found确保qemu-system-arm已正确安装。在Ubuntu上包名就是qemu-system-arm。编译错误提示找不到arm-none-eabi-gcc确认工具链已安装which arm-none-eabi-gcc。确认scons能找到它。可以尝试在scons命令前显式指定工具链路径RTT_EXEC_PATH/usr/bin scons。有时工具链版本太新或太旧可能导致某些头文件问题。可以尝试安装一个较通用的版本如gcc-arm-none-eabi-10-2020-q4-major。QEMU启动后无输出或者卡住检查-kernel参数指定的文件路径和文件名是否正确。确认使用的是bsp/qemu-vexpress-a9目录下编译出的rtthread.elf而不是其他BSP目录的。尝试去掉-sd sd.bin参数先确保最基本的系统能跑起来。检查终端是否被其他进程占用。可以尝试在新的终端标签页中运行QEMU命令。4. 深入BSP理解QEMU vexpress-a9的板级支持BSP是连接RT-Thread内核与具体硬件平台的桥梁。qemu-vexpress-a9这个BSP目录里的文件就是告诉RT-Thread如何在QEMU模拟的这块“虚拟板子”上工作。理解它的结构对你后续移植RT-Thread到其他平台或者深度定制当前平台非常有帮助。4.1 BSP目录结构解析我们来看一下bsp/qemu-vexpress-a9目录下的关键文件和文件夹qemu-vexpress-a9/ ├── applications/ # 用户应用目录可以在这里放你的测试代码 ├── drivers/ # 板级驱动如串口、GPIO虚拟的、SD卡控制器驱动等 ├── libraries/ # 可能包含一些芯片厂商的库文件对于QEMU可能为空或很少 ├── rtconfig.h # **最重要的配置文件**由menuconfig生成定义了所有宏开关 ├── SConscript # Scons构建脚本定义了如何编译本BSP ├── board.c # 板级初始化代码包括时钟、内存布局、串口初始化等 ├── board.h # 板级相关宏定义 ├── link.lds # 链接脚本决定了代码、数据、栈等在内存中的布局 └── ...rtconfig.h这是整个BSP的“大脑”。当你运行scons --menuconfig进行配置时所有的选择最终都会体现在这个文件里比如RT_USING_CONSOLE是否使用控制台、RT_USING_DFS是否使用设备文件系统、BSP_USING_SDIO是否使能SD卡驱动等等。任何功能开关的修改最终都要反映到这个文件或通过menuconfig来操作。board.c这是板级初始化的入口。rt_hw_board_init()函数就在这里实现。它负责在RT-Thread内核启动的早期初始化硬件环境比如设置系统时钟对于QEMU时钟通常是虚拟的或固定的、初始化串口这样rt_kprintf才能工作、初始化内存管理器的堆内存起始地址和大小。这个函数是移植RT-Thread到新硬件平台时必须修改的核心函数之一。link.lds链接脚本。它定义了程序各个段如.text代码段、.data已初始化数据段、.bss未初始化数据段在内存中的存放地址。对于QEMU vexpress-a9它通常指定程序从内存地址0x60010000开始加载这是该机器类型约定的内核加载地址。理解链接脚本对于解决链接错误、优化内存布局至关重要。4.2 虚拟硬件与驱动对应关系QEMU模拟的vexpress-a9平台提供了一系列虚拟的硬件设备RT-Thread的BSP通过驱动与它们对接UART/串口这是最重要的设备是调试和Shell的命脉。在drivers目录下drv_uart.c实现了串口驱动。它操作的是QEMU模拟的PL011 UART控制器。board.c中的rt_hw_board_init()会调用rt_hw_uart_init()来初始化它并将其注册为RT-Thread的标准控制台设备console和标准输入输出设备stdin/stdout。中断控制器GICCortex-A9支持中断。BSP中的interrupt.c等文件实现了对QEMU虚拟的通用中断控制器GIC的初始化和管理使得RT-Thread的软中断机制和线程间通信如信号量、事件能够正常工作。定时器Timer系统心跳SysTick和高精度定时器如HPET的驱动也在BSP中实现为RT-Thread的时钟节拍和软件定时器功能提供基础。SD卡控制器当你在QEMU命令中指定-sd sd.bin时就模拟了一个SD卡设备。BSP中的SDIO或MMC驱动如drv_sdio.c会识别这个设备并允许RT-Thread的文件系统组件如elm FatFs在上面创建分区和文件系统。理解这些对应关系当你想在QEMU环境中测试一个特定的驱动比如I2C、SPI如果QEMU有模拟的话或者功能时你就知道该去BSP的哪个部分寻找或添加代码。5. 进阶实战为虚拟开发板添加“实体”功能让RT-Thread在QEMU里跑起来只是第一步。接下来我们利用这个环境做一些更贴近真实开发的实践比如挂载文件系统、配置网络甚至进行源码级调试。5.1 挂载文件系统让虚拟板拥有“硬盘”默认的sd.bin是一个空镜像。我们可以格式化为FAT32文件系统并在RT-Thread中挂载它。创建并格式化SD卡镜像在宿主机Ubuntu上操作# 创建一个64MB的空白镜像文件 dd if/dev/zero ofsd.bin bs1M count64 # 将其格式化为FAT32文件系统 sudo mkfs.vfat -F 32 sd.bin现在sd.bin就是一个可以被识别为FAT32的“虚拟SD卡”了。在RT-Thread中启用并挂载文件系统运行scons --menuconfig。进入RT-Thread Components - Device virtual file system确保它被启用。进入RT-Thread Components - Device virtual file system - elm-chan‘s FatFs, generic FAT filesystem module启用FatFs。在Hardware Drivers Config - Onboard Peripheral Drivers中确保Enable SDCARD被选中。保存退出并重新编译 (scons)。使用之前的命令启动QEMU记得带上-sd sd.bin。在RT-Thread Shell中操作文件系统 系统启动后在MSH中执行msh / mkfs sd0 # 在sd0设备上创建文件系统如果之前没在宿主机格式化或需要重新格式化 msh / mount sd0 / # 将sd0设备挂载到根目录“/” msh / ls / # 列出根目录文件初始应为空 msh / echo “hello qemu” /test.txt # 创建并写入文件 msh / cat /test.txt # 读取文件内容这样你就拥有了一个可持久化存储的文件系统。你甚至可以在宿主机上挂载这个sd.bin文件使用mount -o loop来直接存取里面的文件实现宿主机和目标机之间的文件共享。5.2 配置网络连接虚拟与真实世界QEMU的vexpress-a9模型可以通过-net参数模拟网络设备。RT-Thread的SALSocket抽象层和lwIP协议栈可以使其具备网络能力。启用RT-Thread网络功能运行scons --menuconfig。进入RT-Thread Components - Network - Socket abstraction layer启用SAL。进入RT-Thread Components - Network - light weight TCP/IP stack启用lwIP。在Hardware Drivers Config - On-chip Peripheral Drivers或Onboard Peripheral Drivers中找到并启用网络设备驱动可能是Enable EMAC或类似的选项具体取决于BSP的实现。保存并编译。以TAP/TUN模式启动QEMU需要宿主机权限 这种方式能让虚拟机获得一个与宿主机同网段的IP实现双向网络通信。设置稍复杂需要创建TAP设备并配置桥接。更简单的方式是使用“用户模式网络”User Networking。使用用户模式网络简单 修改QEMU启动命令添加网络参数qemu-system-arm -M vexpress-a9 -kernel rtthread.elf -serial stdio -sd sd.bin -netdev user,idmynet -device virtio-net-device,netdevmynet这种模式下QEMU内部会运行一个虚拟的DHCP服务器和NAT虚拟机可以访问外部网络如ping通宿主机网关或外网但外部网络无法直接访问虚拟机。在RT-Thread中配置网络 启动后在MSH中网络接口如eth0可能已经通过DHCP自动获取了IP。你可以使用命令查看和测试msh / ifconfig # 查看网络接口信息 msh / ping 10.0.2.2 # 在QEMU用户网络模式下10.0.2.2是宿主机网关的固定地址 msh / dns google.com # 测试DNS解析如果lwIP配置了DNS服务器现在你就可以在RT-Thread中编写网络应用如TCP客户端/服务器、HTTP请求等并在QEMU环境中进行测试了。5.3 源码级调试使用GDB洞察内核这是QEMU最强大的功能之一。你可以像调试桌面程序一样单步调试RT-Thread内核代码。以调试模式启动QEMU 在启动命令中加入-s -S参数。qemu-system-arm -M vexpress-a9 -kernel rtthread.elf -serial stdio -sd sd.bin -s -S-S在启动时冻结CPU等待调试器连接。-s是-gdb tcp::1234的简写在TCP的1234端口开启一个GDB服务器。启动GDB并连接 打开另一个终端进入RT-Thread源码目录启动arm-none-eabi-gdb并加载带有调试符号的elf文件。arm-none-eabi-gdb rtthread.elf在GDB界面中执行以下命令(gdb) target remote localhost:1234 # 连接到QEMU的GDB服务器 (gdb) b rtthread_startup # 在RT-Thread启动函数处设置断点 (gdb) c # 继续执行会停在断点处 (gdb) n # 单步执行 (gdb) p variable_name # 打印变量值 (gdb) bt # 查看调用栈现在你就可以逐行跟踪RT-Thread的初始化流程、线程切换、中断处理等核心过程了。这对于理解RT-Thread内核工作原理或者定位一些极其诡异的bug来说是无价之宝。6. 从模拟到真实QEMU学习的价值延伸在QEMU上玩转RT-Thread之后你获得的技能可以无缝迁移到真实的硬件开发中。内核与API熟悉度你在QEMU上使用的线程创建(rt_thread_create)、信号量(rt_semaphore)、消息队列(rt_mq)、设备驱动框架(rt_device)等API与在真实硬件上完全一致。你的应用程序代码大部分可以直接复用。构建系统与配置系统scons和menuconfig的使用流程一模一样。你学会了如何裁剪系统、添加软件包这套方法论对任何RT-Thread BSP都适用。驱动开发思维虽然QEMU的驱动是虚拟的但其注册到RT-Thread设备框架的流程rt_hw_xxx_init-rt_device_register和真实驱动是相通的。你理解了设备如何被应用层发现和调用。调试方法GDB调试的思路是通用的。在真实硬件上你可能使用J-Link或ST-Link配合OpenOCD来搭建GDB Server但调试命令断点、单步、查看变量/内存是完全相同的。移植新BSP当你拿到一块新的真实开发板时移植RT-Thread的过程本质上就是在新的board.c中正确实现rt_hw_board_init()初始化时钟、内存、串口并根据芯片手册编写或适配各类外设驱动如GPIO、SPI、I2C、ETH等。你在QEMU BSP中看到的代码结构就是最好的参考模板。所以不要把QEMU仅仅看作一个玩具。它是一个高效的学习平台、原型验证平台和调试平台。它极大地降低了嵌入式RTOS学习的门槛和成本让你能更专注于软件逻辑和系统原理本身。当你用QEMU把RT-Thread的核心机制和开发流程摸透之后再过渡到真实的硬件开发会发现很多概念和操作都已是轻车熟路唯一需要克服的只是具体硬件的物理特性而已。
返回列表