在调试内核我们使用 initramfs + kernel 构建了一个基础的 linux kernel 调试环境, 但是目前的所有操作都只会保存在 initramfs 中, 目前可用的软件很少, 没有编译器, 没有联网, 没有 apt 包管理工具, 不能持久化存储
本文介绍如何使用 qemu 模拟更加复杂的环境, 以及如何利用现有的 Linux 发行版根文件系统(比如说 Ubuntu 的基础软件包)来搭建一个可用的 linux 开发环境, 替换自己编译的内核
手动构建软件包环境是十分繁琐的, 可以直接利用现有的 Linux 发行版提供的系统映像安装, 这里以 Ubuntu 为例
可以在 ubuntu server 查找到 Ubuntu 的 server 镜像 iso 文件 也有一些其他存档镜像:
下载的 iso 是光盘 CD/DVD 映像, 需要先创建一个存储介质用于安装系统
qemu-img create ubuntu.raw -f raw 20G
安装系统, 其中 -drive
-cdrom
的路径自行调整
qemu-system-x86_64 \
-m 4G \
-drive format=raw,file=disk/ubuntu.raw,if=virtio \
-cdrom iso/ubuntu-24.04-live-server-amd64.iso
具体的安装过程略过, 注意不要开启 LVM, 启用 OpenSSH
安装完成之后就不再需要 -cdrom
了, 可以直接通过磁盘启动
qemu-system-x86_64 \
-m 4G \
-drive format=raw,file=disk/ubuntu.raw,if=virtio
启动会有点慢, 耐心一些
启动之后可以使用 lsblk 看一下当前的所有磁盘, 启动 sr0 代表第一个光盘驱动器(CD-ROM Drive), vda 代表第一个 Virtio 磁盘设备
kamilu@kamilu:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sr0 11:0 1 1024M 0 rom
vda 253:0 0 20G 0 disk
├─vda1 253:1 0 1M 0 part
└─vda2 253:2 0 20G 0 part /
由于命令行中设置参数中 if(interface) 指定为
if=virtio
所以这里显示的是 vda, 如果设置为if=sd
则这里应该是 sda, 这里设置为 virtio 可以让设备通过允许客体系统直接与主机通信而绕过仿真层, 以实现高效的磁盘Iqemu 全部支持的设备模拟见下文 qemu 参数
可以看到 /dev/vda1
是用于启动的 /boot 分区, /dev/vda2
是挂载的根分区, 也是后面启动时需要手动指定的分区
使用 Linux 发行版镜像的主要目的就是借用其已经编译好的软件包环境, 通常内核则使用自己修改后重新编译的 bzImage
qemu-system-x86_64 \
-m 4G \
-kernel bzImage \
-drive format=raw,file=disk/ubuntu.raw,if=virtio \
-append "root=/dev/vda2 console=ttyS0" \
-nographic -no-reboot -d guest_errors
-d guest_errors
是 QEMU 的一个调试选项,用于记录和报告客户机操作系统中的错误, 这些错误信息会被输出到 QEMU 的日志中,以便开发人员或系统管理员进行调试和问题排查.
如果设置了 kernel 参数那么就会使用新内核而不是当前 ubuntu.raw 中的内核, 相当于不再使用 /dev/vda1
的 /boot 分区, 不会读取其中 /boot/grub/grub.cfg
的配置文件, 因此需要手动指定挂载的根分区所在的位置
启动的时候可能会出现如下报错: VFS: Unable to mount root fs on unknown wn-block(0,0)
可能有两个原因, 一个是因为现在使用的是 qemu 的虚拟机环境, 所以需要内核也支持虚拟化的设备/网络/PCI模拟, 需要在内核选项中开启如下的配置
CONFIG_EXT4_FS=y
CONFIG_XFS_FS=y
CONFIG_JBD2=y
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BALLOON=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_NET=y
CONFIG_VIRTIO=y
CONFIG_VIRTIO_RING=y
正常来说使用
defconfig
的应该都已经开启了, 如果没有可以在 menuconfig 中手动搜索一下开启
不出意外的话就可以正常进入了
进去之后发现没有彩色输出, 修改 .bashrc, 在开头加上
export TERM=xterm-256color
qemu 的网络配置比较复杂, 可以做很多模式的连接, 这里简单介绍一下最基础的联网配置, 至少可以用 apt 下载软件包了
大部分情况我们需要使用自己编译的内核, 在编译内核的时候选择 E1000 网卡驱动
-> Device Drivers
-> Network device support (NETDEVICES [=y])
-> Ethernet driver support (ETHERNET [=y])
-> Intel devices (NET_VENDOR_INTEL [=y])
-> Intel(R) PRO/1000 Gigabit Ethernet support (E1000 [=y])
也可以作为模块编译, 不过建议编译进内核比较省事
启动 qemu 时添加如下参数
qemu-system-x86_64 \
-m 4G \
-kernel /home/kamilu/klinux/arch/x86/boot/bzImage \
-drive format=raw,file=disk/ubuntu.raw \
-append "root=/dev/sda2 console=ttyS0" \
-nographic -no-reboot -d guest_errors -serial mon:stdio \
-netdev user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22 -device e1000,netdev=net0
查看网络配置, 不出意外的话网卡名应该是 enp0s3, 如果不是的话下面对应的名字也替换一下
kamilu@ubuntu2404:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
...
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
...
3: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/sit 0.0.0.0 brd 0.0.0.0
其中 enp0s3 网卡还并未配置, 创建一个网卡配置文件
sudo vim /etc/netplan/01-netcfg.yaml
写入如下内容, 需要注意的是 YAML 对缩进非常敏感, 直接复制进去可能会有缩进问题, 建议手打
network:
version: 2
ethernets:
enp0s3:
addresses:
- 10.0.2.15/24
routes:
- to: default
via: 10.0.2.2
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4
修改文件权限
sudo chmod 600 /etc/netplan/01-netcfg.yaml
应用 Netplan 配置
sudo netplan apply
此时网络就已经完成了配置, 再次查看网络配置
kamilu@ubuntu2404:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: enp0s3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:12:34:56 brd ff:ff:ff:ff:ff:ff
inet 10.0.2.15/24 brd 10.0.2.255 scope global enp0s3
valid_lft forever preferred_lft forever
inet6 fec0::5054:ff:fe12:3456/64 scope site dynamic mngtmpaddr noprefixroute
valid_lft 86274sec preferred_lft 14274sec
inet6 fe80::5054:ff:fe12:3456/64 scope link
valid_lft forever preferred_lft forever
3: sit0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/sit 0.0.0.0 brd 0.0.0.0
然后就可以正常联网使用了, 再次启动也会自动联网
WARNING存疑, 还有问题! 不要用!
一般的网络配置建议直接使用 user
-netdev user 模式适用于简单的网络需求,但如果你需要更复杂的网络配置或完整的桥接网络连接,可以考虑使用桥接模式.
首先在你的主机上创建 tap0 接口,并将其添加到一个网桥中
sudo ip tuntap add tap0 mode tap user $(whoami)
sudo ip link set tap0 up
sudo brctl addbr br0
sudo brctl addif br0 tap0
sudo dhclient br0
其中最后一步可能会卡住, 通常是因为网桥 br0
没有正确配置或没有有效的网络接口连接, 可以尝试为网桥 br0
手动分配静态 IP 地址,避免依赖 DHCP:
sudo ip addr add 192.168.1.100/24 dev br0 # 替换为你网络的 IP 地址段
sudo ip link set br0 up
要配置桥接网络,修改启动参数
qemu-system-x86_64 \
-m 4G \
-kernel /home/kamilu/klinux/arch/x86/boot/bzImage \
-drive format=raw,file=disk/ubuntu.raw \
-append "root=/dev/sda2 console=ttyS0" \
-nographic -no-reboot -d guest_errors -serial mon:stdio \
-netdev tap,id=net0,ifname=tap0,script=no,downscript=no -device e1000,netdev=net0
有时候我们希望添加一些在磁盘镜像中添加一些内容,
如果只有一个分区直接 mount 就可以了, 如果磁盘分为了多个分区, 比如说前面的分区作为 /boot 分区, 这时候没办法直接 mount
可以先查看一下该磁盘的分区
sudo fdisk -l ubuntu.raw
此时发现该磁盘有两个分区, 第二个分区为根分区, 所以应该跳过第一个分区, 找到对应的偏移量开始挂载
偏移量计算为 (4095 + 1) * 512 = 2097152
sudo mount -o loop,offset=2097152 ubuntu.raw tmp
内核中有一些组件并不是直接编译进内核的, 还有一些内核模块作为可选项, 有的时候需要加载和卸载
我们可以编译所有模块, 并保存到 modules/ 目录下
make modules_install INSTALL_MOD_PATH=modules/
将 modules 目录下的格式如下, 其中 lib/modules/ 下包含你的内核版本+名字
└── lib
└── modules
└── 6.6.0+
├── build -> /home/kamilu/klinux
└── kernel
├── drivers
│ ├── net
│ │ └── ethernet
│ │ └── intel
│ │ └── e1000
│ └── thermal
│ └── intel
├── fs
│ └── efivarfs
└── net
├── ipv4
│ └── netfilter
└── netfilter
将该目录 copy 到磁盘镜像的 /lib/modules 下即可
如果初始创建的磁盘镜像随着使用发现空间不足, 可以比较方便的扩容
第一步将磁盘镜像文件的大小从 20GB 扩展到 50GB
qemu-img resize ubuntu.raw 50G
扩展磁盘后,还需要在虚拟机内部调整分区和文件系统以使用新增的空间, 使用 cfdisk 调整分区 /dev/sda
如果是用的 if=virtio 那么是 /dev/vda
sudo cfdisk /dev/sda
选择 /dev/sda2 分区将其扩容至 50GB
使用 df -h 查看磁盘剩余容量, 发现还是 20G
kamilu@ubuntu2404-VM:~$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/root 20G 8.2G 11G 45% /
tmpfs 95G 0 95G 0% /dev/shm
tmpfs 38G 892K 38G 1% /run
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 19G 12K 19G 1% /run/user/1000
但是 lsblk 可以看到分区信息已经更新
kamilu@ubuntu2404-VM:~$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
sda 8:0 0 50G 0 disk
├─sda1 8:1 0 1M 0 part
└─sda2 8:2 0 50G 0 part /
sr0 11:0 1 1024M 0 rom
这是因为文件系统的信息还没有更新, 查看根目录的文件系统类型(默认是ext4)
kamilu@ubuntu2404-VM:~$ df -Th
Filesystem Type Size Used Avail Use% Mounted on
/dev/root ext4 20G 8.2G 11G 45% /
tmpfs tmpfs 95G 0 95G 0% /dev/shm
tmpfs tmpfs 38G 892K 38G 1% /run
tmpfs tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs tmpfs 19G 12K 19G 1% /run/user/1000
重新更新文件系统信息
sudo resize2fs /dev/sda2
ext4 更新块信息后再次查看发现磁盘大小正常
kamilu@ubuntu2404-VM:~$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/root 50G 8.2G 39G 18% /
tmpfs 95G 0 95G 0% /dev/shm
tmpfs 38G 892K 38G 1% /run
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 19G 12K 19G 1% /run/user/1000