记一次记一次如何手动编译一个linux内核并从 QEMU 测试到替换宿主内核

#### 特别声明与警告

⚠️ 警告:本文涉及修改和替换系统的核心组件(内核)。此操作具有一定风险,可能导致系统无法启动。

  • 实验环境整个过程必须在虚拟机(如 VMware WorkstationVirtualBox)中进行。虚拟机提供的“沙盒”环境,可以大胆尝试而无需担心损坏物理主机系统。本文所有操作均在 VMware Workstation 17 Pro 中完成。

  • 备份: 在进行替换内核操作前,务必为你的虚拟机创建一个快照。如果操作失误,你可以快速回滚到之前的状态。

  • 适用范围: 本文旨在用于学习和测试目的。请不要在生产环境或你无法承担风险的机器上直接操作。

  • 使用以下命令查询当前系统内核版本:

    1
    uname -r

实验目标

  1. 在 Ubuntu 24.04 中从国内镜像站下载并编译 Linux 内核源码(版本 6.16.7)。
  2. 使用 QEMU 模拟器测试新编译的内核和初始内存磁盘(initramfs),确保其能正常启动。
  3. 将经过测试的内核安装到宿主 Ubuntu 系统中,并使其成为默认启动选项。

第一步:安装必要的编译工具和依赖库

打开终端 (Ctrl+Alt+T),执行以下命令来安装所有必要的工具:

1
2
3
4
5
6
7
sudo apt update && sudo apt upgrade -y
# 编译内核和模块所需的工具
sudo apt install -y build-essential libncurses-dev libssl-dev libelf-dev bison flex dwarves zstd debhelper-compat dh-exec build-essential devscripts
# QEMU 模拟器和其它工具
sudo apt install -y qemu-system-x86 git wget cpio
# 制作安装包所需的工具
sudo apt install -y dpkg-dev

第二步:从清华大学镜像站下载 Linux 内核源码

为了避免网络问题,我们使用 wget 从国内镜像站下载压缩包。本文使用清华大学镜像源。

1
2
3
4
5
6
7
8
# 先回到当前用户的目录
cd ~

# 使用清华大学 tuna 镜像源下载 Linux 6.16.7 源码包 (.tar.gz)
wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v6.x/linux-6.16.7.tar.gz

# 解压 .gz 格式的源码包
tar -xzvf linux-6.16.7.tar.gz

第三步:配置内核 - 关键步骤:确保驱动支持

这是最关键的一步,这一步我们不仅要生成默认配置,还要确保内核支持从 initramfs 启动,这是现代 Linux 系统启动的标配

  1. 生成默认配置

    1
    2
    3
    4
    5
    # 首先进入源码目录 (注意目录名已变为 linux-6.16.7)
    cd linux-6.16.7

    # 随后生成默认配置
    make defconfig

  2. 深入配置以启用必需驱动
    运行 make menuconfig 进行更详细的检查。我们需要确保以下选项被启用:

    1
    make menuconfig

    在打开的界面中,逐项检查并确认以下选项:

    • General setup —>

      • [*] Initial RAM filesystem and RAM disk (initramfs/initrd) support **【这是非常关键的一项,必须为 *

    • Device Drivers —>

      • Block devices —>
        • <*> RAM block device support 【确保编译进内核,而不是 M】
        • (65536) Default RAM disk size (kbytes) 【保持默认或调整,64MB 通常足够】
    • [*] The Extended 4 (ext4) filesystem 【与宿主系统保持一致,通常是 ext4,如果是Btrfs或者其他的请自行更改】

    • [*] Second extended fs support 【保留 ext2/ext3 支持,这里注意,第一次切换是[M]而不是[*]】

      • SCSI device support —>

        • <*> SCSI disk support 【QEMU 的虚拟硬盘通常是 SCSI 或 VirtIO】

    • Miscellaneous filesystems —>

    • [*] SquashFS 4.0 - Squashed file system support 【initramfs 中可能用到】

    使用空格键切换选择状态:* 是编译进内核,M 是编译为模块。对于启动最核心的驱动(如 RAM disk, ext4),建议直接编译进内核(*),以避免模块加载问题。

    配置完成后,保存并退出。

