使用QEMU启动自编译Linux内核并挂载ext4实验盘
本文记录一套操作流程:使用~/workspace/Linux/linux里编出来的bzImage,通过QEMU启动一个最小initramfs环境,并以virtio块设备的方式外挂一块ext4格式的虚拟磁盘镜像,便于在虚拟机内进行ext4挂载、卸载与ftrace跟踪等操作。
环境约定(本仓库实际路径)
- 内核源码目录:
~/workspace/Linux/linux - 实验目录:
~/workspace/Linux/qemu-lab - 内核镜像:
~/workspace/Linux/linux/arch/x86/boot/bzImage
1. 编译内核
如果编译时遇到以下两类常见阻塞,可以用对应方式处理:
1.1 gendwarfksyms缺host依赖(dwarf.h)
报错示例:fatal error: dwarf.h: 没有那个文件或目录
两种解决方式(二选一):
- 安装依赖(推荐长期方案):安装
libdw-dev等 - 或禁用该特性(快速通过编译):
bash
cd ~/workspace/Linux/linux
./scripts/config --disable GENDWARFKSYMS
make olddefconfig
1.2 缺debian/canonical-certs.pem
报错示例:没有规则可制作目标 debian/canonical-certs.pem
处理方式(置空trusted/revocation keys):
bash
cd ~/workspace/Linux/linux
./scripts/config --set-str SYSTEM_TRUSTED_KEYS ""
./scripts/config --set-str SYSTEM_REVOCATION_KEYS ""
make olddefconfig
1.3 编译
bash
cd ~/workspace/Linux/linux
make -j"$(nproc)"
主要产物:
vmlinuxarch/x86/boot/bzImage
2. 安装宿主机工具
bash
sudo apt install qemu-system-x86 e2fsprogs busybox-static cpio gzip
说明:
qemu-system-x86是Ubuntu/Debian上的软件包名,安装后会提供qemu-system-x86_64命令用于启动内核busybox-static用于制作最小initramfs(避免动态库依赖)e2fsprogs提供mkfs.ext4
3. 准备实验目录
bash
mkdir -p ~/workspace/Linux/qemu-lab/initramfs/{bin,sbin,proc,sys,dev,mnt}
4. 制作最小initramfs(关键点:先安装busybox applets)
把busybox放进initramfs:
bash
cp -a /usr/bin/busybox ~/workspace/Linux/qemu-lab/initramfs/bin/busybox
ln -sf busybox ~/workspace/Linux/qemu-lab/initramfs/bin/sh
创建 /init:
~/workspace/Linux/qemu-lab/initramfs/init
推荐用一条命令直接生成该文件(避免手动复制粘贴漏行):
bash
cat > ~/workspace/Linux/qemu-lab/initramfs/init <<'EOF'
#!/bin/sh
mkdir -p /proc /sys /dev /mnt /bin /sbin
/bin/busybox --install -s /bin
/bin/busybox --install -s /sbin
mount -t proc proc /proc
mount -t sysfs sys /sys
mount -t devtmpfs dev /dev
mkdir -p /sys/kernel/tracing
mount -t tracefs nodev /sys/kernel/tracing
mkdir -p /sys/kernel/debug
mount -t debugfs nodev /sys/kernel/debug
echo "initramfs: mount ext4 /dev/vda -> /mnt"
mount -t ext4 /dev/vda /mnt
echo "initramfs: mount exit=$?"
ls -al /dev/vda 2>/dev/null || true
ls -al /mnt 2>/dev/null || true
exec /bin/sh
EOF
chmod +x ~/workspace/Linux/qemu-lab/initramfs/init
注意:
- 如果没有执行
busybox --install -s,会出现mount: not found(因为只有busybox本体,没有mount这个命令名的链接) - 上面这份
/init默认会在进入shell前自动尝试挂载ext4实验盘(virtio-blk的/dev/vda),便于确认环境是否正常
打包initramfs:
bash
chmod +x ~/workspace/Linux/qemu-lab/initramfs/init
cd ~/workspace/Linux/qemu-lab/initramfs
find . -print0 | cpio --null -ov --format=newc | gzip -9 > ~/workspace/Linux/qemu-lab/initramfs.cpio.gz
产物:
~/workspace/Linux/qemu-lab/initramfs.cpio.gz
5. 创建ext4实验盘镜像
bash
dd if=/dev/zero of=~/workspace/Linux/qemu-lab/ext4.img bs=1M count=1024 status=none
mkfs.ext4 -F ~/workspace/Linux/qemu-lab/ext4.img
产物:
~/workspace/Linux/qemu-lab/ext4.img(1GiB,ext4)
6. QEMU启动
bash
cd ~/workspace/Linux/linux
qemu-system-x86_64 \
-m 1024 \
-smp 2 \
-kernel arch/x86/boot/bzImage \
-initrd ~/workspace/Linux/qemu-lab/initramfs.cpio.gz \
-drive file=~/workspace/Linux/qemu-lab/ext4.img,if=virtio,format=raw \
-append "console=ttyS0 console=ttyS1 rdinit=/init" \
-nographic
-initrd用于向内核提供initramfs(初始内存根文件系统)。本实验使用rdinit=/init,因此内核启动后会执行initramfs里的/init脚本进入BusyBox环境。若不提供-initrd,就需要改成"磁盘rootfs"方案(镜像里要有/sbin/init),或把initramfs直接编进内核。 这里的"virtio-blk"指的是磁盘在QEMU里通过virtio的块设备模型提供给虚拟机(if=virtio),在虚拟机里通常体现为/dev/vda。除了virtio-blk,也可以用virtio-scsi(通常是/dev/sda)、IDE/SATA(AHCI)、NVMe等方式挂载同一个镜像;对学习ext4来说,virtio-blk/virtio-scsi都很常用。
7. 在虚拟机内挂载ext4并验证
在QEMU里:
sh
mkdir -p /mnt
mount -t ext4 /dev/vda /mnt
ls -al /mnt
预期看到 lost+found 等目录;内核日志中会出现类似:
virtio_blk ... [vda] ...EXT4-fs (vda): mounted filesystem ... r/w
8. 手动退出QEMU
如果是-nographic模式想手动退出QEMU(不关机),可在QEMU窗口按:
Ctrl-a x(退出)- 或
Ctrl-a c进入QEMU monitor,再输入quit
9. ftrace:函数调用跟踪(tracefs)
ftrace是Linux内核自带的轻量级跟踪框架,可以在不改代码/不重编译的情况下动态开启跟踪,用于观察内核函数调用、调用耗时与部分调用关系。对学习文件系统来说,它非常适合用来抓取某次mount/umount/open/read/write触发的关键路径(如ext4_fill_super、__ext4_iget、ext4_map_blocks等),从而把"现象"映射到"具体走了哪些内核函数"。
9.1 /sys/kernel/tracing可能是空的
/sys/kernel/tracing是tracefs的挂载点。如果只挂了proc/sysfs/devtmpfs,但没有挂载tracefs,这个目录会是空的,自然也就看不到current_tracer/trace/trace_pipe/set_ftrace_filter等接口文件。
本实验的 /init 已经加入了:
sh
mkdir -p /sys/kernel/tracing
mount -t tracefs nodev /sys/kernel/tracing
9.2 进入虚拟机后手动确认
sh
mount | grep -E 'tracefs|debugfs' || true
ls /sys/kernel/tracing | head
如果还没挂载,可以手动挂:
sh
mount -t tracefs nodev /sys/kernel/tracing
9.3 function tracer示例
sh
cd /sys/kernel/tracing
# 1) 清理
echo nop > current_tracer
echo 0 > tracing_on
: > trace
# 2) 设过滤函数
echo "ext4_*" > set_ftrace_filter
echo function > current_tracer
echo 1 > tracing_on
# 3) 强制触发(最稳:重新挂载)
umount /mnt
mount -t ext4 /dev/vda /mnt
# 4) 关tracing并看结果
echo 0 > tracing_on
cat trace #能看到umount/mount的函数调用流程
说明:
- 第1步把tracer设为
nop并清空trace,避免混入上一次实验的残留 - 第2步用
set_ftrace_filter限定只记录ext4_*相关函数 - 第3步选择
umount/mount是因为它们会稳定触发ext4的卸载/挂载路径(如ext4_kill_sb、ext4_put_super、ext4_fill_super、__ext4_iget等),比单纯ls更容易看到关键流程 - 第4步关闭
tracing_on后再查看trace,能得到一个完整的"操作->触发的函数链"快照;可配合grep ext4_或tail -n 200聚焦阅读
示例输出(节选自~/workspace/Linux/qemu-lab/trace.log,用于参考格式与字段含义):
text
# tracer: function
# entries-in-buffer/entries-written: 5850/5850 #P:2
# TASK-PID CPU# ||||| TIMESTAMP FUNCTION
umount-99 [000] ..... 75.535497: ext4_kill_sb <-deactivate_locked_super
umount-99 [000] ..... 75.535971: ext4_sync_fs <-sync_filesystem
mount-100 [000] ..... 84.846012: ext4_init_fs_context <-alloc_fs_context
mount-100 [000] ..... 84.846329: ext4_fill_super <-get_tree_bdev_flags
mount-100 [000] ..... 84.848419: ext4_inode_csum <-__ext4_iget
字段说明:
TASK-PID:触发本次调用的进程(这里的umount-99和mount-100对应第3步的卸载/挂载)CPU#:事件发生在哪个CPU上(如[000];多核时会交错出现)TIMESTAMP:从启动开始的时间戳(秒)FUNCTION<-CALLER:FUNCTION被调用,直接调用者是CALLER。例如ext4_kill_sb<-deactivate_locked_super表示VFS在卸载super时调用了ext4的.kill_sb回调