使用QEMU安装并启动一个Ubuntu发行版
之前自己编译 QEMU 跑模拟器/虚拟机基本都是跑的一个基于 buildroot 构建的小型根文件系统,虽然也能够完成一些基础的测试,但是功能完备性上相比发行版来说还是要差很多,而且如果想添加新的软件环境也比较麻烦。因此这两天花了点时间倒腾了一下环境,把 QEMU 安装和运行发行版的流程走通,便于后续的学习和研究。
安装系统
发行版我选择的是 Ubuntu,没有什么别的原因,只是因为用的最多。而已经安装好 Ubuntu 系统的磁盘镜像可能不太好找(官网只看到 RISC-V 架构有 pre-installed 版本),因此我选择下载 iso 镜像,然后手动安装。这里可以直接选择通过清华镜像源下载:
wget https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/noble/ubuntu-24.04.3-live-server-amd64.iso
我下载的是较新的版本,因此 seabios 可能不支持,需要更现代的固件,虚拟机场景下通常是 OVMF,使用包管理工具安装一下:
sudo apt install ovmf
找到安装好的 OVMF 固件,通常在目录 /usr/share/OVMF 目录下。
最后创建一个 QEMU 的虚拟磁盘文件 QCOW2(大小自行选择,我的选择为 20G),用作虚拟机的磁盘,它相比传统文件类型的优势在于可以动态扩容。
qemu-img create -f qcow2 ubuntu-vm.qcow2 20G
最后使用下面所示的 QEMU 启动参数进行启动:
qemu-system-x86_64 \
-machine q35,accel=kvm \
-cpu host \
-m 8G \
-smp 4 \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS_4M.fd \
-cdrom ubuntu-24.04.3-live-server-amd64.iso \
-boot order=d \
-hda ubuntu-vm.qcow2 \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device e1000,netdev=net0 \
-serial mon:stdio \
-display none
其中的 -cdrom 参数相当于将装有操作系统镜像的光盘插入我们的虚拟机器 q35 上,-boot order=d 相当于系统上电进行引导时最先检查的存储设备为光盘,这个过程和我们现实中为一台裸机电脑安装操作系统如出一辙。
由于我的本机 Linux 环境为 WSL2,因此选择纯命令行安装的方式,需要进行下列额外操作将安装器的输出显示在串口:
- 启动后会出现 GRUB 菜单,选中 Try or Install Ubuntu Server,按 e 进入编辑模式。
- 找到以 linux 开头的那一行,在末尾加上
console=ttyS0,如linux /casper/vmlinuz --- console=ttyS0。 - 按 Ctrl+X 或 F10 启动。
运行系统
具体安装过程中的选项在此就不过多介绍了,安装完成后,下次启动时可以去掉 cdrom 和 boot 参数:
qemu-system-x86_64 \
-machine q35,accel=kvm \
-cpu host \
-m 8G \
-smp 4 \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS_4M.fd \
-hda ubuntu-vm.qcow2 \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device e1000,netdev=net0 \
-serial mon:stdio \
-display none
再次观察上述 QEMU 启动参数,我使用 -netdev user 指定使用用户模式网络,虚拟机能够直接通过宿主机来访问互联网。同时还通过 hostfwd=tcp::2222-:22 设置了端口转发,把宿主机的 TCP 2222 端口转发到虚拟机的 22 端口,宿主机可以通过 ssh 工具连接到虚拟机:
ssh -p 2222 <用户名>@localhost
同理,宿主机要想传输文件到虚拟机,也可以通过:
scp -P 2222 <要传输的文件> <用户名>@localhost:<目录>
而虚拟机要想传输文件回宿主机,可以先为宿主机启用 ssh 服务(如果没有启用的话):
sudo apt update
sudo apt install openssh-server
# 启用 ssh 服务
sudo systemctl start ssh
# 设置开机自动启动
sudo systemctl enable ssh
# 检查 ssh 服务运行状态
sudo systemctl status ssh
此后,虚拟机也可以直接通过 scp 向宿主机进行文件传输(无 -P 2222):
scp <要传输的文件> <用户名>@10.0.2.2:<目录>
使用自己编译的内核
如果只是进行应用程序的开发和测试的话,想必上面的环境已经够用了。但我毕竟是做操作系统相关工作的,难免要对内核代码进行修改并编译测试,因此这就涉及到另一个问题:如何在保留发行版丰富的开发环境的同时,使用自己编译的内核?这一块踩了不少坑,下面直接介绍完整流程:
首先需要编译自己的内核,得到 bzImage,这一块想必不用再介绍了。
然后是需要在内核源代码目录下生成和内核版本对应的 initramfs,得到文件 initrd.img-5.10.0:
(py312) lordaeronesz@Snow:~/LWS/510-linux$ sudo mkinitramfs -o ./initrd.img-5.10.0 5.10.0
W: Kernel configuration /boot/config-5.10.0 is missing, cannot check for zstd compression support (CONFIG_RD_ZSTD)
W: missing /lib/modules/5.10.0
W: Ensure all necessary drivers are built into the linux image!
depmod: ERROR: could not open directory /lib/modules/5.10.0: No such file or directory
depmod: FATAL: could not search modules: No such file or directory
/usr/sbin/mkinitramfs: 136: linux-version: not found
I: The initramfs will attempt to resume from /dev/sdb
I: (UUID=f10066eb-414e-4970-a3c9-9a28c963f849)
I: Set the RESUME variable to override this.
cat: /var/tmp/mkinitramfs_mpVlsQ/lib/modules/5.10.0/modules.builtin: No such file or directory
depmod: WARNING: could not open modules.order at /var/tmp/mkinitramfs_mpVlsQ/lib/modules/5.10.0: No such file or directory
depmod: WARNING: could not open modules.builtin at /var/tmp/mkinitramfs_mpVlsQ/lib/modules/5.10.0: No such file or directory
depmod: WARNING: could not open modules.builtin.modinfo at /var/tmp/mkinitramfs_mpVlsQ/lib/modules/5.10.0: No such file or directory
看到上面这么多警告信息我还以为失败了,似乎是因为没有指定内核模块的缘故,这一块先不琢磨了,能得到 initrd 就行。
由于后面要在 QEMU 启动参数中添加传递给内核的 cmdline,因此我们必须确定该发行版下根目录对应的是哪个设备目录,这里要先用 运行系统 章节介绍的启动参数启动 Ubuntu 自带的内核,查看 df -h:
lordaeronesz@flame:~$ df -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 794M 1004K 793M 1% /run
efivarfs 256K 110K 142K 44% /sys/firmware/efi/efivars
/dev/mapper/ubuntu--vg-ubuntu--lv 9.8G 5.5G 3.8G 60% /
tmpfs 3.9G 0 3.9G 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 3.9G 0 3.9G 0% /run/qemu
/dev/sda2 1.7G 101M 1.5G 7% /boot
/dev/sda1 952M 6.2M 945M 1% /boot/efi
tmpfs 794M 16K 794M 1% /run/user/1000
可以看到,根目录的对应的设备目录为 /dev/mapper/ubuntu--vg-ubuntu--lv,之所这么奇怪而不是类似 sda 这种似乎是使用了 LVM(Logical Volume Manager) 的缘故。
最后,在 QEMU 启动参数中指定我们上面得到的 initrd、kernel、root。完整的启动参数如下:
qemu-system-x86_64 \
-machine q35,accel=kvm \
-cpu host \
-m 8G \
-smp 4 \
-kernel bzImage \
-initrd initrd.img-5.10.0 \
-drive if=pflash,format=raw,readonly=on,file=/usr/share/OVMF/OVMF_CODE_4M.fd \
-drive if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS_4M.fd \
-append "root=/dev/mapper/ubuntu--vg-ubuntu--lv console=ttyS0" \
-hda ubuntu-vm.qcow2 \
-netdev user,id=net0,hostfwd=tcp::2222-:22 \
-device e1000,netdev=net0 \
-serial mon:stdio \
-display none
成功启动后,通过 uname -a 查看内核信息:
lordaeronesz@flame:~$ uname -a
Linux flame 5.10.0+ #58 SMP Thu Oct 30 19:18:14 CST 2025 x86_64 x86_64 x86_64 GNU/Linux
可以看到,内核为我自行编译的 5.10 版本,而非自带的 6.8 版本:
lordaeronesz@flame:~$ uname -a
Linux flame 6.8.0-87-generic #88-Ubuntu SMP PREEMPT_DYNAMIC Sat Oct 11 09:28:41 UTC 2025 x86_64 x86_64 x86_64 GNU/Linux
说实话,我对操作系统启动这一块的内容不太熟悉,对 initrd、initramfs 等这些名词有些一知半解,所以这一块的配置才踩了那么多坑🥲。在此立个 flag,这部分内容等后续找个时间深入研究一下,再整理成博客。













