QEMU 命令参数逐读:一条完整启动命令如何拼出一台虚拟机
在 QEMU 安装与第一个 RISC-V 虚拟平台 里,我们用一条只有五个参数的命令让 virt 板跑起了 OpenSBI。本篇把命令加长到一条「接近实战」的形态:显式指定内存和双核、挂上一块 qcow2 数据盘、规定控制台与退出行为,然后从左到右逐参数精读。第一次接触 QEMU 的读者建议先读 认识 QEMU:从零看懂虚拟机、镜像与快照,建立宿主机、客户机与镜像的基本直觉;本篇专注回答一个问题——命令行上的每个参数,到底改变了虚拟机的哪个部件。
本文要回答的问题
- 一条十来个参数的 QEMU 启动命令,能不能按职责拆成几组分别理解?
-m 256M和-smp 2改的是虚拟机的什么,启动日志里哪里能看到它们生效?- 挂磁盘为什么要分
-drive和-device两段写,少一段会怎样? -nographic、-serial mon:stdio、-no-reboot三个参数各自管什么,为什么自动化脚本离不开它们?- 不写某个参数时 QEMU 用什么默认值,怎么让 QEMU 自己把可选取值列出来?
一、整条命令先通读:参数的四组分工
先看本篇精读的完整命令,它在一台 MacBook Pro 上真实执行,五秒后由脚本自动结束。
qemu-system-riscv64 -machine virt -bios default -m 256M -smp 2 -nographic -serial mon:stdio -no-reboot -drive file=./output/disk.qcow2,if=none,id=drive0,format=qcow2 -device virtio-blk-device,drive=drive0参数虽多,按职责只能分成四组:决定「板子长什么样」的机器与固件组、决定「算力有多少」的计算资源组、决定「盘从哪来、怎么接」的存储组,以及决定「人怎么跟它交互、它怎么退出」的控制台组。下面这张概念卡把四组分工和组内参数一次摆清,后面的编号节就是按这张卡片的分组逐一展开。
读这张卡片时建立一个核心视角:QEMU 命令行不是一堆开关的堆砌,而是一份虚拟机硬件清单。每个参数都在向 QEMU 这个进程声明「我要一块什么样的板、板上插什么、接多少内存、控制台连到哪里」。参数解析完,一台完整的虚拟机器就在进程内存里被构造出来了。下面这张接线图把参数与它们最终落到的虚拟机部件一一对上。
图中左侧四种颜色对应参数的四组职责,右侧同色部件就是它们在虚拟机里的落点。红色决定板型与固件,蓝色划出内存和核,橙色的存储参数分两步接线——先建后端、再挂前端,紫色的控制台参数把串口拉到你眼前的终端。后续四节就沿着红、蓝、橙、紫这个顺序走。
二、机器与固件:先有一块板,再给它固件
-machine virt 是整条命令的地基。「机器(machine)」是 QEMU 对一整块开发板的建模:CPU 型号范围、物理内存起始地址、中断控制器、定时器、各外设的内存映射地址全部由它固定。virt 是 RISC-V 架构下最常用的虚拟板,不对应任何真实芯片,地址布局干净标准化,固件、U-Boot、Linux 都以它为公共底座。换一个机器参数,后面所有设备地址和可用 CPU 型号都会跟着变,所以它在习惯上永远写在命令最前面。
-bios default 解决「板上电后执行的第一段代码从哪来」。default 不是一个文件名,而是告诉 QEMU 使用安装包内置的 OpenSBI 固件,不需要自己编译或提供任何 RISC-V 二进制。本次启动日志里 OpenSBI 自报版本 v1.8.1,并打印 Firmware Base : 0x80000000、Firmware Size : 333 KB,说明固件确实被加载到了 virt 板约定的物理地址起点。固件之上的启动分层(OpenSBI → U-Boot/内核)在第 02 篇里已经建立,本篇不再展开。
三、内存与 CPU:日志里看得见的两个数字
-m 256M 给虚拟机划拨 256MiB 物理内存。这里的内存在宿主机上是惰性分配的——QEMU 进程先保留虚拟地址空间,Guest 真正写到哪一页,宿主机才分配哪一页,所以指定 256M 不代表立刻吃掉 256M 物理内存。容量后缀也有讲究,让 QEMU 自己说一遍最可靠:日志末尾 -m help 的输出写明可选后缀为 k, M, G, T, P or E,分别表示 kilo 到 exa 字节,不带后缀时默认按字节解释,写大内存数值时务必带上单位避免误读。
-smp 2 配置两个对称多处理核,在 RISC-V 术语里就是两个 HART(硬件线程)。这个参数有没有生效,不用猜,OpenSBI 横幅直接给证据:本次启动打印 Platform HART Count : 2,而第 02 篇不带 -smp 的默认配置下同位置打印的 HART Count 是 1。同一份日志的 Domain0 HARTs : 0*,1* 进一步列出两个核,星号标记的 0 号 HART 是负责上电引路的 boot HART,1 号核初始停在等待中断的状态,等固件通过软件间中断唤醒——这与真实多核 SoC 的启动语义一致。
下面这张终端实录收进了完整命令与 OpenSBI 横幅的关键部分,读的时候对照两处:命令行里的 -smp 2 与横幅里的 Platform HART Count : 2,参数与硬件事实正好闭环。
与核数相关的另一个参数本篇命令里没有出现:-cpu。不写时 virt 使用默认的 rv64 通用模型,横幅里 Boot HART Base ISA : rv64imafdch 就是它实际暴露的指令集。想知道本机 QEMU 还支持哪些 CPU 模型,可以跑 qemu-system-riscv64 -cpu help 自省,本次输出里能看到 lowrisc-ibex、max、max32、rv32、rv32e、rv32i 等型号;其中 max 表示开启当前 QEMU 支持的全部扩展,做特性实验时常换用它,跑通用系统则保持默认即可。
四、磁盘挂载:为什么 -drive 和 -device 要分两段
本次挂载的盘在启动前刚创建:一条 qemu-img create 生成 64MiB 的 qcow2 镜像,qemu-img info 显示 virtual size: 64 MiB 而 disk size: 196 KiB。两个数字的差距正是 qcow2 稀疏分配的体现——Guest 看到 64MiB 的盘,宿主机上一个数据簇都还没写,文件里只有头部和元数据。镜像创建与格式细节见 qemu-img create/info/convert 实操。
挂载动作刻意拆成两个参数,这是 QEMU 块设备模型的核心设计,值得逐段读懂。
-drive file=./output/disk.qcow2,if=none,id=drive0,format=qcow2
-device virtio-blk-device,drive=drive0-drive 定义的是块设备后端:file 指向镜像文件,format=qcow2 显式写死格式(省略时靠探测,工程实践不推荐),id=drive0 给这段后端起一个名字,最关键的是 if=none——它告诉 QEMU「只创建后端,先不要把它接到任何总线上」。于是这块盘此时存在于进程里,却对 Guest 不可见。
-device 定义的是设备前端:virtio-blk-device 在 virtio 总线上实例化一个 virtio-blk 磁盘控制器,drive=drive0 通过刚才的 id 把后端接到这个前端上。Guest 在设备枚举时看到的是一块标准 virtio 磁盘,它不需要知道背后是 qcow2 文件、raw 镜像还是网络存储。前后端分离带来的直接好处是同一个后端可以换不同前端(virtio、AHCI、USB 存储),驱动开发和性能对比时只改 -device 一段即可。如果把两段合并成老式的 -drive if=virtio 写法,QEMU 会代劳前后端绑定,但也就失去了这种显式组合的灵活性。
顺带一提,qcow2 后端还可以挂在 backing file 增量链上,多个薄 overlay 共享一个只读 base,克隆一台虚拟机的成本就是本篇日志里这个 196KiB;完整机制见 qcow2 backing file:增量链原理与秒级克隆。
五、控制台与退出行为:三个容易混在一起的参数
-nographic、-serial mon:stdio、-no-reboot 经常一起出现,但管的是三件不同的事。
-nographic 关闭 QEMU 的图形窗口与虚拟显示输出。virt 板本身没有显示器场景,串口是主要控制台,开图形窗口只会多一个无用的黑框;关掉它之后,QEMU 成为一个纯粹的终端进程。
-serial mon:stdio 把虚拟机的第一个串口重定向到当前终端的标准输入输出,并让 QEMU 监视器(monitor)复用这条通道。mon:stdio 是个复合语法:串口字符流和监视器命令走同一根管道,固件往串口写的字符直接打印在你的终端里;需要发监视器命令时用 Ctrl+A 再按 C 切换,主动退出 QEMU 则是 Ctrl+A 再按 X。横幅里的 Platform Console Device : uart8250 就是这条通道在 Guest 一侧的端点——一个 16550 兼容 UART。
-no-reboot 管的是 Guest 触发重启或进程收到终止信号时的行为:不重新引导虚拟机,而是直接退出。没有它,固件崩溃后的自动重启会让进程在脚本里无限循环。本次实验的脚本让虚拟机运行五秒后发送 SIGINT,日志尾部清楚记录了退出过程。
这张卡片收的是同一次运行脚本的尾部证据。第一行 terminating on signal 2 里的 signal 2 就是 SIGINT,进程有界退出、没有重启,正是 -no-reboot 的语义兑现;后面两段分别是脚本附带执行的 -cpu help 与 -m help 自省输出,列出了可选 CPU 模型与内存单位后缀,供调整计算资源组参数时查用。
六、参数速查表
把本篇所有参数按「作用、不写时的默认值、常用取值」汇总,日后拼命令时可以直接查。
| 参数 | 作用 | 不写时的默认行为 | 常用取值 |
|---|---|---|---|
-machine | 选定虚拟开发板,固定地址布局与设备集 | 各架构有默认板型,RISC-V 下显式写 virt 更稳妥 | virt,可加 dumpdtb=文件 导出设备树 |
-bios | 指定上电第一段固件 | 不加载外置固件,部分板型无法继续引导 | default(内置 OpenSBI)或固件路径 |
-m | 划拨虚拟物理内存容量 | virt 默认 128MiB(随版本可能调整) | 256M、1G,后缀 k/M/G/T/P/E |
-smp | 配置 HART(CPU 核)数量 | 1 个 HART | 2、4,横幅 HART Count 可核验 |
-cpu | 选择 CPU 模型与指令集扩展 | virt 默认 rv64 通用模型 | max(全开扩展)、rv64,用 -cpu help 列清单 |
-drive | 定义块设备后端,if=none 时不自动接线 | 无后端,虚拟机没有盘 | file=镜像,if=none,id=名字,format=qcow2 |
-device | 实例化前端设备并按 id 引用后端 | 无前端,后端对 Guest 不可见 | virtio-blk-device,drive=drive0 |
-nographic | 关闭图形窗口,纯终端运行 | 弹出图形窗口 | 无值开关,服务器与脚本场景必加 |
-serial | 重定向第一个串口 | 串口接图形窗口 | mon:stdio(串口与监视器复用终端) |
-no-reboot | 重启信号改为直接退出进程 | Guest 重启时 QEMU 重新引导 | 无值开关,自动化场景必加 |
这一篇在知识体系里的位置
本篇是 SoC 与 QEMU 入门路线上的「命令行精读」环节:向上承接 认识 QEMU:从零看懂虚拟机、镜像与快照 的概念底座与 QEMU 安装与第一个 RISC-V 虚拟平台 的首次上电,把那条五参数简版命令扩成了带磁盘、双核与完整控制台的实战形态。横向看,磁盘两段式挂载的后端是 qcow2 文件,镜像的创建、转换见 qemu-img create/info/convert 实操,秒级克隆与写隔离见 qcow2 backing file:增量链原理与秒级克隆。再往后,命令行上声明的这些虚拟设备会在设备树里留下契约,Linux 驱动按兼容字符串逐个匹配——理解了参数如何变成设备,设备树与驱动枚举的学习就有了具体的落点。
代码获取:本篇实验的可复现脚本(含 qcow2 数据盘创建、带超时的有界运行封装与两条参数自省命令)见公开仓库 derekzhuo/geek-doc.cn 的
experiments/qemu-command-line-annotated/目录。克隆后执行./run.sh即可复现从建盘、启动到自省输出的完整日志。
实验环境
- 机器/系统:MacBook Pro(Intel x86_64)/ macOS 26
- 工具版本:qemu-system-riscv64 11.1.2(Homebrew 安装的全系统模拟器)
- 日期:2026-11-07
- 完整命令:见公开仓库
experiments/qemu-command-line-annotated/run.sh,启动行为./run.sh一条命令复现,原始输出即本文两张终端实录的来源
总结
一条 QEMU 启动命令本质上是一份虚拟机硬件清单,按职责拆成四组即可逐段读懂:-machine virt 与 -bios default 定板型、给固件,OpenSBI 横幅里的固件基址是它们的回执;-m 256M 与 -smp 2 划拨内存和双核,Platform HART Count : 2 与默认单核的对比是参数生效的直接证据;-drive if=none 先建 qcow2 后端、-device virtio-blk-device 再挂前端,前后端靠 id 接线,64MiB 虚拟容量的盘此刻实占只有 196KiB;-nographic、-serial mon:stdio、-no-reboot 分别管窗口、串口通道和退出策略,日志里的 terminating on signal 2 印证了有界退出。拿不准可选取值时,让 QEMU 用 -cpu help、-m help 自己报出来,比死记文档更可靠。