第四步:开始编译内核

使用多线程编译以节省时间。这里的 $(nproc) 会自动获取你 CPU 的核心数。

1
make -j$(nproc)

这个过程的长短取决于你的CPU性能,如果你给虚拟机分配的核心数比较少可能会导致虚拟机卡顿,但是完成后应该就好了,中途如果有warning可以忽略,如果出现error请自行回头检查是否遗漏或者多选了哪些选项导致,如果完全安装本教程来的我也不知道怎么办了,反正人和代码有一个能跑就行,完成后核心产物是 arch/x86/boot/bzImage

第五步:使用 BusyBox 制作简易根文件系统 (initramfs)

我们需要一个最小的根文件系统来在 QEMU 中测试内核。

  1. 下载并编译 BusyBox

    1
    2
    3
    4
    5
    6
    cd ~
    # 下载 BusyBox (可能需要魔法,没有魔法的话也可以寻找国内镜像源或者试多几次)
    wget https://busybox.net/downloads/busybox-1.37.0.tar.bz2
    tar -xvf busybox-1.37.0.tar.bz2
    cd busybox-1.37.0
    make menuconfig

    进入 Settings —>,选中 [*] Build static binary (no shared libs),然后退出并保存

    这里有一个坑,因为 BusyBox 1.37.0 版本与当前系统的内核头文件不兼容,特别是网络工具 tc (Traffic Control) 相关的代码,这个会导致编译时报错,解决方法是禁用tc,这是最快最简单的解决方案,因为我们只是用 BusyBox 制作一个简单的 initramfs,并不需要 TC 功能

    Networking Utilities —>
    [ ] tc

    然后又开始漫长的编译环节

    1
    make -j$(nproc) && make install
  2. 设置初始化脚本和打包

    1
    cd _install

    进入该目录后输入ls命令你应该能看到这几个文件:

    bin linuxrc sbin usr

    1
    mkdir -p proc sys dev etc

    创建并编辑初始化脚本 init,内容可以自行更改,但是尽量不要出现中文:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    cat > init << EOF
    #!/bin/sh

    echo "This is the Linux kernel compiled by feng."
    echo "Mounting proc, sys, dev..."

    mount -t proc none /proc
    mount -t sysfs none /sys
    mount -t devtmpfs none /dev

    # 创建控制台设备并设置权限
    mknod -m 660 /dev/console c 5 1
    mknod -m 660 /dev/tty0 c 4 0
    mknod -m 660 /dev/ttyS0 c 4 64

    echo "Booting completed successfully."

    # 使用 setsid 启动 shell 以确保它有控制终端
    exec setsid /bin/sh -c 'exec /bin/sh </dev/ttyS0 >/dev/ttyS0 2>&1'
    EOF

    赋予执行权限并打包:

    1
    2
    3
    chmod +x init
    find . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gz
    cd ~

第六步:在 QEMU 中测试内核

这是验证我们编译的内核是否成功的重要一步。如果 QEMU 都无法启动,绝不能安装到宿主机。

1
2
3
4
5
6
qemu-system-x86_64 \
-kernel ~/linux-6.16.7/arch/x86/boot/bzImage \
-initrd ~/busybox-1.37.0/initramfs.cpio.gz \
-append "console=ttyS0 rdinit=/init quiet" \
-nographic \
-m 2G

注意 -append 参数使用了 rdinit=/init,这是专门指定 initramfs 中的初始化脚本。路径已更新为 linux-6.16.7

如果成功看到 shell 提示符 / #,说明内核编译和 initramfs 制作成功,输入uname -r可以看到linux内核的版本,右边是ubuntu的内核版本:

可以看到前三行是自定义的init脚本的输出内容,表明内核成功启动并挂载了必要的文件系统。

最后一行 /bin/sh: can't access tty; job control turned off 是正常的,特别是在这种简单的 initramfs 环境中

输入 poweroff 或按 Ctrl+A 然后 X 退出 QEMU。

