之前自己编译 QEMU 跑模拟器/虚拟机基本都是跑的一个基于 buildroot 构建的小型根文件系统,虽然也能够完成一些基础的测试,但是功能完备性上相比发行版来说还是要差很多,而且如果想添加新的软件环境也比较麻烦。因此这两天花了点时间倒腾了一下环境,把 QEMU 安装和运行发行版的流程走通,便于后续的学习和研究。
安装系统 发行版我选择的是 Ubuntu,没有什么别的原因,只是因为用的最多。而已经安装好 Ubuntu 系统的磁盘镜像可能不太好找(官网只看到 RISC-V 架构有 pre-installed 版本),因此我选择下载 iso 镜像,然后手动安装。这里可以直接选择通过清华镜像源下载:
1 wget https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/noble/ubuntu-24.04.3-live-server-amd64.iso
我下载的是较新的版本,因此 seabios 可能不支持,需要更现代的固件,虚拟机场景下通常是 OVMF,使用包管理工具安装一下:
找到安装好的 OVMF 固件,通常在目录 /usr/share/OVMF 目录下。
最后创建一个 QEMU 的虚拟磁盘文件 QCOW2(大小自行选择,我的选择为 20G),用作虚拟机的磁盘,它相比传统文件类型的优势在于可以动态扩容。
1 qemu-img create -f qcow2 ubuntu-vm.qcow2 20G
最后使用下面所示的 QEMU 启动参数进行启动:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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 参数:
1 2 3 4 5 6 7 8 9 10 11 12 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 工具连接到虚拟机:
1 ssh -p 2222 <用户名>@localhost
同理,宿主机要想传输文件到虚拟机,也可以通过:
1 scp -P 2222 <要传输的文件> <用户名>@localhost:<目录>
而虚拟机要想传输文件回宿主机,可以先为宿主机启用 ssh 服务(如果没有启用的话):
1 2 3 4 5 6 7 8 9 10 11 12 sudo apt updatesudo apt install openssh-serversudo systemctl start sshsudo systemctl enable sshsudo systemctl status ssh
此后,虚拟机也可以直接通过 scp 向宿主机进行文件传输(无 -P 2222):
1 scp <要传输的文件> <用户名>@10.0.2.2:<目录>
使用自己编译的内核 如果只是进行应用程序的开发和测试的话,想必上面的环境已经够用了。但我毕竟是做操作系统相关工作的,难免要对内核代码进行修改并编译测试,因此这就涉及到另一个问题:如何在保留发行版丰富的开发环境的同时,使用自己编译的内核?这一块踩了不少坑,下面直接介绍完整流程:
首先需要编译自己的内核,得到 bzImage,这一块想必不用再介绍了。
然后是需要在内核源代码目录下生成和内核版本对应的 initramfs,得到文件 initrd.img-5.10.0:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 (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 directorydepmod: 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:
1 2 3 4 5 6 7 8 9 10 11 12 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。完整的启动参数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 查看内核信息:
1 2 3 lordaeronesz@flame:~$ uname -a Linux flame 5.10.0+
可以看到,内核为我自行编译的 5.10 版本,而非自带的 6.8 版本:
1 2 3 lordaeronesz@flame:~$ uname -a Linux flame 6.8.0-87-generic
说实话,我对操作系统启动这一块的内容不太熟悉,对 initrd、initramfs 等这些名词有些一知半解,所以这一块的配置才踩了那么多坑🥲。在此立个 flag,这部分内容等后续找个时间深入研究一下,再整理成博客。