1. 项目概述与动机最近在搞一个软件功能模块的开发这个模块本身是平台无关的理论上在哪个硬件上跑都行。但每次调试都得把程序烧写到真实的开发板上编译、链接、烧录、上电、看日志……一套流程下来哪怕改一行代码也得等上几十秒甚至几分钟。对于需要快速迭代验证逻辑的场景这种等待简直是效率杀手。于是我就琢磨着能不能在电脑上模拟一个硬件环境来跑RT-Thread这样调试起来不就快多了吗RT-Thread官方提供了基于QEMU虚拟机的BSP板级支持包其中就有针对64位RISC-V架构的qemu-virt64-riscv。这玩意儿能在你的Linux系统里虚拟出一个标准的RISC-V开发板让你编译好的RT-Thread镜像直接在里面运行省去了所有物理硬件的操作。最初我瞄上的是qemu-virt64-aarch64模拟ARM64但发现当前RT-Thread Smart的userapps用户态应用仓库对AArch64平台的支持还不完善有些坑要填。相比之下RISC-V平台的生态支持更成熟一些所以果断转向了qemu-virt64-riscv。整个搭建流程的核心就是准备一个Linux编译环境获取RT-Thread源码配置好针对RISC-V的交叉编译工具链最后让QEMU能正确启动我们编译出来的系统镜像。下面我就把从零开始踩坑、填坑的完整过程以及其中一些容易忽略的细节给你掰开揉碎了讲清楚。2. 基础环境准备与工程初始化搭建环境的第一步是准备好“工地”。我的主力机是Windows 10为了获得一个纯净且高效的Linux开发环境我选择了在VMware Workstation Pro里安装Ubuntu 20.04 LTS。选择这个版本是因为它在长期支持期内社区资料丰富稳定性有保障。在虚拟机里分配了至少4核CPU、8GB内存和50GB的硬盘空间确保编译过程不会因为资源不足而卡顿。日常编码和终端操作我习惯用VS Code通过其强大的Remote-SSH扩展连接到Ubuntu虚拟机这样就能在Windows下享受舒适的编辑器同时在Linux环境中执行命令两全其美。接下来是获取RT-Thread的源代码。国内访问Gitee通常比GitHub更顺畅所以我们使用RT-Thread在Gitee的官方镜像仓库。我个人的习惯是先把官方仓库Fork到我自己的Gitee账号下这样方便后续做个性化的修改和跟踪。然后在Ubuntu终端里用git克隆代码。这里有个小建议虽然可以直接克隆官方仓库但为了项目管理的清晰我更喜欢为这个QEMU模拟项目建立一个独立的目录结构。# 安装必要的工具 sudo apt update sudo apt install git build-essential -y # 创建一个专门的工作目录 mkdir -p ~/workspace/rtt-smart-qemu cd ~/workspace/rtt-smart-qemu # 克隆RT-Thread源码这里以直接克隆官方仓库为例 git clone https://gitee.com/rtthread/rt-thread.git cd rt-thread进入BSP目录bsp/qemu-virt64-riscv这就是我们本次工作的“主战场”。你可以看到里面已经包含了这个虚拟板卡的基础驱动、链接脚本和QEMU启动脚本。为了不影响主代码库也为了方便自己折腾我通常会在这里复制一份配置建立一个独立的构建目录但本次我们直接在此目录下操作。注意整个环境搭建过程都在Linux命令行下完成。如果你不熟悉Linux基础命令需要先了解一下cd切换目录、ls列出文件、mkdir创建目录等操作。另外网络连接需要稳定因为过程中会下载不少工具和代码包。3. 构建系统配置与交叉编译工具链获取RT-Thread使用scons作为构建工具。首先进入BSP目录尝试编译系统会提示我们缺少scons。cd bsp/qemu-virt64-riscv scons如果提示“Command ‘scons’ not found”那就按提示安装它sudo apt install scons安装完成后我们先通过scons --menuconfig命令来配置系统。这个命令会启动一个基于Kconfig的图形化配置界面在这里你可以裁剪内核组件、配置硬件参数、启用软件包等。首次运行这个命令时它会自动克隆RT-Thread的env工具和packages软件包仓库到本地这个过程需要一点时间取决于你的网络速度。scons --menuconfig配置完成后保存退出。接下来尝试编译这时很可能会遇到第一个拦路虎找不到RISC-V的交叉编译工具链。错误信息通常类似于riscv64-unknown-linux-musl-gcc: not found。这是因为编译RISC-V架构的代码需要专门的GCC工具链而我们的Ubuntu系统默认只安装了针对x86_64即我们电脑本身的GCC。RT-Thread社区提供了一个便捷的脚本来下载这些工具链。但这个脚本get_toolchain.py并没有直接包含在BSP目录里。我们需要手动创建它。脚本可以从RT-Thread的userapps仓库中找到。我们在BSP目录下创建一个tools文件夹来存放这些工具脚本。# 在bsp/qemu-virt64-riscv目录下 mkdir tools cd tools然后创建get_toolchain.py文件并将以下内容复制进去。这个脚本会根据你传入的架构参数如riscv64下载对应的工具链。#!/usr/bin/env python # -*- coding: utf-8 -*- # ... (脚本内容同用户输入此处省略以节省篇幅关键是要确保代码正确)这个脚本依赖于另一个ci.py文件。同样在tools目录下创建ci.py内容也需完整复制。实操心得直接手动编写这两个Python文件时务必注意缩进Python对缩进极其敏感建议使用文本编辑器如VS Code、Vim的代码模式来粘贴或编写避免使用记事本等可能引入格式问题的工具。一个快速的检查方法是在保存后运行python3 -m py_compile get_toolchain.py如果没有报错说明语法基本正确。文件创建好后为其添加可执行权限并运行它来下载RISC-V工具链。chmod x get_toolchain.py python3 get_toolchain.py riscv64脚本运行后会在tools目录下生成一个gnu_gcc文件夹里面就包含了我们需要的交叉编译工具链。下载的文件是一个压缩包脚本会自动解压。4. 环境变量配置与系统编译工具链下载好了但系统还不知道去哪里找它。我们需要设置环境变量告诉scons构建系统该使用哪个编译器。在BSP根目录qemu-virt64-riscv下创建一个设置环境变量的脚本例如smart_env.sh。cd .. # 回到bsp/qemu-virt64-riscv目录 vim smart_env.sh将提供的脚本内容支持arm, aarch64, riscv64, i386复制进去。这个脚本的核心逻辑是根据传入的参数设置RTT_EXEC_PATH工具链路径和RTT_CC_PREFIX编译器前缀等环境变量。创建完成后赋予执行权限并加载针对RISC-V的环境配置chmod x smart_env.sh source smart_env.sh riscv64执行source命令后终端会输出类似下面的信息表明环境变量已生效Arch riscv64 CC gcc PREFIX riscv64-unknown-linux-musl- EXEC_PATH /home/yourname/workspace/rtt-smart-qemu/rt-thread/bsp/qemu-virt64-riscv/tools/gnu_gcc/riscv64-linux-musleabi_for_x86_64-pc-linux-gnu/bin重要提示source命令的作用是在当前Shell会话中执行脚本从而改变当前终端的环境变量。如果你新开了一个终端窗口或者退出了当前会话需要重新执行一次source smart_env.sh riscv64。为了方便你也可以把这行命令加到你的~/.bashrc文件末尾这样每次打开终端都会自动设置。现在万事俱备可以开始编译了。在BSP目录下直接运行scons命令。scons如果一切顺利你会看到编译过程滚动大量的输出信息最后在bsp/qemu-virt64-riscv目录下生成一个名为rtthread.bin的文件有时也可能是rtthread.elf取决于配置这就是我们编译好的RT-Thread系统镜像。5. QEMU启动问题排查与版本升级编译成功只是第一步让它在QEMU里跑起来才是关键。BSP目录下通常有一个名为qemu-nographic.sh或类似的启动脚本。我们直接运行它./qemu-nographic.sh这时你很可能遇到启动失败的问题。在我最初尝试时遇到了两个典型错误错误一BIOS警告与立即终止qemu-system-riscv64: warning: No -bios option specified... qemu-system-riscv64: warning: This default will change... QEMU: Terminated系统打印几条警告后立刻退出。这通常是因为当前系统安装的QEMU版本太旧与BSP配置或新版RT-Thread镜像不兼容。Ubuntu 20.04默认软件源里的QEMU版本可能较低。错误二网络设备参数不支持在更新QEMU后可能还会遇到类似-device virtio-net-device,...参数不被识别的错误这是因为新版QEMU的某些设备参数或语法发生了变化。解决方案手动编译安装新版QEMU最彻底的方法是手动编译最新版的QEMU。这听起来有点复杂但跟着步骤走其实很直接。安装编译依赖sudo apt-get install git libglib2.0-dev libfdt-dev libpixman-1-dev zlib1g-dev ninja-build这些库是编译QEMU所必需的ninja-build是一个更快的构建系统。获取QEMU源码cd ~/workspace git clone https://github.com/qemu/qemu.git cd qemu注意QEMU仓库包含一些子模块如关键的子项目代码必须同步更新git submodule update --init --force --recursive配置与编译mkdir build cd build ../configure --target-listriscv64-softmmu # 只编译RISC-V系统模拟加快速度 make -j$(nproc) # -j后面跟数字表示并行编译的线程数$(nproc)会自动获取你CPU的核心数编译过程比较耗时取决于你的电脑性能可能需要10-30分钟。编译成功后在build目录下会生成qemu-system-riscv64等可执行文件。验证版本并替换./qemu-system-riscv64 --version确认版本号较新例如7.x或8.x。然后我们需要修改RT-Thread BSP目录下的启动脚本让它使用我们新编译的QEMU。用编辑器打开qemu-nographic.sh或其他启动脚本名。找到类似qemu-system-riscv64的命令行将其路径替换为你刚编译生成的二进制文件的绝对路径。例如# 将原来的 ‘qemu-system-riscv64’ 替换为 /home/yourname/workspace/qemu/build/qemu-system-riscv64同时为了解决可能的参数兼容性问题一个稳妥的做法是参考BSP目录下可能存在的README.md或官方文档中的最新启动命令。在我的实践中我暂时移除了脚本中与网络设备相关的参数-netdev user,idnet0 -device virtio-net-device,netdevnet0先确保内核能启动。修改后的脚本核心部分可能如下#!/bin/bash if [ ! -f sd.bin ]; then dd if/dev/zero ofsd.bin bs1024 count65536 fi /home/yourname/workspace/qemu/build/qemu-system-riscv64 -nographic -machine virt -m 256M -kernel rtthread.bin \ -drive ifnone,filesd.bin,formatraw,idblk0 -device virtio-blk-device,driveblk0,busvirtio-mmio-bus.0 \ -device virtio-serial-device -chardev socket,host127.0.0.1,port4321,serveron,waitoff,telneton,idconsole0 -device virtserialport,chardevconsole0避坑指南编译QEMU时如果../configure步骤报错缺少某个库请根据错误信息使用apt search和apt install安装对应的-dev包。务必确保git submodule更新完整否则编译可能失败。替换QEMU路径时使用绝对路径最可靠。如果启动后卡住无输出可以尝试在QEMU命令中增加-s -S参数然后通过GDB连接进行调试但这属于更进阶的调试手段。6. 系统启动与文件系统挂载使用修改后的脚本再次启动QEMU./qemu-nographic.sh如果一切正确你将看到一串OpenSBIRISC-V平台的标准引导程序的输出紧接着是RT-Thread的启动Logo和初始化信息最后出现RT-Thread的命令行提示符例如msh /。这表明RT-Thread内核已经在QEMU模拟的RISC-V虚拟机上成功运行了退出QEMU的方法QEMU在-nographic模式下组合键CtrlA松开后紧接着按X即可安全退出虚拟机。成功启动后你可以在RT-Thread的shellmsh里输入一些命令如list_device查看设备free查看内存。但你会发现尝试使用ls命令可能会提示找不到文件或目录。这是因为默认的BSP配置可能没有挂载任何文件系统。为了让系统有一个可用的存储空间我们需要挂载文件系统。QEMU启动脚本中有一行-drive ifnone,filesd.bin,formatraw,idblk0这模拟了一个虚拟的SD卡镜像文件为sd.bin。首次运行脚本时如果不存在sd.bin脚本会用dd命令创建一个64MB的全零文件。格式化虚拟SD卡这个原始的sd.bin需要被格式化为RT-Thread支持的文件系统如FAT才能使用。注意请在QEMU虚拟机完全关闭的状态下在Linux宿主机终端中进行操作。# 首先安装工具如果未安装 sudo apt install dosfstools # 将sd.bin格式化为FAT32文件系统 mkfs.fat -F 32 sd.bin在RT-Thread中挂载RT-Thread需要相应的驱动和初始化代码来识别并挂载这个块设备。通常BSP中会有一个类似mnt.c的文件里面包含了文件系统初始化代码。你可以从其他已支持文件系统的BSP如qemu-virt64-aarch64中拷贝一份mnt.c到当前BSP的applications目录下并确保在rtconfig.h或通过menuconfig启用了RT_USING_DFS设备文件系统、RT_USING_DFS_ELMFATELM FatFs等配置选项。重新编译与验证修改配置或添加文件后需要重新执行scons --menuconfig确保配置已保存然后运行scons重新编译。再次启动QEMU如果挂载成功在RT-Thread的msh中输入ls /应该能看到目录列表你还可以用mkdir,echo等命令进行文件操作测试。7. 从RT-Thread到RT-Thread Smart的切换你可能注意到了我们目前编译运行的是RT-Thread内核态而我们的初衷是搭建RT-Thread Smart的开发环境。RT-Thread Smart简称rt-smart是RT-Thread的用户态版本它实现了内核与应用的分离更接近于传统操作系统的形态。切换到RT-Thread Smart通常不需要更换BSP而是通过修改配置来实现。具体步骤如下进入配置界面在BSP目录下运行scons --menuconfig。启用Smart配置在图形界面中使用方向键导航。通常路径是进入RT-Thread Kernel菜单。找到Kernel Device Model选项确保其被启用。返回主菜单进入RT-Thread Smart菜单可能位于较顶层或RT-Thread Components下。启用Enable RT-Thread Smart选项。调整内存布局切换到rt-smart后内存布局特别是用户态堆栈可能需要调整。这通常在board.c或专门的链接脚本中定义。对于qemu-virt64-riscv官方BSP应该已经提供了适配的配置。如果编译链接时出现地址错误需要检查linker_scripts目录下的链接脚本如link.lds中关于内存区域的划分。重新编译保存配置并退出menuconfig然后运行scons重新编译。此时生成的镜像如rtthread.elf或rtthread.bin就是RT-Thread Smart的内核。注意事项RT-Thread Smart的内核启动后会等待加载用户态应用程序。这些应用程序需要从文件系统中加载。因此确保文件系统如前文的FAT格式sd.bin已正确挂载至关重要。用户态应用的编译需要另外的SDK和工具链这涉及到RT-Thread的userapps仓库是下一步的工作。8. 常见问题与解决方案速查表在整个环境搭建过程中你可能会遇到各种各样的问题。下面我将一些典型问题及排查思路整理成表方便你快速对照解决。问题现象可能原因排查步骤与解决方案运行scons提示找不到命令scons构建工具未安装执行sudo apt install scons安装。编译时提示riscv64-unknown-linux-musl-gcc: not found交叉编译工具链未安装或环境变量未设置1. 运行python3 tools/get_toolchain.py riscv64下载工具链。2. 执行source smart_env.sh riscv64设置环境变量。3. 检查$RTT_EXEC_PATH变量是否正确指向了bin目录。执行source smart_env.sh后编译仍报同样错误环境变量未在当前Shell生效1. 确认执行了source命令且没有报错。2. 尝试新开一个终端重新进入BSP目录并执行source。3. 使用echo $RTT_EXEC_PATH命令查看变量是否已设置。QEMU启动后立即退出提示No -bios option等警告QEMU版本过旧按照本文第5节手动编译安装新版本的QEMU并修改启动脚本指向新的可执行文件。QEMU启动失败提示未知参数或设备错误启动脚本参数与新版本QEMU不兼容1. 对照QEMU官方文档或BSP最新示例更新启动脚本参数。2. 可尝试先简化脚本移除网络等非必要设备确保内核能启动。3. 使用qemu-system-riscv64 -device help查看支持的设备列表。内核启动后在QEMU中无输出或卡住内核崩溃或串口配置不对1. 检查编译的镜像是否正确rtthread.bin。2. 在QEMU命令中增加-serial stdio或-nographic确保输出到终端。3. 尝试在scons命令后加-j1单线程编译排除并行编译导致的异常。文件系统无法挂载ls命令无效1.sd.bin未格式化。2. 文件系统驱动未启用。3. 挂载代码未执行。1. 在宿主机用mkfs.fat sd.bin格式化虚拟SD卡。2. 通过menuconfig确认已启用DFS、ELM FatFs等组件。3. 检查mnt.c等初始化代码是否被编译进系统查看build文件夹下的.o文件或map文件。切换RT-Thread Smart配置后编译失败内存布局冲突或链接错误1. 检查link.lds链接脚本中内核与用户态的内存空间划分是否合理无重叠。2. 确认在menuconfig中正确选择了RT-Thread Smart相关配置项。3. 清理旧编译产物scons -c然后重新scons。运行get_toolchain.py下载很慢或失败网络连接问题或源地址变更1. 检查网络连通性。2. 尝试修改脚本中的URL使用其他镜像源如果社区提供了的话。3. 手动从RT-Thread官方资源站或第三方镜像下载对应工具链压缩包解压到tools/gnu_gcc目录下。最后再分享一个调试小技巧如果QEMU启动有问题可以尝试在命令中增加-d in_asm,exec,cpu,guest_errors -D qemu.log参数这会让QEMU将详细的执行日志和错误信息输出到qemu.log文件中对于分析复杂的启动故障非常有帮助。搭建这样的模拟器开发环境初期踩坑是难免的但一旦跑通后续的软件开发、调试效率将会得到质的提升。尤其是对于驱动开发、内核模块测试、协议栈验证等平台无关性较强的开发工作在QEMU里反复重启、测试的成本几乎为零这能节省大量等待硬件复位和烧录的时间。