这里多嘴提一句,在make menuconfig时我们是可以自定义可以通过内核配置界面设置本地版本:

  1. 运行 make menuconfig

  2. 导航到:

    1
    2
    General setup --->
    () Local version - append to kernel release
  3. 在其中输入你的自定义后缀,比如我这里:-feng-compiled

  4. 保存配置并重新编译内核即可

注意事项

  1. 修改 EXTRAVERSION 会影响内核模块的命名,所有内核模块也会带有这个后缀。
  2. 如果你已经安装了之前编译的内核到宿主机,你需要重新安装新编译的内核包。
  3. 确保你的自定义后缀不包含空格或特殊字符,最好只使用字母、数字和连字符。

完成这些步骤后,你的自定义内核就会显示你想要的版本名称了

第七步:编译并安装内核到宿主机系统

大家可以尝试使用QEMU进行冒烟测试,这样就可以放心地将内核安装到宿主机了,真机也可以,这里时间问题就不进行测试了,我们直接安装到宿主机中

  1. 编译内核并生成 Debian 安装包
    回到内核源码目录,使用以下命令来生成便于安装和管理的 .deb 包。

    1
    2
    3
    4
    5
    cd ~/linux-6.16.7
    # 清理之前的编译结果(可选)
    make clean
    # 重新编译并打包
    make -j$(nproc) bindeb-pkg

    这一步可能会报错提示缺少默默依赖包,解决方法是他说缺哪个我们就去安装哪个,直到可以编译为止:

    这个过程结束后,会在内核源码目录的上一级目录(即 ~/)生成几个 .deb 文件,例如 linux-image-6.16.7_6.16.7-1_amd64.deb

  2. 安装编译好的内核包
    使用 dpkg 命令安装生成的包,我们需要安装以下两个核心包(版本号会根据你的实际编译结果变化):

    1
    2
    cd ~
    sudo dpkg -i linux-image-6.16.7-feng-compiled_6.16.7-5_amd64.deb linux-headers-6.16.7-feng-compiled_6.16.7-5_amd64.deb`linux-image-*`: 包含内核本体和 initramfs 镜像。
    • linux-headers-*: 包含内核头文件,用于编译外部内核模块(如 NVIDIA 驱动)

    安装程序 dpkg 会自动调用 update-grub,将新内核添加到 GRUB 启动菜单中

    不出意外的话应该会如图所示:

第八步:重启并进入新内核

  1. 重启系统

    1
    sudo systemctl reboot
  2. 选择新内核启动
    在 GRUB 启动菜单出现时,迅速按下 Shift 键(对于 BIOS 启动)或 Esc 键(对于 UEFI 启动)以显示菜单选项。
    选择 “Advanced options for Ubuntu”,然后选择你刚编译的 “Ubuntu, with Linux 6.16.7” 启动项。

  3. 验证
    系统启动后,打开终端,输入:

    1
    uname -r

    如果输出显示 6.16.7,那么恭喜你!你已经成功使用自己手动编译的 Linux 内核了!

故障排除与回滚

  • 如果新内核无法启动: 别担心,这正是我们做快照的原因。

    • 重启虚拟机,在 GRUB 菜单中选择之前默认的旧内核启动(例如 6.14.0-xx-generic)。

    • 启动成功后,你可以卸载有问题的自定义内核:

      1
      2
      3
      # 请使用实际安装的包名,可以通过 dpkg -l | grep linux-image 查看
      sudo apt remove linux-image-6.16.7 linux-headers-6.16.7
      sudo update-grub
    • 或者,最推荐的方式:直接使用 VMware 的快照功能回滚到安装前的状态,这是最干净、最安全的方式。

总结

本次实验我们成功完成了既定目标:

  1. 安全环境:在 VMware 虚拟机中操作,风险可控
  2. 获取与配置:下载源码,并重点配置了 RAM disk 和 initramfs 等关键驱动
  3. 初步验证:使用 QEMU 模拟器进行“冒烟测试”,确保内核基本功能正常
  4. 生产安装:通过生成 .deb 包的方式,将测试通过的内核成功地安装到宿主机 Ubuntu 系统中