记一次记一次如何手动编译一个linux内核并从 QEMU 测试到替换宿主内核#### 特别声明与警告⚠️ 警告:本文涉及修改和替换系统的核心组件(内核)。此操作具有一定风险,可能导致系统无法启动。
实验环境: 整个过程必须在虚拟机(如 VMware Workstation 或 VirtualBox)中进行。虚拟机提供的“沙盒”环境,可以大胆尝试而无需担心损坏物理主机系统。本文所有操作均在 VMware Workstation 17 Pro 中完成。
备份: 在进行替换内核操作前,务必为你的虚拟机创建一个快照。如果操作失误,你可以快速回滚到之前的状态。
适用范围: 本文旨在用于学习和测试目的。请不要在生产环境或你无法承担风险的机器上直接操作。
使用以下命令查询当前系统内核版本:
1uname -r
实验目标
在 Ubuntu 24.04 中从国内镜像站下载并编译 Linux 内核源码(版本 6.16.7)。
使用 QEMU 模拟器测试新编译的内核和初始内存磁盘(initramfs),确保其能正常启动。
将经过测试的内核安装到宿主 Ubuntu 系统中,并使其成为默认启动选项。
第一步:安装必要的编译工具和依赖库打开终端 (Ctrl+Alt+T),执行以下命令来安装所有必要的工具:
1234567sudo 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 从国内镜像站下载压缩包。本文使用清华大学镜像源。
12345678# 先回到当前用户的目录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 系统启动的标配
生成默认配置:
12345# 首先进入源码目录 (注意目录名已变为 linux-6.16.7)cd linux-6.16.7# 随后生成默认配置make defconfig
深入配置以启用必需驱动:运行 make menuconfig 进行更详细的检查。我们需要确保以下选项被启用:
1make 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 的核心数。
1make -j$(nproc)
这个过程的长短取决于你的CPU性能,如果你给虚拟机分配的核心数比较少可能会导致虚拟机卡顿,但是完成后应该就好了,中途如果有warning可以忽略,如果出现error请自行回头检查是否遗漏或者多选了哪些选项导致,如果完全安装本教程来的我也不知道怎么办了,反正人和代码有一个能跑就行,完成后核心产物是 arch/x86/boot/bzImage
第五步:使用 BusyBox 制作简易根文件系统 (initramfs)我们需要一个最小的根文件系统来在 QEMU 中测试内核。
下载并编译 BusyBox:
123456cd ~# 下载 BusyBox (可能需要魔法,没有魔法的话也可以寻找国内镜像源或者试多几次)wget https://busybox.net/downloads/busybox-1.37.0.tar.bz2tar -xvf busybox-1.37.0.tar.bz2cd busybox-1.37.0make menuconfig
进入 Settings —>,选中 [*] Build static binary (no shared libs),然后退出并保存
这里有一个坑,因为 BusyBox 1.37.0 版本与当前系统的内核头文件不兼容,特别是网络工具 tc (Traffic Control) 相关的代码,这个会导致编译时报错,解决方法是禁用tc,这是最快最简单的解决方案,因为我们只是用 BusyBox 制作一个简单的 initramfs,并不需要 TC 功能
Networking Utilities —> [ ] tc
然后又开始漫长的编译环节
1make -j$(nproc) && make install
设置初始化脚本和打包:
1cd _install
进入该目录后输入ls命令你应该能看到这几个文件:
bin linuxrc sbin usr
1mkdir -p proc sys dev etc
创建并编辑初始化脚本 init,内容可以自行更改,但是尽量不要出现中文:
1234567891011121314151617181920cat > init << EOF#!/bin/shecho "This is the Linux kernel compiled by feng."echo "Mounting proc, sys, dev..."mount -t proc none /procmount -t sysfs none /sysmount -t devtmpfs none /dev# 创建控制台设备并设置权限mknod -m 660 /dev/console c 5 1mknod -m 660 /dev/tty0 c 4 0mknod -m 660 /dev/ttyS0 c 4 64echo "Booting completed successfully."# 使用 setsid 启动 shell 以确保它有控制终端exec setsid /bin/sh -c 'exec /bin/sh /dev/ttyS0 2>&1'EOF
赋予执行权限并打包:
123chmod +x initfind . -print0 | cpio --null -ov --format=newc | gzip -9 > ../initramfs.cpio.gzcd ~
第六步:在 QEMU 中测试内核这是验证我们编译的内核是否成功的重要一步。如果 QEMU 都无法启动,绝不能安装到宿主机。
123456qemu-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时我们是可以自定义可以通过内核配置界面设置本地版本:
运行 make menuconfig
导航到:
12General setup ---> () Local version - append to kernel release
在其中输入你的自定义后缀,比如我这里:-feng-compiled
保存配置并重新编译内核即可
注意事项
修改 EXTRAVERSION 会影响内核模块的命名,所有内核模块也会带有这个后缀。
如果你已经安装了之前编译的内核到宿主机,你需要重新安装新编译的内核包。
确保你的自定义后缀不包含空格或特殊字符,最好只使用字母、数字和连字符。
完成这些步骤后,你的自定义内核就会显示你想要的版本名称了
第七步:编译并安装内核到宿主机系统大家可以尝试使用QEMU进行冒烟测试,这样就可以放心地将内核安装到宿主机了,真机也可以,这里时间问题就不进行测试了,我们直接安装到宿主机中
编译内核并生成 Debian 安装包:回到内核源码目录,使用以下命令来生成便于安装和管理的 .deb 包。
12345cd ~/linux-6.16.7# 清理之前的编译结果(可选)make clean# 重新编译并打包make -j$(nproc) bindeb-pkg
这一步可能会报错提示缺少默默依赖包,解决方法是他说缺哪个我们就去安装哪个,直到可以编译为止:
这个过程结束后,会在内核源码目录的上一级目录(即 ~/)生成几个 .deb 文件,例如 linux-image-6.16.7_6.16.7-1_amd64.deb。
安装编译好的内核包:使用 dpkg 命令安装生成的包,我们需要安装以下两个核心包(版本号会根据你的实际编译结果变化):
12cd ~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 启动菜单中
不出意外的话应该会如图所示:
第八步:重启并进入新内核
重启系统:
1sudo systemctl reboot
选择新内核启动:在 GRUB 启动菜单出现时,迅速按下 Shift 键(对于 BIOS 启动)或 Esc 键(对于 UEFI 启动)以显示菜单选项。选择 “Advanced options for Ubuntu”,然后选择你刚编译的 “Ubuntu, with Linux 6.16.7” 启动项。
验证:系统启动后,打开终端,输入:
1uname -r
如果输出显示 6.16.7,那么恭喜你!你已经成功使用自己手动编译的 Linux 内核了!
故障排除与回滚
如果新内核无法启动: 别担心,这正是我们做快照的原因。
重启虚拟机,在 GRUB 菜单中选择之前默认的旧内核启动(例如 6.14.0-xx-generic)。
启动成功后,你可以卸载有问题的自定义内核:
123# 请使用实际安装的包名,可以通过 dpkg -l | grep linux-image 查看sudo apt remove linux-image-6.16.7 linux-headers-6.16.7sudo update-grub
或者,最推荐的方式:直接使用 VMware 的快照功能回滚到安装前的状态,这是最干净、最安全的方式。
总结本次实验我们成功完成了既定目标:
安全环境:在 VMware 虚拟机中操作,风险可控
获取与配置:下载源码,并重点配置了 RAM disk 和 initramfs 等关键驱动
初步验证:使用 QEMU 模拟器进行“冒烟测试”,确保内核基本功能正常
生产安装:通过生成 .deb 包的方式,将测试通过的内核成功地安装到宿主机 Ubuntu 系统中