编译内核

Linux 环境

这里假定读者已经拥有一个宿主 GNU/Linux 发行版作为宿主系统, 具体细节不再这里赘述了, 读者可以参考下面的一些内容来配置基础 linux 环境

您可以前往VMware官网下载其最新版

license key: MC60H-DWHD5-H80U9-6V85M-8280D

2024.6 更新: VMware 免费了!

创建虚拟机的过程比较简单这里就不赘述了, 磁盘空间给大一些40GB, 编译内核大概就需要 20GB 的中间文件, 交换区需要调整一下给8GB

这台虚拟机只是用于辅助编译的, 并不需要特别复杂的配置, 我的个人习惯是使用Vscode+SSH连接到这台虚拟机,在Windows中工作. 这部分的配置相对来说比较繁琐, 做不做都可以, 笔者这里贴出个人的一些笔记以供参考

接下来就可以正式进入编译阶段了!

编译内核

The Linux Kernel Archives 提供了所有Linux内核的历史版本,你可以使用git下载一个巨大的仓库,但笔者建议使用HTTP协议选择一个Linux版本使用即可

以6.6版本Linux内核源代码为例, 可以在 Linux Kernel 找到v6.x版本, 搜索6.6.gz 即可

本文以6.6为例, 您可以使用wget下载

wget https://mirrors.edge.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.gz
tar xf linux-6.6.tar.gz
cd linux-6.6

使用 v6.6 是因为这是最新的 LTS kernel releases

安装依赖

配合编译需要的软件程序很多, 而且特定版本的内核也可能需要特定版本的软件, 新的编译器版本也可能会带来一系列问题

另外安装软件的时候也很可能会出现依赖冲突等等问题, 内核编译的时候也会因为各种配置带来各种问题的报错, 解决起来相当之麻烦, 尤其是老版本内核...

如果实在解决不了, 建议换 Ubuntu 的版本, 换源, 换 Linux 版本

下面的过程在 wsl2(ubuntu22.04) 和 linux6.6 上是没有问题的, 大概

sudo apt update
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex libelf-dev bison vim

内核编译配置

make mrproper

该命令确保内核源代码树绝对干净,内核开发组建议在每次编译内核前运行该命令.尽管内核源代码树在解压后应该是干净的,但这并不完全可靠

make defconfig

make defconfig 命令根据当前平台的默认配置生成一个内核配置文件(通常是 .config 文件).这个默认配置文件包含了内核开发者认为适用于大多数用户的默认选项. 每种架构(例如 x86、arm 等)都有其特定的默认配置文件.这些默认配置文件为不同的硬件平台提供了一个合理的起点,使得用户在编译内核时不需要从头开始配置所有选项. 对于希望快速编译内核的用户,make defconfig 提供了一个简便的方法,可以立即生成一个基本的配置文件,而不需要手动配置每一个内核选项.这对新手尤其有用,因为手动配置内核选项可能会非常繁琐和复杂.

使用 make defconfig 生成默认配置文件后,用户可以进一步使用 make menuconfigmake xconfigmake nconfig 等命令对配置文件进行定制,以满足特定的需求.

make menuconfig

这里会打开一个图形化的界面方便控制选择配置, 需要做一些修改以便后面的调试

menuconfig 中有一个比较方便的搜索功能, 输入 / 进入一个搜索页面, 可以在里面搜索关键字找到对应的配置位置

例如搜索 GDB 可以得到如下的一些结果, 根据结果中 Location 的信息找到对应的配置位置, 再进行修改. 这里按下 4 就可以直接跳转到这个选项所在的位置来进行修改

20230518174610

或者对于一些配置选项比如 CONFIG_VIRTIO_PCI 可以直接搜索找到相关的选项并打开, 不建议直接改动 .config 文件因为可能 CONFIG 选项之间有关联关系, 最好在 menuconfig 中修改


如果后期修改了内核编译出来的内核版本号带有dirty后缀, 这是编译内核的时候自动添加的. 可以通过如下选项关闭

General setup  --->
    [ ] Automatically append version information to the version string

修改完成之后选择 save 保存退出即可, 得到 .config 配置文件

编译

make -j`nproc`

默认编译的内核同宿主机处理器架构, 如果要编译一个其他架构的内核(比如说 i386), 可以使用 ARCH 指定

make -j`nproc` ARCH=i386

编译时间不确定, 短则几分钟, 长则十几几十分钟, 取决于你的配置和开的进程数, 笔者 16 核基本几分钟就可以编译完成

20231119160927

可以得到 vmlinuxarch/x86/boot/bzImage

vmlinux和bzImage都是Linux内核编译生成的可执行文件,它们的主要区别在于它们的文件格式和用途.

当系统引导时,bzImage首先会被加载到内存中,然后被解压缩成vmlinux形式的内核映像.因为vmlinux文件比较大,而且内核启动过程中需要加载和解压缩文件,所以bzImage文件通常比vmlinux文件小很多.

简而言之, vmlinux主要用于内核开发和调试,而bzImage用于实际的Linux操作系统启动.

如果编译出错了那么可以执行如下命令清除重试

sudo make mrproper
sudo make distclean

可以选择安装内核和所有的模块, 这一步不是必须的

sudo make modules_install
sudo make install

install 不会替换掉原有的内核, 只会将内核以及符号表等放到 /boot 目录下, 并更新 /boot/grub/grub.cfg

sudo update-grub

然后可以使用新内核启动: VMware虚拟安装Ubuntu,然后切换Ubuntu内核版本

或者额外新建两个目录, 分别用于保存安装内核所需的文件和所有的模块

mkdir install
mkdir modules

