如何验证错误并二分查找回归

本文档描述了如何检查某个 Linux 内核问题是否发生在开发人员当前支持的代码中——然后解释如果它是一个回归(例如,在早期版本中没有发生)的话,如何定位导致该问题的更改。

本文档面向在商用硬件上运行主流 Linux 发行版内核并希望向 Linux 上游开发人员报告内核错误的用户。尽管有此意图,但这些说明对于已经熟悉构建自己的内核的用户同样有效:它们有助于避免即使是经验丰富的开发人员偶尔也会犯的错误。

流程精髓(即“TL;DR”)

[如果您是 Linux 构建或二分查找的新手,请忽略本节,直接查看下面的分步指南。它使用与本节相同的命令,但以简明的方式进行描述。这些步骤易于遵循,并且与参考部分中的配套条目一起提到了许多替代方案、陷阱和附加方面,所有这些在您当前的情况下可能都是必不可少的。]

如果您想检查错误是否存在于开发人员当前支持的代码中,只需执行准备工作第 1 部分;在此过程中,将您日常使用的最新 Linux 内核视为“工作”内核。在以下示例中,假设它是 6.0,因此将使用其源代码来准备 .config 文件。

如果您遇到回归,请至少遵循步骤直到第 2 部分结束。然后您可以提交一份初步报告——或者继续进行第 3 部分,其中描述了如何执行二分查找以获得一份完整的回归报告。在以下示例中,假设 6.0.13 是“工作”内核,6.1.5 是第一个“损坏”的内核,因此 6.0 将被视为“良好”版本,并用于准备 .config 文件。

  • 准备工作:设置所有内容以构建您自己的内核

    # * Remove any software that depends on externally maintained kernel modules
    #   or builds any automatically during bootup.
    # * Ensure Secure Boot permits booting self-compiled Linux kernels.
    # * If you are not already running the 'working' kernel, reboot into it.
    # * Install compilers and everything else needed for building Linux.
    # * Ensure to have 15 Gigabyte free space in your home directory.
    git clone -o mainline --no-checkout \
      https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git ~/linux/
    cd ~/linux/
    git remote add -t master stable \
      https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
    git switch --detach v6.0
    # * Hint: if you used an existing clone, ensure no stale .config is around.
    make olddefconfig
    # * Ensure the former command picked the .config of the 'working' kernel.
    # * Connect external hardware (USB keys, tokens, ...), start a VM, bring up
    #   VPNs, mount network shares, and briefly try the feature that is broken.
    yes '' | make localmodconfig
    ./scripts/config --set-str CONFIG_LOCALVERSION '-local'
    ./scripts/config -e CONFIG_LOCALVERSION_AUTO
    # * Note, when short on storage space, check the guide for an alternative:
    ./scripts/config -d DEBUG_INFO_NONE -e KALLSYMS_ALL -e DEBUG_KERNEL \
      -e DEBUG_INFO -e DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT -e KALLSYMS
    # * Hint: at this point you might want to adjust the build configuration;
    #   you'll have to, if you are running Debian.
    make olddefconfig
    cp .config ~/kernel-config-working
    
  • 第 1 部分:从最新的主线代码库构建内核。

    这尤其检查了问题是否已经修复,以及后续需要通知哪些开发人员有关该问题;如果发生回归,这排除了 .config 更改是问题根源的可能性。

    1. 检出最新的主线代码

      cd ~/linux/
      git switch --discard-changes --detach mainline/master
      
    2. 构建、安装并启动内核

      cp ~/kernel-config-working .config
      make olddefconfig
      make -j $(nproc --all)
      # * Make sure there is enough disk space to hold another kernel:
      df -h /boot/ /lib/modules/
      # * Note: on Arch Linux, its derivatives and a few other distributions
      #   the following commands will do nothing at all or only part of the
      #   job. See the step-by-step guide for further details.
      sudo make modules_install
      command -v installkernel && sudo make install
      # * Check how much space your self-built kernel actually needs, which
      #   enables you to make better estimates later:
      du -ch /boot/*$(make -s kernelrelease)* | tail -n 1
      du -sh /lib/modules/$(make -s kernelrelease)/
      # * Hint: the output of the following command will help you pick the
      #   right kernel from the boot menu:
      make -s kernelrelease | tee -a ~/kernels-built
      reboot
      # * Once booted, ensure you are running the kernel you just built by
      #   checking if the output of the next two commands matches:
      tail -n 1 ~/kernels-built
      uname -r
      cat /proc/sys/kernel/tainted
      
    3. 检查此内核是否也出现问题。

  • 第 2 部分:确保“良好”内核也是“工作”内核。

    这尤其验证了精简的 .config 文件是否确实运行良好,否则使用它进行二分查找将是浪费时间。

    1. 首先检出“良好”版本的源代码

      cd ~/linux/
      git switch --discard-changes --detach v6.0
      
    2. 按照前面第 1 部分,b 节所述构建、安装并启动内核——您不必执行“du”命令,因为您已经有一个大致的估算。

    3. 确保在“损坏”内核中出现回归的功能在此内核中确实正常工作。

  • 第 3 部分:执行并验证二分查找。

    1. 检索您的“损坏”版本的源代码

      git remote set-branches --add stable linux-6.1.y
      git fetch stable
      
    2. 初始化二分查找

      cd ~/linux/
      git bisect start
      git bisect good v6.0
      git bisect bad v6.1.5
      
    3. 按照前面第 1 部分,b 节所述构建、安装并启动内核。

      如果因无关原因导致内核构建或启动失败,请运行 git bisect skip。在所有其他情况下,检查回归功能是否在新构建的内核中工作。如果工作,通过执行 git bisect good 告知 Git;如果不工作,则运行 git bisect bad

      所有这三个命令都会让 Git 检出另一个提交;然后重新执行此步骤(例如,构建、安装、启动和测试内核,然后告知 Git 结果)。如此反复,直到 Git 显示是哪个提交导致了问题。如果在此过程中磁盘空间不足,请查看下面的“补充任务:流程中及流程后的清理”部分。

    4. 完成二分查找后,请收拾一些东西

      cd ~/linux/
      git bisect log > ~/bisect-log
      cp .config ~/bisection-config-culprit
      git bisect reset
      
    5. 尝试验证二分查找结果

      git switch --discard-changes --detach mainline/master
      git revert --no-edit cafec0cacaca0
      cp ~/kernel-config-working .config
      ./scripts/config --set-str CONFIG_LOCALVERSION '-local-cafec0cacaca0-reverted'
      

    这是可选的,因为有些提交无法回滚。但如果第二个命令完美运行,请构建、安装并启动一个内核;只是这次跳过复制基础 .config 文件的第一个命令,因为这已经处理过了。

  • 补充任务:流程中及流程后的清理。

    1. 为避免在二分查找期间磁盘空间不足,您可能需要删除一些早期构建的内核。您很可能希望保留在第 1 部分和第 2 部分构建的内核一段时间,但您很可能不再需要实际二分查找(第 3 部分 c)期间测试的内核。您可以使用以下命令按构建顺序列出它们

      ls -ltr /lib/modules/*-local*
      

    然后,例如,要擦除一个自识别为“6.0-rc1-local-gcafec0cacaca0”的内核,请使用此命令

    sudo rm -rf /lib/modules/6.0-rc1-local-gcafec0cacaca0
    sudo kernel-install -v remove 6.0-rc1-local-gcafec0cacaca0
    # * Note, on some distributions kernel-install is missing
    #   or does only part of the job.
    
    1. 如果您已执行二分查找并成功验证结果,请随意删除在实际二分查找(第 3 部分 c)期间构建的所有内核;您可能希望将早期和后期构建的内核保留一两周。

  • 可选任务:稍后测试调试补丁或提议的修复

    git fetch mainline
    git switch --discard-changes --detach mainline/master
    git apply /tmp/foobars-proposed-fix-v1.patch
    cp ~/kernel-config-working .config
    ./scripts/config --set-str CONFIG_LOCALVERSION '-local-foobars-fix-v1'
    

    按照第 1 部分,b 节所述构建、安装并启动内核——但这次省略复制构建配置的第一个命令,因为这已经处理过了。

如何验证错误和二分查找回归的分步指南

本指南描述了如何设置您自己的 Linux 内核以调查您打算报告的错误或回归。您希望遵循说明的程度取决于您的问题

执行所有步骤直到第 1 部分结束,以验证您的内核问题是否存在于 Linux 内核开发人员支持的代码中。如果存在,您就可以报告错误了——除非它在早期内核版本中没有发生,因为那样您至少需要继续进行第 2 部分,以检查该问题是否符合回归条件,从而获得优先处理。根据结果,您可以准备报告错误或提交初步回归报告;而不是后者,您也可以直接继续并遵循第 3 部分,以执行二分查找,以便提交开发人员有义务处理的完整回归报告。

每个部分的步骤都阐明了流程的重要方面,而全面的参考部分包含了几乎所有步骤的额外细节。参考部分有时还会概述替代方法、陷阱以及在特定步骤可能出现的问题——以及如何重新启动。

有关如何报告 Linux 内核问题或回归的更多详细信息,请查阅报告问题,它与本文档结合使用。它特别解释了为什么即使您面临来自“稳定/长期”系列内核(例如 6.0.13)的问题,您也需要使用最新的“主线”内核(例如 6.0、6.1-rc1 或 6.1-rc6 等版本)来验证错误。

对于面临回归的用户,该文档还解释了为什么在第 2 部分之后发送初步报告可能是明智之举,因为回归及其罪魁祸首可能已经为人所知。有关实际构成回归的更多详细信息,请查阅报告回归

如果您在遵循本指南时遇到任何问题,或者有改进它的想法,请告知内核开发人员

准备工作:设置所有内容以构建您自己的内核

以下步骤为所有后续任务奠定了基础。

注意:这些说明假设您在同一台机器上进行构建和测试;如果您想在另一台系统上编译内核,请查看下面的在不同的机器上构建内核

  • 创建一份全新的备份,并备好系统修复和恢复工具,以防万一出现意外情况。

    [详情]

  • 删除所有依赖于外部开发内核驱动程序或自动构建它们的软件。这包括但不限于 DKMS、openZFS、VirtualBox 和 Nvidia 的图形驱动程序(包括 GPL 许可的内核模块)。

    [详情]

  • 在具有“安全启动”或类似解决方案的平台上,准备好一切以确保系统允许您自行编译的内核启动。在商用 x86 系统上实现这一点的最快最简单方法是在 BIOS 设置实用程序中禁用此类技术;或者,通过由 mokutil --disable-validation 启动的流程来解除其限制。

    [详情]

  • 确定本指南中被视为“良好”和“损坏”的内核版本

    • 您是否遵循本指南来验证主要开发人员关注的代码中是否存在错误?那么,将您目前经常使用的最新内核版本视为“良好”版本(例如 6.0、6.0.13 或 6.1-rc2)。

    • 您是否面临回归,例如切换到较新内核版本后出现问题或性能变差?在这种情况下,这取决于问题出现的版本范围

      • 当从稳定/长期版本(例如 6.0.13)更新到较新的主线系列(例如 6.1-rc7 或 6.1)或基于它们的稳定/长期版本(例如 6.1.5)时出现回归?那么,将您的工作内核所基于的主线版本视为“良好”版本(例如 6.0),并将第一个出现问题的版本视为“损坏”版本(例如 6.1-rc7、6.1 或 6.1.5)。请注意,此时仅假设 6.0 正常;这个假设将在第 2 部分中进行检查。

      • 当从一个主线版本(例如 6.0)切换到后续版本(例如 6.1-rc1)或基于它的稳定/长期版本(例如 6.1.5)时出现回归?那么,将最后一个正常工作的版本(例如 6.0)视为“良好”版本,将第一个出现问题的版本(例如 6.1-rc1 或 6.1.5)视为“损坏”版本。

      • 当在稳定/长期系列中更新(例如从 6.0.13 到 6.0.15)时出现回归?那么,将这些版本视为“良好”和“损坏”版本(例如 6.0.13 和 6.0.15),因为您需要在该系列中进行二分查找。

    请注意,不要混淆“良好”版本和“工作”内核;在本指南中,“工作内核”一词将指最后一个运行正常的内核。

    [详情]

  • 启动进入“工作”内核并简要使用明显有问题的特性。

    [详情]

  • 确保有足够的空闲空间来构建 Linux。您的主目录中 15 GB 通常就足够了。如果可用空间较少,请务必注意后续关于检索 Linux 源代码和处理调试符号的步骤:两者都解释了减少空间量的方法,这应该能让您在大约 4 GB 空闲空间的情况下完成这些任务。

    [详情]

  • 安装构建 Linux 内核所需的所有软件。通常您需要:‘bc’、‘binutils’(‘ld’等)、‘bison’、‘flex’、‘gcc’、‘git’、‘openssl’、‘pahole’、‘perl’以及‘libelf’和‘openssl’的开发头文件。参考部分展示了如何在各种流行的 Linux 发行版上快速安装这些软件。

    [详情]

  • 检索主线 Linux 源代码;然后进入包含这些源代码的目录,因为本指南中的所有后续命令都应从该目录执行。

    注意,以下描述如何使用完整的主线克隆来检索源代码,截至 2024 年初,这将下载大约 2.75 GB。参考部分描述了两种替代方法:一种下载小于 500 MB,另一种更适用于不可靠的互联网连接。

    执行以下命令以检索最新的主线代码库,同时准备好后续添加稳定/长期系列分支

    git clone -o mainline --no-checkout \
      https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git ~/linux/
    cd ~/linux/
    git remote add -t master stable \
      https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
    

    [详情]

  • 您之前确定为“良好”或“损坏”的版本中,是否有一个是稳定版或长期版(例如 6.1.5)?那么,请下载它所属系列的源代码(在本例中为“linux-6.1.y”)

    git remote set-branches --add stable linux-6.1.y
    git fetch stable
    
  • 开始准备内核构建配置(“.config”文件)。

    在此之前,请确保您仍在运行前面步骤中指示您启动的“工作”内核;如果您不确定,请使用 uname -r 检查当前的 kernelrelease 标识符。

    之后,检出您之前确定为“良好”版本的源代码。在以下示例命令中,假设其版本为 6.0;请注意,在此以及所有后续 Git 命令中,版本号需要加上“v”前缀

    git switch --discard-changes --detach v6.0
    

    现在创建一个构建配置文件

    make olddefconfig
    

    内核构建脚本将尝试定位正在运行内核的构建配置文件,然后根据您检出的内核源代码的需求进行调整。在此过程中,它将打印几行您需要检查的信息。

    寻找以“# using defaults found in”开头的行。它后面应该跟着一个指向“/boot/”中文件的路径,该文件包含您当前工作内核的发布标识符。如果该行而是以“arch/x86/configs/x86_64_defconfig”之类的内容继续,则构建基础设施未能找到您正在运行的内核的 .config 文件——在这种情况下,您必须手动将其放置在那里,如参考部分所述。

    如果您找不到这样的行,请寻找包含“# configuration written to .config”的行。如果是这种情况,则说明您有一个过时的构建配置。除非您打算使用它,否则请将其删除;然后再次运行“make olddefconfig”并检查它是否现在已选取正确的配置文件作为基础。

    [详情]

  • 禁用您设置中明显多余的任何内核模块。这是可选的,但对于二分查找来说尤其明智,因为它能极大地加快构建过程——至少除非上一步中选择的 .config 文件已经根据您和您的硬件需求进行了定制,在这种情况下您应该跳过此步骤。

    为了准备精简,请连接您偶尔使用的外部硬件(USB 密钥、令牌等),快速启动虚拟机,并启用 VPN。如果您自开始本指南后重启过,请确保您自启动系统以来尝试使用过导致问题的功能。只有这样才能精简您的 .config

    yes '' | make localmodconfig
    

    这里有一个陷阱,正如本步骤开头句中的“明显”和准备说明中已经暗示的那样

    “localmodconfig”目标可以很容易地禁用仅偶尔使用的功能的内核模块——例如自启动以来尚未连接的外部外设模块、尚未使用的虚拟化软件、VPN 隧道以及其他一些东西。这是因为某些任务依赖于 Linux 仅在您首次执行上述任务时才加载的内核模块。

    localmodconfig 的这个缺点不值得您为此失眠,但需要记住:如果在遵循本指南构建的内核中出现异常行为,这很可能是原因。您可以通过参考部分中概述的技巧来减少或几乎消除风险;但如果只是为了快速测试而构建内核,通常不值得投入大量精力,只要它能启动并允许正确测试导致问题的功能即可。

    [详情]

  • 确保您将构建的所有内核都使用特殊标签和唯一版本号清晰可识别

    ./scripts/config --set-str CONFIG_LOCALVERSION '-local'
    ./scripts/config -e CONFIG_LOCALVERSION_AUTO
    

    [详情]

  • 决定如何处理调试符号。

    在本文档的上下文中,通常明智的做法是启用它们,因为您很可能需要解码来自“panic”、“Oops”、“warning”或“BUG”的堆栈跟踪信息

    ./scripts/config -d DEBUG_INFO_NONE -e KALLSYMS_ALL -e DEBUG_KERNEL \
      -e DEBUG_INFO -e DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT -e KALLSYMS
    

    但是,如果您的存储空间非常紧张,您可能希望禁用调试符号

    ./scripts/config -d DEBUG_INFO -d DEBUG_INFO_DWARF_TOOLCHAIN_DEFAULT \
      -d DEBUG_INFO_DWARF4 -d DEBUG_INFO_DWARF5 -e CONFIG_DEBUG_INFO_NONE
    

    [详情]

  • 检查您是否可能需要调整其他一些内核配置选项

    • 您正在运行 Debian 吗?那么您需要通过参考部分中解释的额外调整来避免已知问题。

      [详情]。

    • 如果您想影响配置的其他方面,请立即使用您喜欢的工具进行操作。请注意,要使用‘menuconfig’或‘nconfig’等 make 目标,您需要安装 ncurses 的开发文件;对于‘xconfig’,您同样需要 Qt5 或 Qt6 头文件。

      [详情]。

  • 在最新调整后重新处理 .config 并将其存储在一个安全的地方

    make olddefconfig
    cp .config ~/kernel-config-working
    

    [详情]

第 1 部分:尝试使用最新代码库重现问题

以下步骤验证问题是否发生在开发人员当前支持的代码中。如果您遇到回归,它还会检查问题是否不是由 .config 更改引起的,因为那样报告问题将是浪费时间。 [详情]

  • 检出最新的 Linux 代码库。

    • 您的“良好”和“损坏”版本是否来自同一个稳定或长期系列?那么请检查kernel.org 的首页:如果它列出了该系列中的一个版本且没有“[EOL]”标签,请检出该系列最新版本(在以下示例中为“linux-6.1.y”)

      cd ~/linux/
      git switch --discard-changes --detach stable/linux-6.1.y
      

      如果您的系列未列出或带有“生命周期结束”标签,则表示它不受支持。在这种情况下,您可能需要检查后续系列(例如 linux-6.2.y)或主线(参见下一点)是否修复了该错误。

    • 在所有其他情况下,运行

      cd ~/linux/
      git switch --discard-changes --detach mainline/master
      

    [详情]

  • 使用您准备好的配置文件构建您的第一个内核的镜像和模块

    cp ~/kernel-config-working .config
    make olddefconfig
    make -j $(nproc --all)
    

    如果您想将内核打包成 deb、rpm 或 tar 文件,请查看参考部分中的替代方案,这显然也需要其他安装步骤。

    [详情]

  • 安装您新构建的内核。

    在此之前,请考虑检查是否仍有足够的空间用于它

    df -h /boot/ /lib/modules/
    

    目前假设 /boot/ 中有 150 MB,/lib/modules/ 中有 200 MB 就足够了;您的内核实际需要多少空间将在本指南的稍后部分确定。

    现在安装内核的模块和镜像,它们将与您的 Linux 发行版的内核并行存储

    sudo make modules_install
    command -v installkernel && sudo make install
    

    第二个命令理想情况下将处理此时所需的三个步骤:将内核镜像复制到 /boot/,生成 initramfs,以及为两者添加一个引导加载器配置条目。

    不幸的是,一些发行版(包括 Arch Linux 及其衍生版,以及许多不可变 Linux 发行版)将不执行或只执行其中一些任务。因此,您需要检查是否所有任务都已处理完毕,并手动执行那些未处理的任务。参考部分提供了更多详细信息;您的发行版文档也可能有所帮助。

    一旦您弄清了此时所需的步骤,请考虑将其记录下来:如果您将按照第 2 部分和第 3 部分的描述构建更多内核,您将不得不在执行 command -v installkernel [...] 后再次执行这些步骤。

    [详情]

  • 如果您打算继续遵循本指南,请检查内核、其模块以及 initramfs 等其他相关文件占用了多少存储空间

    du -ch /boot/*$(make -s kernelrelease)* | tail -n 1
    du -sh /lib/modules/$(make -s kernelrelease)/
    

    记下或记住这两个值以备后用:它们使您能够在二分查找期间防止意外耗尽磁盘空间。

    [详情]

  • 显示并存储您刚刚构建的内核的 kernelrelease 标识符

    make -s kernelrelease | tee -a ~/kernels-built
    

    暂时记住此标识符,因为它将帮助您在重启时从引导菜单中选择正确的内核。

  • 重启到您新构建的内核。为确保您实际启动的是您刚刚构建的内核,您可能需要验证这些命令的输出是否匹配

    tail -n 1 ~/kernels-built
    uname -r
    
  • 检查内核是否将自身标记为“tainted”

    cat /proc/sys/kernel/tainted
    

    如果该命令不返回‘0’,请查阅参考部分,因为其原因可能会干扰您的测试。

    [详情]

  • 验证您的错误是否在新构建的内核中出现。如果没有,请查阅参考部分中的说明,以确保您的测试期间没有出现任何意外情况。

    [详情]

  • 您刚刚构建了一个稳定版或长期版内核吗?并且您能够用它重现回归吗?那么您也应该测试最新的主线代码库,因为结果决定了该错误必须提交给哪些开发人员。

    为了准备该测试,请检出当前主线

    cd ~/linux/
    git switch --discard-changes --detach mainline/master
    

    现在使用检出的代码,按照前面步骤中已更详细描述的命令,构建并安装另一个内核

    cp ~/kernel-config-working .config
    make olddefconfig
    make -j $(nproc --all)
    # * Check if the free space suffices holding another kernel:
    df -h /boot/ /lib/modules/
    sudo make modules_install
    command -v installkernel && sudo make install
    make -s kernelrelease | tee -a ~/kernels-built
    reboot
    

    确认您启动了您打算启动的内核并检查其污染状态

    tail -n 1 ~/kernels-built
    uname -r
    cat /proc/sys/kernel/tainted
    

    现在验证此内核是否显示问题。如果显示,则需要向主要开发人员报告该错误;如果未显示,则报告给稳定版团队。有关详细信息,请参阅报告问题

    [详情]

您是否遵循本指南来验证问题是否存在于 Linux 内核开发人员当前支持的代码中?那么您到此就完成了。如果您以后想删除刚刚构建的内核,请查阅补充任务:遵循本指南期间和之后的清理工作

如果您遇到回归,请继续并至少执行下一个部分。

第 2 部分:检查您构建的内核是否正常工作

如果出现回归,您现在需要确保您之前创建的精简配置文件按预期工作;否则使用 .config 文件进行二分查找将是浪费时间。 [详情]

  • 构建您自己的“工作”内核变体,并检查出现回归的特性是否按预期工作。

    首先检出您之前确定为“良好”版本的源代码(这里再次假设为 6.0)

    cd ~/linux/
    git switch --discard-changes --detach v6.0
    

    现在使用检出的代码,按照前一小节更详细解释的命令,配置、构建并安装另一个内核

    cp ~/kernel-config-working .config
    make olddefconfig
    make -j $(nproc --all)
    # * Check if the free space suffices holding another kernel:
    df -h /boot/ /lib/modules/
    sudo make modules_install
    command -v installkernel && sudo make install
    make -s kernelrelease | tee -a ~/kernels-built
    reboot
    

    系统启动后,您可能需要再次验证您启动的内核是否就是您刚刚构建的内核

    tail -n 1 ~/kernels-built
    uname -r
    

    现在检查此内核是否按预期工作;如果不是,请查阅参考部分以获取进一步说明。

    [详情]

第 3 部分:执行二分查找并验证结果

在所有准备工作和预防性构建都完成后,您现在可以开始二分查找了。这将使您构建相当多的内核——通常在更新到较新系列(例如从 6.0.13 到 6.1.5)时遇到回归,大约需要构建 15 个内核。但请不要担心,由于之前创建了精简的构建配置,这比许多人想象的要快得多:总的来说,在商用 x86 机器上,平均每个内核的编译时间通常只需 10 到 15 分钟。

  • 开始二分查找并告知 Git 之前确定为“良好”版本(在以下示例命令中为 6.0)和“损坏”版本(6.1.5)

    cd ~/linux/
    git bisect start
    git bisect good v6.0
    git bisect bad v6.1.5
    

    [详情]

  • 现在使用 Git 检出的代码,按照之前介绍的命令构建、安装并启动内核

    cp ~/kernel-config-working .config
    make olddefconfig
    make -j $(nproc --all)
    # * Check if the free space suffices holding another kernel:
    df -h /boot/ /lib/modules/
    sudo make modules_install
    command -v installkernel && sudo make install
    make -s kernelrelease | tee -a ~/kernels-built
    reboot
    

    如果编译因某种原因失败,请运行 git bisect skip 并从头开始重新执行命令堆栈。

    如果您在指南中跳过了“测试最新代码库”步骤,请查看其描述以了解为何此处有“df [...]”和“make -s kernelrelease [...]”命令。

    重要提示:从这一点开始,后一个命令将打印可能让您觉得奇怪或错误的发布标识符——但它们并非如此,因为如果您在版本 6.1 和 6.2 之间进行二分查找,看到像“6.0-rc1-local-gcafec0cacaca0”这样的发布标识符是完全正常的。

    [详情]

  • 现在检查您刚刚构建的内核中出现回归的功能是否正常工作。

    您可能再次需要首先确保您启动的内核就是您刚刚构建的内核

    cd ~/linux/
    tail -n 1 ~/kernels-built
    uname -r
    

    现在验证出现回归的功能在此内核二分查找点是否正常工作。如果正常工作,运行此命令

    git bisect good
    

    如果不行,运行此命令

    git bisect bad
    

    请务必确定您告诉 Git 的内容,因为一旦出错,剩余的二分查找将完全偏离轨道。

    在二分查找进行期间,Git 将使用您提供的信息来查找并检出另一个二分查找点供您测试。在此过程中,它将打印类似“Bisecting: 675 revisions left to test after this (roughly 10 steps)”(二分查找中:此后还剩 675 个修订版本待测试(大约 10 步))的信息,以指示它预计还有多少更改需要测试。现在,使用上一步的说明构建并安装另一个内核;之后再次遵循此步骤中的说明。

    重复此操作,直到完成二分查找——当 Git 在将更改标记为“良好”或“损坏”后打印出类似“cafecaca0c0dacafecaca0c0dacafecaca0c0da 是第一个坏提交”的信息时,即表示完成;紧接着它将显示有关罪魁祸首的一些详细信息,包括该更改的补丁描述。后者可能会填满您的终端屏幕,因此您可能需要向上滚动才能看到提及罪魁祸首的消息;或者,运行 git bisect log > ~/bisection-log

    [详情]

  • 在告诉 Git 将源代码重置到二分查找之前的状态之前,将 Git 的二分查找日志和当前的 .config 文件存储在一个安全的地方

    cd ~/linux/
    git bisect log > ~/bisection-log
    cp .config ~/bisection-config-culprit
    git bisect reset
    

    [详情]

  • 尝试在最新主线代码的基础上回滚问题提交,以查看这是否修复了您的回归。

    这是可选的,因为它可能无法实现或难以实现。如果是二分查找确定合并提交是罪魁祸首,则属于前一种情况;如果其他更改依赖于罪魁祸首,则属于后一种情况。但如果回滚成功,则值得构建另一个内核,因为它验证了二分查找的结果,二分查找很容易偏离轨道;它还将让内核开发人员知道,他们是否可以通过快速回滚来解决回归。

    首先根据您二分查找的范围检出最新的代码库

    • 您是否在稳定/长期系列中(例如 6.0.13 到 6.0.15 之间)遇到回归,但主线中没有发生?那么请像这样检出受影响系列的最新代码库

      git fetch stable
      git switch --discard-changes --detach linux-6.0.y
      
    • 在所有其他情况下,检出最新的主线代码

      git fetch mainline
      git switch --discard-changes --detach mainline/master
      

      如果您二分查找的回归在稳定/长期系列中发生,并且也在主线中发生,那么还有一件事要做:查找主线提交 ID。为此,请使用 git show abcdcafecabcd 这样的命令来查看罪魁祸首的补丁描述。顶部附近会有一行类似“commit cafec0cacaca0 upstream.”或“Upstream commit cafec0cacaca0”;在下一个命令中使用该提交 ID,而不是二分查找所指责的 ID。

    现在尝试通过指定其提交 ID 来回滚问题提交

    git revert --no-edit cafec0cacaca0
    

    如果失败,请放弃尝试并继续下一步;如果成功,请调整标签以方便识别并防止意外覆盖另一个内核

    cp ~/kernel-config-working .config
    ./scripts/config --set-str CONFIG_LOCALVERSION '-local-cafec0cacaca0-reverted'
    

    使用熟悉的命令序列构建内核,只是不复制基础 .config 文件

    make olddefconfig &&
    make -j $(nproc --all)
    # * Check if the free space suffices holding another kernel:
    df -h /boot/ /lib/modules/
    sudo make modules_install
    command -v installkernel && sudo make install
    make -s kernelrelease | tee -a ~/kernels-built
    reboot
    

    现在最后一次检查让您执行二分查找的功能是否在该内核中正常工作:如果一切顺利,它应该不会出现回归。

    [详情]

补充任务:二分查找期间和之后的清理

在遵循本指南期间和之后,您可能希望或需要删除一些已安装的内核:否则启动菜单会变得混乱或空间可能不足。

  • 要删除您安装的某个内核,请查找其“kernelrelease”标识符。本指南将它们存储在“~/kernels-built”中,但以下命令也会打印它们

    ls -ltr /lib/modules/*-local*
    

    在大多数情况下,您会希望删除在实际二分查找期间(例如本指南的第 3 部分)构建的最旧内核。您事先创建的两个内核(例如,用于测试最新代码库和被认为是“良好”的版本)可能在以后验证某些内容时派上用场——因此最好保留它们,除非您的存储空间确实不足。

    要删除内核(其 kernelrelease 标识符为“6.0-rc1-local-gcafec0cacaca0”)的模块,请首先删除包含其模块的目录

    sudo rm -rf /lib/modules/6.0-rc1-local-gcafec0cacaca0
    

    之后尝试以下命令

    sudo kernel-install -v remove 6.0-rc1-local-gcafec0cacaca0
    

    在相当多的发行版上,这将删除所有其他已安装的内核文件,同时从启动菜单中删除内核条目。但在某些发行版上,kernel-install 不存在,或者会留下引导加载器条目、内核映像和相关文件;在这种情况下,请按照参考部分所述将其删除。

    [详情]

  • 完成二分查找后,不要立即删除您设置的任何内容,因为您可能需要再次使用一些东西。哪些可以安全删除取决于二分查找的结果

    • 您是否最初能用最新代码库重现回归,并且在二分查找后,通过在最新代码库上回滚问题提交解决了问题?那么您会希望保留这两个内核一段时间,但可以安全地删除所有发布标识符中带有“-local”的其他内核。

    • 二分查找是否以合并提交结束,或因其他原因看起来可疑?那么您会希望尽可能多地保留内核几天:很可能您会被要求重新检查一些东西。

    • 在其他情况下,最好保留以下内核一段时间:从最新代码库构建的内核,从被认为是“良好”版本创建的内核,以及您在实际二分查找过程中编译的最后三到四个内核。

    [详情]

可选:测试回滚、补丁或后续版本

在报告错误期间或之后,您可能希望或可能会被要求测试回滚、调试补丁、提议的修复或其他版本。在这种情况下,请遵循这些说明。

  • 更新您的 Git 克隆并检出最新代码。

    • 如果您想测试主线,请在检出其代码之前获取其最新更改

      git fetch mainline
      git switch --discard-changes --detach mainline/master
      
    • 如果您想测试稳定版或长期版内核,请首先添加包含您感兴趣系列的(在本例中为 6.2)分支,除非您之前已经这样做过

      git remote set-branches --add stable linux-6.2.y
      

      然后获取最新更改并检出该系列的最新版本

      git fetch stable
      git switch --discard-changes --detach stable/linux-6.2.y
      
  • 复制您的内核构建配置

    cp ~/kernel-config-working .config
    
  • 您的下一步取决于您想做什么

    • 如果您只想测试最新的代码库,请直接进行下一步,您已经准备就绪了。

    • 如果您想测试回滚是否修复了问题,请通过指定其提交 ID 来回滚一个或多个更改

      git revert --no-edit cafec0cacaca0
      

      现在给该内核一个特殊标签,以方便识别并防止意外覆盖另一个内核

      ./scripts/config --set-str CONFIG_LOCALVERSION '-local-cafec0cacaca0-reverted'
      
    • 如果您想测试补丁,请将补丁存储在一个文件(例如“/tmp/foobars-proposed-fix-v1.patch”)中,并像这样应用它

      git apply /tmp/foobars-proposed-fix-v1.patch
      

      如果存在多个补丁,请对其他补丁重复此步骤。

      现在给该内核一个特殊标签,以方便识别并防止意外覆盖另一个内核

      ./scripts/config --set-str CONFIG_LOCALVERSION '-local-foobars-fix-v1'
      
  • 使用熟悉的命令构建内核,只是不复制内核构建配置,因为这已经处理过了

    make olddefconfig &&
    make -j $(nproc --all)
    # * Check if the free space suffices holding another kernel:
    df -h /boot/ /lib/modules/
    sudo make modules_install
    command -v installkernel && sudo make install
    make -s kernelrelease | tee -a ~/kernels-built
    reboot
    
  • 现在验证您是否启动了新构建的内核并进行检查。

[详情]

总结

您已到达分步指南的末尾。

您在遵循逐步指南时是否遇到了下文参考章节未能说明的麻烦?您发现了错误吗?或者您有改进指南的想法吗?

如果有以上任何情况,请发送简短说明或补丁给 Thorsten Leemhuis <linux@leemhuis.info> 以告知开发人员,理想情况下请抄送公共 Linux 文档邮件列表 <linux-doc@vger.kernel.org>。此类反馈对于进一步改进此文本至关重要,这符合每个人的利益,因为它将使更多的人能够掌握此处描述的任务。

分步指南的参考部分

本节包含上述分步指南中几乎所有项目的附加信息。

构建您自己的内核的准备工作

本节中的步骤为所有进一步的测试奠定了基础。 [...]

本指南所有后续部分的步骤都依赖于此处描述的步骤。

[返回分步指南]。

为紧急情况做好准备

创建一份全新备份,并备好系统修复和恢复工具。 [...]

请记住,您正在处理计算机,它们有时会做一些意想不到的事情——特别是如果您修改了操作系统内核等关键部分。这正是您在此过程中将要做的。因此,最好为可能出现的意外情况做好准备,即使它不应该发生。

[返回分步指南]

处理安全启动等技术

在具有“安全启动”或类似技术的平台上,准备好一切以确保系统稍后允许您自行编译的内核启动。 [...]

许多现代系统只允许某些操作系统启动;这就是为什么它们默认拒绝启动自行编译的内核。

理想情况下,您可以通过借助证书让您的平台信任您自行构建的内核来处理此问题。本文不描述如何做到这一点,因为它需要各种步骤,会使文本偏离其目的太远;“内核模块签名工具”和各种网站已经更详细地解释了所需的一切。

暂时禁用安全启动等解决方案是让您自己的 Linux 启动的另一种方法。在商用 x86 系统上,可以在 BIOS 设置实用程序中完成此操作;所需步骤因机器而异,因此无法在此处描述。

在主流 x86 Linux 发行版上,还有第三种通用选项:禁用您的 Linux 环境的所有安全启动限制。您可以通过运行 mokutil --disable-validation 来启动此过程;这将告诉您创建一个一次性密码,可以安全地记下来。现在重启;在您的 BIOS 执行完所有自检后,引导加载程序 Shim 将显示一个蓝色框,其中包含消息“Press any key to perform MOK management”。在倒计时结束前按下任意键,这将打开一个菜单。选择“Change Secure Boot state”。Shim 的“MokManager”现在将要求您输入之前指定的一次性密码中的三个随机字符。提供它们后,确认您确实要禁用验证。之后,允许 MokManager 重启机器。

[返回分步指南]

启动上一个正常工作的内核

启动到上一个正常工作的内核,并简要重新检查出现回归的特性是否确实正常工作。 [...]

这将使后续涵盖创建和精简配置的步骤能够正确执行。

[返回分步指南]

空间要求

确保有足够的空闲空间来构建 Linux。 [...]

所提及的数字是粗略估计,并额外加了一些量以确保安全,因此您通常需要更少的空间。

如果您的空间受限,请务必注意关于调试符号的步骤及其配套参考部分,因为禁用它们将减少数 GB 的磁盘空间消耗。

[返回分步指南]

二分查找范围

确定本指南中被视为“良好”和“损坏”的内核版本。 [...]

确定要检查的提交范围通常很简单,除非在从一个稳定系列的版本切换到后续系列的版本时发生回归(例如从 6.0.13 到 6.1.5)。在这种情况下,Git 需要一些手动协助,因为没有直接的演进路线。

这是因为随着 6.0 版本的发布,主线继续发展到 6.1,而稳定系列 6.0.y 则分支出来。因此,理论上您在 6.1.5 中遇到的问题可能只在 6.0.13 中正常工作,因为它是通过一个进入 6.0.y 系列某个版本的提交修复的,但从未进入主线或 6.1.y 系列。幸运的是,由于稳定版/长期版维护者维护代码的方式,这种情况通常不应该发生。因此,假设 6.0 是一个“良好”内核是相当安全的。无论如何,这个假设都将被测试,因为该内核将在本指南的“第 2 部分”中构建和测试;如果您尝试在 6.0.13 和 6.1.15 之间进行二分查找,Git 也会强制您这样做。

[返回分步指南]

安装构建要求

安装构建 Linux 内核所需的所有软件。 [...]

内核是相当独立的,但除了编译器等工具之外,有时您还需要一些库来构建它。如何安装所需的一切取决于您的 Linux 发行版以及您将要构建的内核的配置。

以下是一些主流发行版上您通常需要的一些示例

  • Arch Linux 及其衍生版

    sudo pacman --needed -S bc binutils bison flex gcc git kmod libelf openssl \
      pahole perl zlib ncurses qt6-base
    
  • Debian、Ubuntu 及其衍生版

    sudo apt install bc binutils bison dwarves flex gcc git kmod libelf-dev \
      libssl-dev make openssl pahole perl-base pkg-config zlib1g-dev \
      libncurses-dev qt6-base-dev g++
    
  • Fedora 及其衍生版

    sudo dnf install binutils \
      /usr/bin/{bc,bison,flex,gcc,git,openssl,make,perl,pahole,rpmbuild} \
      /usr/include/{libelf.h,openssl/pkcs7.h,zlib.h,ncurses.h,qt6/QtGui/QAction}
    
  • openSUSE 及其衍生版

    sudo zypper install bc binutils bison dwarves flex gcc git \
      kernel-install-tools libelf-devel make modutils openssl openssl-devel \
      perl-base zlib-devel rpm-build ncurses-devel qt6-base-devel
    

这些命令安装了一些经常需要但并非总是必需的软件包。例如,您可能希望跳过安装 ncurses 的开发头文件,只有在您以后可能想使用 make 目标“menuconfig”或“nconfig”调整内核构建配置时才需要它们;同样,如果您不打算使用“xconfig”调整 .config,则省略 Qt6 的头文件。

此外,您可能需要额外的库及其开发头文件来完成本指南未涵盖的任务——例如从内核的 tools/ 目录构建实用程序时。

[返回分步指南]

使用 Git 下载源代码

检索 Linux 主线源代码。 [...]

分步指南概述了如何使用 Linus 主线仓库的完整 Git 克隆来下载 Linux 源代码。关于这一点没有什么可多说的了——但是有两种替代方法可以检索源代码,可能更适合您

使用 bundle 下载 Linux 主线源代码

使用以下命令通过 bundle 检索 Linux 主线源代码

wget -c \
  https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/clone.bundle
git clone --no-checkout clone.bundle ~/linux/
cd ~/linux/
git remote remove origin
git remote add mainline \
  https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
git fetch mainline
git remote add -t master stable \
  https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

如果“wget”命令失败,只需重新执行它,它将从上次中断的地方继续。

[返回分步指南] [返回章节介绍]

使用浅克隆下载 Linux 主线源代码

首先,执行以下命令以检索最新的主线代码库

git clone -o mainline --no-checkout --depth 1 -b master \
  https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git ~/linux/
cd ~/linux/
git remote add -t master stable \
  https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git

现在,将您的克隆历史深化到您的“良好”版本主线发布版的第二个前身。如果后者是 6.0 或 6.0.13,那么 5.19 将是第一个前身,5.18 是第二个——因此将历史深化到该版本

git fetch --shallow-exclude=v5.18 mainline

之后,按照分步指南中的说明,添加稳定 Git 仓库作为远程仓库,并添加所有必需的稳定分支。

请注意,浅克隆有一些独特的特性

  • 对于二分查找,历史需要比看起来必要的更深几个主线版本,如上所述。这是因为 Git 否则将无法回滚或描述某个范围(例如 6.1..6.2)内的大多数提交,因为它们内部基于更早的内核版本(例如 6.0-rc2 或 5.19-rc3)。

  • 本文档在大多数地方使用带有 --shallow-exclude=git fetch 来指定您关心的最早版本(或者更准确地说:其 Git 标签)。您也可以使用参数 --shallow-since= 来指定一个绝对日期(例如 '2023-07-15')或相对日期('12 months')来定义您要下载的历史深度。在二分查找主线时使用它们时,请确保将历史深化到您的“良好”内核所基于的主线发布版本发布之前至少 7 个月。

  • 请注意,当深化您的克隆时,您可能会遇到类似“fatal: error in object: unshallow cafecaca0c0dacafecaca0c0dacafecaca0c0da”的错误。在这种情况下,请运行 git repack -d 并重试。

[返回分步指南] [返回章节介绍]

开始定义您的内核构建配置

开始准备内核构建配置(“.config”文件)。 [...]

请注意,这是本指南中创建或修改构建工件的多个步骤中的第一个。本指南中使用的命令将它们直接存储在源代码树中,以保持简单。如果您更喜欢单独存储构建工件,请创建一个像“~/linux-builddir/”这样的目录,并将参数 ``O=~/linux-builddir/`` 添加到本指南中使用的所有 make 调用中。您还需要将其他命令指向那里——其中包括 ``./scripts/config [...]`` 命令,这将需要 ``--file ~/linux-builddir/.config`` 来定位正确的构建配置。

按照建议创建 .config 文件时,有两件事很容易出错

  • 如果您的构建目录中已经存在 .config 文件(例如“~/linux/.config”),oldconfig 目标将使用它。如果您是故意这样做(请参阅下一步),那完全没问题,但在所有其他情况下,您应该删除它。例如,如果您继续遵循本指南,但由于问题需要回到此处从头重新配置,这一点就很重要。

  • 有时 olddefconfig 无法找到您正在运行的内核的 .config 文件,并将使用默认设置,如指南中简要概述的那样。在这种情况下,请检查您的发行版是否在某个地方提供了配置,如果提供了,请手动将其放置在正确的位置(例如“~/linux/.config”)。在存在 /proc/config.gz 的发行版上,可以使用以下命令实现此目的

    zcat /proc/config.gz > .config
    

    将其放置在那里后,再次运行 make olddefconfig,以便根据即将构建的内核的需求进行调整。

请注意,olddefconfig 目标会将任何未定义的构建选项设置为其默认值。如果您更喜欢手动设置此类配置选项,请使用 make oldconfig。然后,对于每个未定义的配置选项,系统都会询问您如何处理;如果您不确定如何回答,只需按“回车”键即可应用默认值。但请注意,对于二分查找,您通常希望使用默认值,否则您可能会启用一个新功能,导致出现类似回归的问题(例如由于安全限制)。

偶尔,当尝试在较旧的主线版本上使用为某个内核(例如 6.1)准备的配置文件时,会发生一些奇怪的事情——尤其是当它更旧时(例如 5.15)。这就是为什么指南中上一步告诉您启动一切正常工作的内核的原因之一。因此,如果您手动添加 .config 文件,您需要确保它来自正常工作的内核,而不是显示回归的内核。

如果您想为另一台机器构建内核,请找到其内核构建配置;通常 ls /boot/config-$(uname -r) 将打印其名称。将该文件复制到构建机器并将其存储为 ~/linux/.config;之后运行 make olddefconfig 进行调整。

[返回分步指南]

精简您的内核构建配置

禁用您设置中明显多余的任何内核模块。 [...]

正如分步指南中简要解释的那样:使用 localmodconfig 很容易导致您自行构建的内核缺少执行某些任务所需的模块,而这些任务在利用此 make 目标之前您至少从未执行过一次。这种情况发生在当一个任务需要仅在您第一次执行时才自动加载的内核模块时。因此,如果您自启动内核以来从未执行过该任务,则这些模块将不会被加载——并且从 localmodconfig 的角度来看,它们看起来是多余的,因此将其禁用以减少要编译的代码量。

您可以通过执行通常会自动加载额外内核模块的典型任务来尝试避免这种情况:启动虚拟机,建立 VPN 连接,循环挂载 CD/DVD ISO,挂载网络共享(CIFS、NFS 等),并连接所有外部设备(2FA 密钥、耳机、网络摄像头等)以及使用您平时不用的文件系统(btrfs、ext4、FAT、NTFS、XFS 等)的存储设备。但很难想到所有可能需要的东西——甚至内核开发人员在这个阶段也经常会忘记一些事情。

不要让这种风险困扰您,特别是当您仅出于测试目的编译内核时:通常关键的一切都会存在。如果您忘记了重要的事情,以后可以手动开启缺失的功能,并快速再次运行命令来编译和安装一个包含您所需一切的内核。

但是,如果您打算定期构建和使用自构建内核,您可能希望通过记录您的系统在几周内加载的模块来降低风险。您可以使用 modprobed-db 自动化此过程。之后使用 LSMOD=<path> 将 localmodconfig 指向 modprobed-db 发现正在使用的模块列表

yes '' | make LSMOD='${HOME}'/.config/modprobed.db localmodconfig

该参数还允许您为另一台机器构建精简内核,以防您复制了合适的 .config 文件作为基础(请参阅上一步)。只需在该系统上运行 lsmod > lsmod_foo-machine 并将生成的文件复制到您构建主机的主目录。然后运行这些命令,而不是分步指南中提到的命令

yes '' | make LSMOD=~/lsmod_foo-machine localmodconfig

[返回分步指南]

标记即将构建的内核

确保您将构建的所有内核都使用特殊标签和唯一版本标识符清晰可识别。 [...]

这使您能够区分您的发行版内核和在此过程中创建的内核,因为后者的文件或目录名称中将包含“-local”;它还有助于在启动菜单中选择正确的条目,并避免混淆您的内核,因为在二分查找期间它们的版本号会显得有些混乱。

[返回分步指南]

决定启用或禁用调试符号

决定如何处理调试符号。 [...]

当您的内核在运行时抛出“panic”、“Oops”、“warning”或“BUG”时,拥有调试符号可能很重要,因为这样您就可以找到代码中出现问题的确切位置。但是,收集和嵌入所需的调试信息需要时间并占用相当大的空间:2022 年末,一个使用 localmodconfig 精简的典型 x86 内核的构建工件在启用调试符号时大约占用 5 GB 空间,而禁用时则小于 1 GB。生成的内核镜像和模块也更大,这增加了 /boot/ 的存储要求和加载时间。

如果您想要一个小型内核,并且以后不太可能解码堆栈跟踪,那么您可能希望禁用调试符号以避免这些缺点。如果后来发现您需要它们,只需按所示启用它们并重新构建内核。

另一方面,如果将来很可能需要解码堆栈跟踪,那么您绝对希望在此过程中启用它们。报告问题中的“解码失败消息”部分更详细地解释了此过程。

[返回分步指南]

调整构建配置

检查您是否可能需要调整其他一些内核配置选项

根据您的需求,此时您可能希望或必须调整一些内核配置选项。

发行版特定调整

您正在运行 [...] 吗?

以下部分将帮助您避免在一些商用发行版上遵循本指南时已知会发生的构建问题。

Debian

  • 删除对证书文件的陈旧引用,这会导致您的构建失败

    ./scripts/config --set-str SYSTEM_TRUSTED_KEYS ''
    

    或者,下载所需的证书并使该配置选项指向它,如Debian 手册中更详细的解释——或者生成您自己的证书,如内核模块签名工具中解释的那样。

[返回分步指南]

个性化调整

如果您想影响配置的其他方面,请立即进行。 [...]

此时,您可以使用 make menuconfigmake nconfig 等命令通过文本用户界面启用或禁用某些功能;要使用图形配置实用程序,请改为运行 make xconfig。两者都需要它们所依赖的工具包(分别是 ncurses 和 Qt5 或 Qt6)的开发库;如果缺少所需内容,将显示错误消息。

[返回分步指南]

将 .config 文件放到一边

在最新更改后重新处理 .config 并将其存储在一个安全的地方。 [...]

将您准备好的 .config 文件放在一边,因为在本指南中,您每次构建另一个内核之前都希望将其复制回构建目录。这是因为在不同版本之间来回切换可能会以奇怪的方式改变 .config 文件;这些偶尔会导致副作用,可能混淆测试或在某些情况下使您的二分查找结果毫无意义。

[返回分步指南]

尝试使用最新代码库重现问题

验证回归不是由 .config 更改引起的,并检查它是否仍然存在于最新代码库中。 [...]

对于某些读者来说,此时检查最新代码库可能看起来没有必要,特别是如果您已经使用发行版准备的内核进行了检查,或者在稳定/长期系列中遇到了回归。但由于以下原因,强烈推荐这样做

  • 在您实际开始二分查找之前,您会遇到由您的设置引起的任何问题。这将使区分“这很可能是我的设置中的一些问题”和“在二分查找期间需要跳过此更改,因为该阶段的内核源代码包含导致构建或启动失败的无关问题”变得容易得多。

  • 这些步骤将排除您的构建配置在“工作”内核和“损坏”内核之间发生某些更改是否导致了问题。例如,当您的发行版在新内核中启用了旧内核未禁用或尚未支持的额外安全功能时,就可能发生这种情况。该安全功能可能会干扰您正在做的事情——在这种情况下,从 Linux 内核上游开发人员的角度来看,您的问题不属于回归,如报告回归中更详细的解释。因此,如果您尝试对此进行二分查找,将是浪费时间。

  • 如果您的回归原因已经在最新的主线代码库中修复,那么您进行二分查找将是徒劳的。对于您在稳定版/长期版中遇到的回归也同样如此,因为它们通常是由主线更改中被回溯移植的问题引起的——在这种情况下,问题必须首先在主线中修复。也许它已经在那里修复,并且该修复正在被回溯移植的过程中。

  • 对于稳定/长期系列中的回归,了解问题是该系列特有的还是也发生在主线内核中至关重要,因为报告需要发送给不同的人

    • 稳定/长期系列特有的回归是稳定团队的责任;主线 Linux 开发人员可能关心,也可能不关心。

    • 在主线中也发生的回归是常规 Linux 开发人员和维护者必须处理的问题;稳定团队不关心,也无需参与报告,他们只需要在修复准备好后被告知回溯移植即可。

    如果将报告发送给错误的方,它可能会被忽略——即使您收到了回复,开发人员也很可能会让您评估是两种情况中的哪一种,然后他们才会仔细查看。

[返回分步指南]

检出最新的 Linux 代码库

检出最新的 Linux 代码库。 [...]

如果您以后想重新检查是否有更新的代码库可以解决问题,请记住再次运行前面提到的 git fetch --shallow-exclude [...] 命令来更新您的本地 Git 仓库。

[返回分步指南]

构建您的内核

使用您准备好的配置文件构建第一个内核的镜像和模块。 [...]

在此阶段可能会出现很多问题,但下面的说明将帮助您自助。另一个小节解释了如何将内核直接打包成 deb、rpm 或 tar 文件。

处理构建错误

当发生构建错误时,可能由您的机器设置的某些方面引起,通常可以快速修复;但有时问题出在代码中,只能由开发人员修复。仔细检查失败消息并结合互联网上的一些研究通常会告诉您是哪一种情况。要执行此类调查,请像这样重新启动构建过程:

make V=1

V=1 激活了详细输出,可能需要它才能看到实际错误。为了更容易发现错误,此命令也省略了之前用于利用系统中所有 CPU 核心的 -j $(nproc --all)——但这种并行性在发生故障时也会导致一些混乱。

几秒钟后,构建过程应该会再次遇到错误。现在尝试找到描述问题最关键的行。然后搜索互联网,查找该行中最重要且非通用的部分(例如 4 到 8 个词);避免或删除任何看起来与系统相关的东西,例如您的用户名或本地路径名,如 /home/username/linux/。首先使用该字符串尝试您的常规互联网搜索引擎,然后通过 lore.kernel.org/all/ 搜索 Linux 内核邮件列表。

大多数情况下,这会找到一些解释错误原因的信息;通常,其中一个结果会为您的​问题提供解决方案。如果您没有找到与您问题匹配的内容,请尝试通过修改搜索词或使用错误消息中的另一行来从不同角度再次尝试。

最终,您遇到的大多数问题很可能已经由其他人遇到并报告过。这包括问题原因不在您的系统,而是在代码中的情况。如果您遇到其中一个,您也可能找到解决您问题的方案(例如补丁)或变通方法。

打包您的内核

分步指南使用默认的 make 目标(例如,x86 上的 'bzImage' 和 'modules')来构建内核的镜像和模块,后续步骤会将其安装。您也可以直接构建所有内容并使用以下目标之一将其直接打包:

  • make -j $(nproc --all) bindeb-pkg 生成一个 deb 包

  • make -j $(nproc --all) binrpm-pkg 生成一个 rpm 包

  • make -j $(nproc --all) tarbz2-pkg 生成一个 bz2 压缩的 tarball

这只是为此目的而可用的一些 make 目标,有关其他目标请参阅 make help。您也可以在运行 make -j $(nproc --all) 之后使用这些目标,因为它们会利用所有已经构建好的内容。

如果您使用这些目标生成 deb 或 rpm 包,请忽略分步指南中关于安装和卸载内核的说明;相反,请使用该格式的包实用程序(例如 dpkg 和 rpm)或基于它们构建的包管理实用程序(apt、aptitude、dnf/yum、zypper 等)来安装和卸载包。请注意,使用这两个 make 目标生成的包旨在在利用这些格式的各种发行版上工作,因此它们有时会与您发行版的内核包表现不同。

[返回分步指南]

放置内核

安装您刚刚构建的内核。 [...]

在分步指南中执行命令后,您需要做什么取决于您的发行版上是否存在 /sbin/installkernel 可执行文件及其实现。

如果找到了 installkernel,内核的构建系统将把内核镜像的实际安装委托给这个可执行文件,它将执行以下部分或全部任务:

  • 在几乎所有 Linux 发行版上,installkernel 都会将您的内核镜像存储在 /boot/ 中,通常命名为 ‘/boot/vmlinuz-<kernelrelease_id>’;通常它还会同时放置一个 ‘System.map-<kernelrelease_id>’。

  • 在大多数发行版上,installkernel 随后会生成一个 ‘initramfs’(有时也称为 ‘initrd’),它们通常存储为 ‘/boot/initramfs-<kernelrelease_id>.img’ 或 ‘/boot/initrd-<kernelrelease_id>’。常用发行版依赖此文件进行启动,因此请确保首先执行 make 目标 ‘modules_install’,否则您的发行版的 initramfs 生成器将无法找到要放入镜像中的模块。

  • 在某些发行版上,installkernel 随后会为您的内核添加到您的引导加载程序配置中。

如果您的发行版缺少 installkernel 脚本或只处理其中一部分任务,您必须自己处理部分或全部任务。请查阅发行版的文档以获取详细信息。如有疑问,请手动安装内核。

sudo install -m 0600 $(make -s image_name) /boot/vmlinuz-$(make -s kernelrelease)
sudo install -m 0600 System.map /boot/System.map-$(make -s kernelrelease)

现在使用您的发行版为此过程提供的工具生成您的 initramfs。之后,将您的内核添加到您的引导加载程序配置中并重新启动。

[返回分步指南]

每个内核的存储要求

检查内核、其模块以及其他相关文件(如 initramfs)占用了多少存储空间。 [...]

在二分查找期间构建的内核在 /boot/ 和 /lib/modules/ 中会占用相当大的空间,尤其是当您启用了调试符号时。这使得在二分查找期间很容易填满卷——因此,甚至以前可以工作的内核也可能无法启动。为防止这种情况,您需要知道每个已安装内核通常需要多少空间。

请注意,大多数情况下,指南中使用的模式 ‘/boot/$(make -s kernelrelease)’ 将匹配启动内核所需的所有文件——但路径和命名方案都不是强制性的。因此,在某些发行版上,您需要查看不同的位置。

[返回分步指南]

检查您新构建的内核是否认为自己“被污染”

检查内核是否将自身标记为“被污染”。 [...]

当发生可能导致看起来完全不相关的后续错误时,Linux 会将自己标记为被污染。这就是为什么开发人员可能会忽略或不认真对待来自被污染内核的报告——当然,除非内核在报告的错误发生时就设置了该标志。

这就是为什么您要按照 被污染的内核 中解释的那样检查内核被污染的原因;这样做也符合您自己的利益,否则您的测试可能会出现问题。

[返回分步指南]

检查从最新主线代码库构建的内核

验证您的 bug 是否在新构建的内核中出现。 [...]

您的 bug 或回归可能不会在您从最新代码库构建的内核中出现,这有几个原因。以下是最常见的原因:

  • 在此期间 bug 已被修复。

  • 您怀疑的回归是由于您的内核提供商更改了构建配置而引起的。

  • 您的问题可能是竞态条件,在您的内核中没有表现出来;精简的构建配置、调试符号的不同设置、使用的编译器以及各种其他因素都可能导致这种情况。

  • 如果您在稳定版/长期支持版内核中遇到了回归,这可能是一个特定于该系列的​问题;本指南的下一步将对此进行检查。

[返回分步指南]

检查从最新稳定版/长期支持版代码库构建的内核

您是否在稳定版/长期支持版中遇到了回归,但无法使用从最新主线源构建的内核重现它?那么请检查该特定系列的最新代码库是否已经解决了这个问题。 [...]

如果此内核也没有出现回归,则很可能不需要进行二分查找。

[返回分步指南]

确保“良好”版本确实运行良好

检查您构建的内核是否运行良好。 [...]

本节将重建一个已知可用的基础。跳过它可能很有吸引力,但这通常是个坏主意,因为它做了一些重要的事情:

它将确保您之前准备的 .config 文件确实按预期工作。这符合您自己的利益,因为修剪配置并非万无一失——在开始怀疑构建配置可能存在问题之前,您可能要构建和测试十个或更多内核却一无所获。

仅凭这一点就足以花费时间,但这并不是唯一的原因。

本指南的许多读者通常运行的内核都是打过补丁、使用附加模块或两者兼而有之的。因此,这些内核不被认为是“原版”的——因此,出现回归的功能可能在“良好”版本的原版构建中从未正常工作过。

对于那些注意到不同系列(例如 6.0.13..6.1.5)的稳定版/长期支持版内核之间存在回归的用户来说,还有第三个原因:它将确保您在早期过程中假定为“良好”的内核版本(例如 6.0)实际上是正常的。

[返回分步指南]

构建您自己的“良好”内核版本

构建您自己的可用内核变体,并检查其回归的功能是否按预期工作。 [...]

如果在新内核中出现问题的功能在您第一次自行构建的内核中不起作用,请在继续之前找出并解决原因。这可能发生的原因有很多。以下是一些查找思路:

  • 检查污染状态和 dmesg 的输出,也许发生了某些不相关的问题。

  • 也许 localmodconfig 做了一些奇怪的事情,禁用了测试该功能所需的模块?那么您可能希望基于最后一个可用内核的 .config 文件重新创建一个 .config 文件,并跳过对其进行精简;手动禁用 .config 中的某些功能也可能有助于缩短构建时间。

  • 也许这不是内核回归,而是由某种意外、损坏的 initramfs(也称为 initrd)、新的固件文件或更新的用户态软件引起的?

  • 也许这是添加到您的发行版内核中的功能,而当时的纯净版 Linux 从未支持过?

请注意,如果您发现并修复了 .config 文件的问题,您需要使用它来构建另一个来自最新代码库的内核,因为您之前对主线以及受影响的稳定版/长期支持版系列最新版本的测试很可能是有缺陷的。

[返回分步指南]

执行二分查找并验证结果

完成所有准备工作和预防性构建后,您现在可以开始二分查找了。 [...]

本部分中的步骤执行并验证二分查找。

[返回分步指南].

开始二分查找

开始二分查找,并告知 Git 之前确定的“良好”和“有问题”版本。 [...]

这将启动二分查找过程;最后一条命令将使 Git 为您检出一个位于“良好”和“有问题”更改之间大约一半位置的提交,供您测试。

[返回分步指南]

从二分查找点构建内核

使用您之前使用的相同命令,从 Git 检出的代码构建、安装并启动内核。 [...]

这里有两点值得注意:

  • 偶尔,由于二分查找点代码中的某些问题,构建内核会失败或者它可能无法启动。在这种情况下,请运行此命令:

    git bisect skip
    

    Git 将检出附近的另一个提交,如果幸运的话,它应该能更好地工作。之后重新开始执行此步骤。

  • 这些看起来有些奇怪的版本标识符可能在二分查找期间出现,因为 Linux 内核子系统在它的前身(例如 6.1)完成之前,会为新的主线版本(例如 6.2)准备它们的更改。因此,它们基于一个稍早的点,如 6.1-rc1 甚至 6.0,然后在 6.1 发布后,无需 rebase 或 squash 即可合并到 6.2。这导致这些看起来有些奇怪的版本标识符在二分查找期间出现。

[返回分步指南]

二分查找检查点

检查回归的功能在您刚刚构建的内核中是否正常工作。 [...]

确保您告知 Git 的信息准确无误:即使只有一次错误,也会使二分查找的其余部分完全偏离轨道,因此之后的所有测试都将毫无意义。

[返回分步指南]

保存二分查找日志

将 Git 的二分查找日志和当前的 .config 文件存储在一个安全的位置。 [...]

如上所述:错误地将一个内核声明为“良好”或“有问题”将使二分查找的最终结果毫无用处。在这种情况下,您通常必须从头开始重新进行二分查找。日志可以防止这种情况,因为它可能允许某人指出二分查找可能在哪里出了岔子——然后您可能只需要构建几个内核就可以解决问题,而不是测试十个或更多内核。

.config 文件被搁置一旁,因为您报告回归后,开发人员很有可能会要求它。

[返回分步指南]

尝试回溯肇事者

尝试在最新代码库的基础上回溯肇事者,以查看这是否能解决您的回归问题。 [...]

这是一个可选步骤,但只要可能就应该尝试:当您提出二分查找结果时,开发人员很有可能会要求您执行此步骤。所以尝试一下吧,您已经进入了流程,此时再构建一个内核应该不是什么大问题。

分步指南已经涵盖了所有相关内容,除了一个稍微罕见的情况:您是否二分查找了一个在主线中也发生并使用了稳定版/长期支持系列中的回归,但 Git 未能在主线中回溯该提交?那么请尝试在受影响的稳定版/长期支持系列中回溯肇事者——如果成功,则改为测试该内核版本。

[返回分步指南]

遵循本指南期间和之后的清理步骤

在遵循本指南期间和之后,您可能希望或需要移除一些已安装的内核。 [...]

本节中的步骤描述了清理过程。

[返回分步指南].

二分查找期间的清理

要移除已安装的某个内核,请查找其“kernelrelease”标识符。 [...]

您在此过程中安装的内核以后很容易移除,因为它的各个部分只存储在两个位置并且清晰可辨。因此,当您手动安装内核(从而绕过发行版的打包系统)时,无需担心会弄乱您的机器:内核的所有部分以后都相对容易移除。

两个位置之一是 /lib/modules/ 中的一个目录,它保存了每个已安装内核的模块。此目录以内核的发布标识符命名;因此,要移除您构建的某个内核的所有模块,只需移除其在 /lib/modules/ 中的模块目录即可。

另一个位置是 /boot/,通常在安装内核时会放置两到五个文件。所有这些文件通常都包含文件名中的发布名称,但文件数量及其确切名称在一定程度上取决于您发行版的 installkernel 可执行文件及其 initramfs 生成器。在某些发行版上,分步指南中提到的 kernel-install remove... 命令将为您删除所有这些文件,同时还从您的引导加载程序配置中删除内核的菜单项。在其他发行版上,您必须自己处理这两个任务。以下命令应该可以交互式地删除发布名称为 ‘6.0-rc1-local-gcafec0cacaca0’ 的内核的三个主要文件:

rm -i /boot/{System.map,vmlinuz,initr}-6.0-rc1-local-gcafec0cacaca0

之后,检查 /boot/ 中是否有其他文件名中包含 ‘6.0-rc1-local-gcafec0cacaca0’ 的文件,并考虑也将其删除。现在从您的引导加载程序配置中删除内核的引导条目;执行此操作的步骤在不同的 Linux 发行版之间差异很大。

请注意,手动删除内核文件或目录时,使用 ‘*’ 等通配符要小心:您可能会不小心删除 6.0.13 内核的文件,而您只想删除 6.0 或 6.0.1。

[返回分步指南]

二分查找后的清理

完成二分查找后,不要立即删除您设置的任何内容,因为您可能需要再次使用一些东西。 [...]

当您的存储空间确实不足时,按照分步指南中描述的方式移除内核可能无法释放您希望的那么多空间。在这种情况下,现在也可以考虑运行 rm -rf ~/linux/*。这将移除构建产物和 Linux 源代码,但会留下 Git 仓库 (~/linux/.git/)——因此简单的 git reset --hard 将会恢复源代码。

此时也移除仓库可能不明智:开发人员很有可能会要求您构建另一个内核以执行额外的测试——例如测试调试补丁或提议的修复。有关如何执行这些操作的详细信息,请参阅 可选任务:测试回溯、补丁或后续版本 部分。

额外的测试也是您希望将 ~/kernel-config-working 文件保留几周的原因。

[返回分步指南]

测试回溯、补丁或后续版本

在报告 bug 期间或之后,您可能想要或可能被要求测试回溯、补丁、提议的修复或​​其他版本。 [...]

本节中使用的所有命令都应该相当直接,所以除了以下一点之外没有太多可补充的:按照指示设置内核标签时,确保它不要比示例中使用的标签长太多,因为如果 kernelrelease 标识符超过 63 个字符就会出现问题。

[返回分步指南].

附加信息

在不同的机器上构建内核

要在另一个系统上编译内核,请稍微修改分步指南的说明:

  • 在您希望稍后安装和测试内核的机器上开始遵循本指南。

  • 执行‘启动到工作内核并简要使用显然已损坏的功能’后,使用 lsmod > ~/test-machine-lsmod 命令将已加载模块列表保存到文件中。然后找到正在运行的内核的构建配置(请参阅‘开始定义内核的构建配置’以获取查找位置的提示),并将其存储为 ‘~/test-machine-config-working’。将这两个文件传输到您的构建主机的家目录。

  • 在构建主机上继续本指南(例如,从‘确保有足够的可用空间用于构建 [...]’开始)。

  • 当您到达‘开始准备内核构建配置[...]’时:在第一次运行 make olddefconfig 之前,执行以下命令,以测试机器上‘工作’内核的配置为基础:

    cp ~/test-machine-config-working ~/linux/.config
    
  • 在下一步‘禁用任何显然多余的内核模块’中,请改用以下命令:

    yes '' | make localmodconfig LSMOD=~/lsmod_foo-machine localmodconfig
    
  • 继续本指南,但每次出现关于如何编译、安装和重新启动内核的说明时,请忽略它们。而是像这样构建:

    cp ~/kernel-config-working .config
    make olddefconfig &&
    make -j $(nproc --all) targz-pkg
    

    这将生成一个 gzip 压缩的 tar 文件,其名称显示在最后一行;例如,一个内核发布标识符为 ‘6.0.0-rc1-local-g928a87efa423’ 的 x86 机器内核通常会存储为 ‘~/linux/linux-6.0.0-rc1-local-g928a87efa423-x86.tar.gz’。

    将该文件复制到您的测试机器的家目录。

  • 切换到测试机器,检查您是否有足够的空间容纳另一个内核。然后解压您传输的文件:

    sudo tar -xvzf ~/linux-6.0.0-rc1-local-g928a87efa423-x86.tar.gz -C /
    

    之后 生成 initramfs 并将内核添加到您的引导加载程序配置中;在某些发行版上,以下命令将同时处理这两项任务:

    sudo /sbin/installkernel 6.0.0-rc1-local-g928a87efa423 /boot/vmlinuz-6.0.0-rc1-local-g928a87efa423
    

    现在重新启动并确保您启动了预期的内核。

这种方法甚至在为其他架构构建时也适用:只需安装交叉编译器并在每次调用 make 时添加适当的参数(例如 make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- [...])。

其他阅读材料