不需要你自己从头编写 U-Boot 代码。U-Boot 是一个开源的引导加载程序,官方已经提供了针对 QEMU 虚拟机(包括 ARM64 的 virt 机器)的默认配置。你只需要下载源码、进行配置和编译即可。
要将你之前的"直接内核启动"改造为"U-Boot 引导启动",你可以按照以下步骤操作:
1. 编译 U-Boot
首先,你需要获取 U-Boot 的源码并编译出适用于 QEMU ARM64 的固件(u-boot.bin)。
bash
# 克隆 U-Boot 源码(这里以 v2022.04 版本为例)
git clone https://gitee.com/mirrors/u-boot.git -b v2022.04 u-boot-2022.04
cd u-boot-2022.04
# 设置交叉编译工具链前缀(根据你的环境调整)
export CROSS_COMPILE=aarch64-linux-gnu-
# 使用 QEMU ARM64 的默认配置
make qemu_arm64_defconfig
# 编译
make -j$(nproc)
编译完成后,当前目录下会生成 u-boot.bin 文件。
2. 修改 QEMU 启动命令
你需要将编译好的 u-boot.bin 通过 -bios 参数传给 QEMU,同时挂载包含内核和根文件系统的磁盘镜像。假设你的根文件系统镜像为 rootfs.img(里面已经包含了内核 boot/Image),启动脚本可以这样写:
bash
#!/bin/bash
qemu-system-aarch64 \
-M virt \
-cpu cortex-a72 \
-m 512M \
-nographic \
-bios u-boot-2022.04/u-boot.bin \
-drive file=rootfs.img,format=raw,id=hd0,if=none \
-device virtio-blk-device,drive=hd0
3. 在 U-Boot 中手动引导内核
运行上述脚本后,QEMU 会加载 U-Boot 并停留在 U-Boot 的命令行界面。此时,你需要手动输入命令让 U-Boot 去读取磁盘并启动内核:
text
# 1. 从 virtio 磁盘的 /boot 目录加载内核到内存地址 0x40200000
U-Boot# load virtio 0 0x40200000 boot/Image
# 2. 设置传递给 Linux 内核的启动参数(如根文件系统位置和控制台)
U-Boot# setenv bootargs "root=/dev/vda rw console=ttyAMA0"
# 3. 从指定内存地址启动 ARM64 内核
U-Boot# booti 0x40200000 - 0x40000000
总结 :
通过这种方式,启动流程变为了:QEMU 加载 U-Boot -> U-Boot 读取磁盘加载内核 -> 内核启动。虽然步骤变多了,但这能让你更深入地理解真实嵌入式设备中系统启动的每一个环节。
rootfs.img怎么制作, 为什么有 driver 和 device两个参数
针对你提出的两个问题,以下是详细的解答:
一、 rootfs.img 是如何制作的?
制作根文件系统镜像(rootfs.img)通常分为"准备文件系统内容"和"打包为镜像文件"两个阶段。以下是使用 Linux 原生工具链的标准制作流程:
1. 准备根文件系统目录
首先,你需要在宿主机上准备一个包含完整目录结构(如 /bin, /sbin, /etc, /lib 等)的文件夹(例如命名为 rootfs_staging)。你可以通过交叉编译 BusyBox 或使用 Buildroot/Yocto 等自动化构建工具来生成这些基础文件。
注意:必须确保目录内文件的拥有者和权限正确(通常需要使用 fakeroot 或 sudo 环境),否则系统启动后会因为权限问题无法正常运行。
2. 制作镜像文件(dd + mkfs.ext4 组合)
使用 dd 命令创建一个指定大小的空文件,然后使用 mkfs.ext4 将其格式化为 Ext4 文件系统,接着挂载并将准备好的文件拷贝进去。具体命令示例如下:
bash
# 1. 创建一个 512MB 的全零空文件
dd if=/dev/zero of=rootfs.img bs=1M count=512 status=progress
# 2. 在该文件上创建 Ext4 文件系统
sudo mkfs.ext4 -F rootfs.img
# 3. 创建临时挂载点并挂载
mkdir -p mnt_point
sudo mount -o loop rootfs.img mnt_point
# 4. 将准备好的根文件系统拷贝进去(-a 参数保留权限和时间戳)
sudo cp -a rootfs_staging/* mnt_point/
# 5. 卸载镜像(这一步会强制写入所有缓存,至关重要)
sudo umount mnt_point
# 6. 检查文件系统并调整大小为实际内容的最小值(节省空间)
sudo e2fsck -p -f rootfs.img
sudo resize2fs -M rootfs.img
通过上述步骤,你就得到了一个可以直接挂载给 QEMU 使用的 rootfs.img 文件。
二、 为什么 QEMU 启动时有 -drive 和 -device 两个参数?
在 QEMU 的架构设计中,存储设备的定义被拆分成了后端(Backend)和前端(Frontend),这两个参数正是分别对应这两部分:
1. -drive 参数:定义存储后端(Backend)
-drive 负责定义实际的存储介质(即"厨师"的角色),它关注的是数据如何被存储和读取。
- 它指定了磁盘镜像文件的路径(
file=)、镜像格式(format=raw/qcow2)以及缓存模式(cache=)等。 - 当使用
-drive if=none时,表示只定义后端存储,不直接连接到前端设备,必须配合-device使用。
2. -device 参数:定义存储前端(Frontend)
-device 负责定义虚拟机内部看到的虚拟硬件设备(即"服务员"的角色),它关注的是 Guest 操作系统如何与硬件交互。
- 它指定了虚拟硬件的类型(如
virtio-blk-device表示高性能 VirtIO 磁盘,ide-hd表示传统 IDE 硬盘)。 - 通过
drive=hd0参数,前端设备会与-drive定义的后端存储进行绑定。
3. 为什么要分离前后端?
这种前后端分离的设计带来了极大的灵活性。一个后端可以被多个前端复用,同时你可以自由组合硬件和存储。例如,你可以用 -drive 定义一个 qcow2 格式的镜像文件,然后用 -device 将其挂载为高性能的 VirtIO 磁盘,或者挂载为传统的 SCSI 硬盘,从而实现对虚拟硬件的细粒度控制。
-drive if=none --这里如果不是none呢,应该怎么写
如果在 -drive 参数中不使用 if=none,你可以直接指定 QEMU 支持的其他虚拟硬件接口类型。常见的选项包括 virtio、ide 和 scsi 等。
当你直接指定了这些接口类型时,QEMU 会自动将后端存储(-drive)与对应的前端虚拟硬件(-device)绑定在一起,因此你不再需要 单独编写 -device 参数。
以下是几种常见的替代写法:
1. 使用 virtio 接口(推荐,性能较高)
bash
-drive file=rootfs.img,format=raw,id=hd0,if=virtio
说明:这种写法等同于你之前使用的 -drive if=none ... -device virtio-blk-device,drive=hd0 的组合。
2. 使用 ide 接口(模拟传统 IDE 硬盘)
bash
-drive file=rootfs.img,format=raw,id=hd0,if=ide
3. 使用 scsi 接口(模拟 SCSI 硬盘)
bash
-drive file=rootfs.img,format=raw,id=hd0,if=scsi
总结对比:
- 分离写法(
if=none+-device):前后端解耦,配置更灵活,适合需要精细控制硬件拓扑的场景。 - 合并写法(直接指定
if=virtio/ide/scsi):配置更简洁,QEMU 自动完成前后端绑定,适合快速启动和常规测试。