make install INSTALL_PATH=install/
make modules_install INSTALL_MOD_PATH=modules/

其中 install/ 下保存着内核文件 vmlinuz-6.6.0, 系统符号表 System.map-6.6.0(并非 Linux 启动所必须的) 和编译内核的配置文件 config-6.6.0

modules/ 下保存着所有的模块, 它们可以在内核启动之后通过 insmod 动态的加载到内核中

如果要使用新的内核只需要重启电脑然后进入 GRUB 中选择新的内核版本即可

如果使用真机的话长按 F12, 如果是 Vmware 的话长按 shift 进入 grub, 对于 Ubuntu 来说选择第二个 Advanced options for Ubuntu

.config

按照上述说明编译后的内核可能会很大, 笔者编译的 linux6.6 已经有 300+ MB 的 vmlinux 和 12MB 的 bzImage 了. 但大部分情况下我们并不需要 kernel 全部的功能, 因此可以在基础之上做一些内核的裁剪

这一步并不是必要的, 视个人情况而定, 很难讲怎么样是最好的

内核配置信息比较复杂, 笔者这里按经验总结了一些 linux6.6 内核配置文件, 适用于 linux6.6 版本

读者可以根据需要直接下载对应的文件, 即跳过前面的 make menuconfig 的部分, 直接使用现成的 .config 配置文件. 可以关闭一些诸如 文件系统支持, 设备驱动, 无线网络支持, USB 支持, 图形支持, 声音等. 具体见 realease 中的信息

如果希望给你的内核起一个名字, 可以修改 CONFIG_LOCALVERSION, 该内容会加在内核版本之后

CONFIG_LOCALVERSION=""

Ubuntu 替换内核

大部分编译的内核由于没有驱动等支持, 所以只能在虚拟机上启动, 没有办法在真机启动 ubuntu. 如果希望在真机(ubuntu)启动可以下载提供 linux6.6 内核配置文件 中的 ubuntu.config 并打包为 deb

make deb-pkg -j`nproc` KCONFIG_CONFIG=ubuntu.config

打包完成后会在上级目录生成一些文件, 启动 *.deb 文件是我们需要的, 安装 headers 和 image

sudo dpkg -i linux-headers-6.6.0+_6.6.0-ga472b7d4a578-12_amd64.deb
sudo dpkg -i linux-image-6.6.0+_6.6.0-ga472b7d4a578-12_amd64.deb

此时会将 vmlinuz initrd.img config 等安装到 /boot 下, 可以使用如下 switch_kernel.sh 脚本替换内核, 可以输入需要选择内核, 此脚本将会自动修改 grub 配置并将该内核设为默认启动项

20240916222355

#!/bin/bash

# Check if the script is run as root
if [ "$(id -u)" -ne "0" ]; then
    echo "This script must be run as root" 1>&2
    exit 1
fi

# Define the directory where the kernel images are stored
KERNEL_DIR="/boot"

# List available kernel versions and assign a number to each
echo "Available kernel versions:"
kernels=($(ls ${KERNEL_DIR}/vmlinuz-*))
count=0
for kernel in "${kernels[@]}"; do
    kernel_version=$(echo $kernel | sed 's/.*\/vmlinuz-//')
    echo "[$count]: $kernel_version"
    ((count++))
done
echo ""

# Prompt the user to select a kernel version by number
read -p "Enter the number of the kernel version you want to switch to: " kernel_number

# Check if the input is a number and within the range
if ! [[ "$kernel_number" =~ ^[0-9]+$ ]] || [ "$kernel_number" -lt "0" ] || [ "$kernel_number" -ge "$count" ]; then
    echo "Invalid selection"
    exit 1
fi

# Get the kernel version based on the number
kernel_version=$(echo ${kernels[$kernel_number]} | sed 's/.*\/vmlinuz-//')
echo "Switching to kernel version: $kernel_version"

# Check if the selected kernel version exists
if [ ! -e "$KERNEL_DIR/vmlinuz-$kernel_version" ]; then
    echo "Kernel version $kernel_version does not exist"
    exit 1
fi

# Extract the menu entry for the default kernel
MID=`awk '/Advanced options.*/{print $(NF-1)}' /boot/grub/grub.cfg`
MID="${MID//\'/}"

KID=`awk -v kern="with Linux $kernel_version" '$0 ~ kern && !/recovery/ { print $(NF - 1) }' /boot/grub/grub.cfg`
KID="${KID//\'/}"

# Update GRUB configuration
sed -i "s/GRUB_DEFAULT=.*/GRUB_DEFAULT=\"$MID>$KID\"/" /etc/default/grub
update-grub

echo -e "\e[31mPlease reboot machine\e[0m"

可能遇到的问题

当然, 编译过程并不都是一帆风顺的, 遇到错误在所难免. 下面是笔者遇到的一些错误和解决措施, 读者可以修改后重新执行上述 make 编译

key 不信任的问题

.config 中的 CONFIG_SYSTEM_TRUSTED_KEY 改为 ""

或者直接执行如下命令, 效果相同

scripts/config --disable SYSTEM_TRUSTED_KEYS

unknown type name 'Elf64_Xword'

不要在 .bashrc 中改 CPATH, 可能是因为与其他软件(比如QEMU)源代码中的符号冲突了

Failed to generate BTF for vmlinux

BTF: .tmp_vmlinux.btf: pahole version v1.15 is too old, need at least v1.16
Failed to generate BTF for vmlinux
Try to disable CONFIG_DEBUG_INFO_BTF
make: *** [Makefile:1199: vmlinux] Error 1

在 .config 把 CONFIG_DEBUG_INFO_BTF 注释掉

参考

zood