开发环境搭建

调试内核我们使用 initramfs + kernel 构建了一个基础的 linux kernel 调试环境, 但是目前的所有操作都只会保存在 initramfs 中, 目前可用的软件很少, 没有编译器, 没有联网, 没有 apt 包管理工具, 不能持久化存储

本文介绍如何使用 qemu 模拟更加复杂的环境, 以及如何利用现有的 Linux 发行版根文件系统(比如说 Ubuntu 的基础软件包)来搭建一个可用的 linux 开发环境, 替换自己编译的内核

Ubuntu 镜像

手动构建软件包环境是十分繁琐的, 可以直接利用现有的 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 可以让设备通过允许客体系统直接与主机通信而绕过仿真层, 以实现高效的磁盘I

qemu 全部支持的设备模拟见下文 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])

也可以作为模块编译, 不过建议编译进内核比较省事

User 模式

启动 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

然后就可以正常联网使用了, 再次启动也会自动联网

tap 模式

[!WARNING] 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

此时发现该磁盘有两个分区, 第二个分区为根分区, 所以应该跳过第一个分区, 找到对应的偏移量开始挂载

20240613182359

偏移量计算为 (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

参考

zood