QEMU kernel not found 报错:could not load kernel 与 RISC-V BIOS 缺失的路径分诊
给 qemu-system-riscv64 挂上 -kernel 想直接引导内核,进程却立刻退出,甩下一行 could not load kernel——这是直接引导路线最常见的第一堵墙。笼统归为「内核文件没找到」会漏掉大半场景:QEMU kernel not found 报错背后至少有五种互不相干的情况,路径拼错、工作目录漂移、文件是目录或空文件、文件存在却不是 ELF,以及 -bios 另一套报错措辞。本篇是 SoC 与 QEMU 入门路线的第 22 篇,承接第 18 篇 QEMU 直接引导内核:-kernel 与 -initramfs 跑通最小 RISC-V Linux 的直挂链路,专门解决「文件加载阶段」的失败;加速器起不来见第 19 篇 KVM 不可用:三类报错的分诊、修复与 TCG 兜底,磁盘镜像打不开见第 21 篇 qcow2 镜像打不开:header too short、格式不认与 Permission denied 三类报错实测。本篇报错全部在 macOS 本机用 QEMU 11.1.2 真复现,原文与退出码逐条可对账。
本文要回答的问题
- QEMU kernel not found 报错出现时,先查路径还是先查文件内容?
- 同一条命令为什么换个目录就报
could not load kernel,回到原目录又好了? -kernel指向目录、空文件、随机字节文件,QEMU 的反应分别是什么?-bios的Unable to find the RISC-V BIOS与-kernel报错有何区别,关键词怎么分流?- 为什么垃圾内核不报错,虚拟机却异常?「不报错」为什么最危险?
先说结论:加载失败按两道关卡分诊。第一道是文件能不能打开——路径不存在、cwd 漂移、指向目录都死在这里,特征是带 No such file or directory 或 Is a directory 的系统调用原文;第二道是内容能不能加载——空文件直接拒绝,非 ELF 随机字节被静默当裸固件放行,故障推迟到启动阶段才暴露。统一动作是先 pwd/ls 核对路径与 cwd,再用 file 核对类型,最后按两套关键词对号入座。
下面这张分诊漏斗把五类故障的收敛过程压在一屏:入口都是启动即退出,越往下分支越窄,最终落到各自确定的处置动作。
读这张漏斗抓住一个要点:五类根因里只有「非 ELF 静默加载」不在启动当口报错,其余四类立刻以退出码 1 失败。先处理会报错的,再警惕不报错的。
一、故障一与故障二:路径不存在,以及相对路径的 cwd 陷阱
绝对路径拼错:系统调用原文先上浮
最直白的场景是绝对路径里根本没有这个文件,比如构建失败后 vmlinuz 没产出,或某个目录名拼错。QEMU 先打印操作系统返回的 No such file or directory,再补一行加载失败结论,退出码 1:
/Users/derekzhuo/Project/geek-doc.cn/experiments/qemu-kernel-not-found-error/build/vmlinuz-missing: No such file or directory
qemu-system-riscv64: could not load kernel '/Users/derekzhuo/Project/geek-doc.cn/experiments/qemu-kernel-not-found-error/build/vmlinuz-missing'
exit=1两行各有归属:第一行是 open() 的 errno 原样上浮,进程连文件句柄都没拿到;第二行是 QEMU 加载器的结论。排查时把第一行路径复制出来 ls 即可——要么文件没构建出来,要么目录名拼错。
相对路径:同一条命令,换个 cwd 就失效
第二类故障隐蔽得多。脚本写相对路径 ./kernels/vmlinuz,文件真实存在,但解析依赖进程的当前工作目录。实验先在正确目录准备好内核,再钻进子目录 subdir 运行同一条命令,QEMU 在子目录下找不到该文件,报错与路径拼错完全一致:
-- 2a. 在错误 cwd(subdir)里引用 ./kernels/vmlinuz,文件实际在上一级 --
./kernels/vmlinuz: No such file or directory
qemu-system-riscv64: could not load kernel './kernels/vmlinuz'
exit=1回到正确的工作目录后,同一条命令不再有任何加载报错,客户机进入默认的 OpenSBI 固件启动流程,打印 OpenSBI v1.8.1 banner:
-- 2b. 修复:回到正确 cwd 后同一条命令不再报加载错误,进入默认 OpenSBI 启动(3 秒后守卫杀掉)--
OpenSBI v1.8.1
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
qemu-system-riscv64: terminating on signal 15 from pid 30969 (<unknown process>)最后一行需要解释:terminating on signal 15 不是 QEMU 崩溃,而是脚本守卫——QEMU 成功加载后会挂在串口持续运行,脚本后台启动它、3 秒后主动发 SIGTERM(信号 15)收尾。看到这一行恰好证明加载已通过;真正的失败都带 exit=1,被守卫杀掉的进程没有退出码可引。
根因是 shell 的相对路径规则:./ 永远相对当前 cwd 而非脚本所在目录。临时排查用 pwd 确认后 cd 回去;写脚本则在开头固定 cwd,实验仓 run.sh 的 cd "$(dirname "$0")" 让脚本无论从哪里调用都先切到自身目录。
下面的终端实录保留了故障一与故障二的原始输出,两类失败措辞一模一样,区别只在路径是绝对还是相对。
二、故障三:文件存在却加载不了的三种形态
路径关过了还有内容关。同一条 QEMU 命令只替换 -kernel 指向的文件,造出目录、0 字节空文件、随机字节文件三种对象,QEMU 的反应分成三个档次。
形态一:指向目录,Is a directory
-kernel 给的是目录时,系统调用直接拒绝,报错带中文 locale 原样输出(引号是中文全角),随后才是 QEMU 的加载失败结论:
-- 3a. -kernel 指向目录 --
rom: file ./adir : error 读取文件“./adir”时出错:Is a directory
qemu-system-riscv64: could not load kernel './adir'
exit=1常见于变量拼接事故:本该写 -kernel "$BUILD_DIR/vmlinuz",BUILD_DIR 却为空,参数落到目录上。ls -ld <路径> 一眼可辨对象类型。
形态二:0 字节空文件,只有一行结论
用 : > empty.bin 造 0 字节文件挂给 -kernel,报错更短——没有前置 rom 读取行,只有 QEMU 的一行结论,退出码同样是 1:
-- 3b. -kernel 指向 0 字节空文件 --
qemu-system-riscv64: could not load kernel './empty.bin'
exit=1文件能打开但一个字节都没有,加载器无映像可装,直接拒绝。典型来源是 touch 出目标文件却编译失败,或下载重定向创建了空文件。
形态三:非 ELF 随机字节,静默放行的隐蔽陷阱
第三种是本篇最该记住的反直觉场景。往 garbage.bin 写 512 字节随机数据——文件存在、可读、非空,但与 ELF 无关。QEMU 不但不报 could not load kernel,反而静默把它当裸固件装进内存,因没有有效内核,机器回退到默认 OpenSBI 启动并打印 banner:
-- 3c. -kernel 指向非 ELF 的随机字节:不报错,静默当裸固件加载并回退 OpenSBI(3 秒后守卫杀掉)--
OpenSBI v1.8.1
____ _____ ____ _____
/ __ \ / ____| _ \_ _|
| | | |_ __ ___ _ __ | (___ | |_) || |
qemu-system-riscv64: terminating on signal 15 from pid 30978 (<unknown process>)机制要讲透:QEMU 在 -kernel 阶段并不强制校验 ELF 格式,逻辑是「文件存在且可读就先装进去」,能按 ELF 解析就按 ELF 走,解析不了就退化为裸二进制加载,把字节原样搬到客户机内存约定地址。坏内核因此不在加载时报错,而表现为后续启动异常——没有内核日志、卡在固件 banner、跳入非法地址后静默挂起。排查只能在启动前自己验证文件内容:
file kernels/vmlinuz # 有效内核应含 ELF 字样,如 ELF 64-bit LSB executable
ls -l kernels/vmlinuz # 大小明显异常(0 字节或只有几百字节)直接判废记住这条不对称性:路径关故障大声报错,内容关的非 ELF 故障一声不吭。「没报错但虚拟机不正常」时,先怀疑 -kernel 文件的实际格式。
三、-bios 是另一套报错措辞,而且检查顺序更靠前
-bios 用于显式指定固件(RISC-V 平台上通常是 OpenSBI 的 fw_dynamic.bin),它的加载失败不走 could not load kernel 文案,搜错关键词会白耗时间。
文件缺失:Unable to find the RISC-V BIOS
-bios 指向不存在的文件时,QEMU 报的是一句完全不同的话,退出码 1:
-- 4a. -bios 文件不存在 --
qemu-system-riscv64: Unable to find the RISC-V BIOS "./fw-missing.bin"
exit=1对照记忆:-kernel 缺失的关键词是 could not load kernel,-bios 缺失是 Unable to find the RISC-V BIOS。两条线都先 ls 核对路径,但检索日志必须用各自的关键词,否则会漏掉记录。
非 ELF 文本:先警告,再回退裸加载后挂起
给 -bios 一个内容为纯文本的 note.txt,QEMU 的反应又与 -kernel 不同——它明确发出一条 warning,说明 ELF 加载失败、正在尝试按裸二进制加载,随后进程挂起,最终被守卫杀掉:
-- 4b. -bios 指向非 ELF 文本:先警告 ELF 加载失败、回退裸加载后挂起(3 秒后守卫杀掉)--
qemu-system-riscv64: warning: could not load ELF format firmware './note.txt' (Failed to load ELF). Attempting to load as binary.
qemu-system-riscv64: terminating on signal 15 from pid 30983 (<unknown process>)这条 warning 把上一节隐藏的机制摆上台面:加载器先尝试 ELF 解析,失败后回退裸加载。-kernel 对非 ELF 文件连 warning 都没有,更安静也更易被忽略。
混用实验:-bios 的检查先于 -kernel 的内容校验
最后一个混用场景把加载顺序钉死:-kernel 给垃圾文件、-bios 给缺失路径,两个问题同时存在,QEMU 只报 -bios 的错误:
-- 4c. 混用:-kernel 给垃圾文件、-bios 给缺失路径,-bios 的错误优先报出 --
qemu-system-riscv64: Unable to find the RISC-V BIOS "./fw-missing.bin"
exit=1这说明 -bios 的存在性检查先于 -kernel 的内容校验。修报错要按检查顺序逐层推进,眼前的 -bios 错误修掉之前,后面的 -kernel 内容问题不会浮出来,别指望一次运行暴露所有故障。
四、分流图与可执行的分诊顺序
把五条支线合成一张排错流程图,颜色按角色区分:红色是现象与根因,蓝色是排查动作,橙色是修复,绿色是复验,紫色是防复发。
读这张图抓住主干:入口先汇入蓝色的路径核对,文件打不开走左侧红色支线、橙色修路径;能打开再进 file 核对类型,内容问题走右侧支线、橙色换映像;-bios 错误按检查顺序优先处理。各支线最后回到绿色复验,再沉淀为紫色的启动前检查。
五类场景的明细见下表,按报错原文检索即可:
| 场景 | 触发方式 | 报错关键词 | 退出码 | 修复动作 |
|---|---|---|---|---|
| 绝对路径拼错 | -kernel 指向不存在的绝对路径 | No such file or directory + could not load kernel | 1 | ls 核对并改正路径 |
| cwd 漂移 | 错误目录下使用 ./ 相对路径 | 同上,路径为相对形式 | 1 | 回正确 cwd,脚本内 cd "$(dirname "$0")" |
| 指向目录 | -kernel 给了目录 | Is a directory + could not load kernel | 1 | 修正路径变量,指向文件而非目录 |
| 空文件 | -kernel 给 0 字节文件 | 仅 could not load kernel | 1 | 重新构建,排查上游编译失败 |
| 非 ELF 内容 | -kernel 给随机字节文件 | 无报错,静默回退 OpenSBI | 无(守卫杀) | file 验明 ELF 后更换有效内核 |
-bios 缺失 | -bios 指向不存在文件 | Unable to find the RISC-V BIOS | 1 | 补齐固件文件,核对路径 |
-bios 非 ELF | -bios 给文本文件 | warning: could not load ELF ... Attempting to load as binary | 无(守卫杀) | 更换有效固件 ELF |
实验环境
- 机器/系统:macOS 本机(Darwin 25.5.0)
- 工具版本:QEMU emulator version 11.1.2(
qemu-system-riscv64 --version首行输出) - 日期:2026-11-14
- 完整命令:
bash run.sh(七段实验覆盖路径拼错、cwd 漂移、目录与空文件、非 ELF 静默加载、-bios缺失与非 ELF、混用优先级;挂起场景由 3 秒守卫杀进程收尾)
代码获取
复现脚本与说明在公开仓 https://github.com/derekzhuo/geek-doc.cn/tree/main/experiments/qemu-kernel-not-found-error。克隆后进入该目录执行 bash run.sh,即可在 macOS 或 Linux 上重现本篇全部报错原文、退出码与 OpenSBI 回退启动的对照,脚本自动在 build/ 下准备各类故障文件并重建现场。
这一篇在知识体系里的位置
本篇是「直接引导链路」的排错答案块,解决内核与固件加载阶段的失败。-kernel、-initrd、-append 怎么协同把内核送进内存,见第 18 篇 QEMU 直接引导内核:-kernel 与 -initramfs 跑通最小 RISC-V Linux,本篇的分诊顺序正是那套链路的启动前检查清单;加速器层面的失败走第 19 篇 KVM 不可用:三类报错的分诊、修复与 TCG 兜底;磁盘镜像本身打不开见第 21 篇 qcow2 镜像打不开:header too short、格式不认与 Permission denied 三类报错实测,与本篇分属不同阶段。镜像操作基础在 qemu-img create/info/convert 实操,整体概念从 认识 QEMU:从零看懂虚拟机、镜像与快照 进入。
总结
QEMU kernel not found 报错按两道关卡分诊:路径关里,绝对路径拼错与 cwd 漂移共用 No such file or directory 加 could not load kernel 措辞,指向目录多一行 Is a directory,全部以退出码 1 失败,靠 pwd/ls 核对、脚本内固定 cwd 修复;内容关里,0 字节文件被直接拒绝,非 ELF 随机字节被静默当裸固件加载、回退默认 OpenSBI,不报错却启动异常,只能靠 file 在启动前验明格式。-bios 走独立文案 Unable to find the RISC-V BIOS,检查顺序先于 -kernel 内容校验。日志里的 terminating on signal 15 是守卫主动收尾而非崩溃。固定动作是先核对路径与 cwd、再核对文件类型、按两套关键词分流、修复后以 OpenSBI banner 复验,并把 file 校验写进脚本成为启动前防线。