如何验证错误并进行回归分析

本文档描述了如何检查某些 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、6.1-rc1 或 6.1-rc6 等版本)来验证错误,即使您在“稳定/长期”系列(例如 6.0.13)的内核中遇到问题也是如此。

对于面临回归的用户,该文档还解释了为什么在第 2 节之后发送初步报告可能是明智的,因为可能已经知道回归及其原因。有关实际符合回归条件的更多详细信息,请查看报告回归

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

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

以下步骤为所有进一步的任务奠定了基础。

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

  • 创建一个新的备份,并准备好系统修复和恢复工具,以应对不太可能发生的意外情况。

    [详情]

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

    [详情]

  • 在具有“安全启动”或类似解决方案的平台上,准备好一切,以确保系统允许您自编译的内核启动。在商品 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 检查当前内核版本标识符。

    之后,检出较早确定的“好”版本的源代码。在以下示例命令中,假设为 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 MByte 和 /lib/modules/ 中 200 MByte 就足够了;您的内核实际需要多少空间将在本指南稍后确定。

    现在安装内核的模块及其镜像,它们将与您的 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
    
  • 检查内核是否将自己标记为“已污染”

    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)”的内容,以指示它期望测试多少进一步的更改。现在使用上一步中的说明构建并安装另一个内核;然后再次按照此步骤中的说明操作。

    一遍又一遍地重复此操作,直到您完成二分查找 - 如果 Git 在将更改标记为“良好”或“不良”后打印类似于“cafecaca0c0dacafecaca0c0dacafecaca0c0da is the first bad commit”的内容,则表示这种情况;紧接着它将显示有关罪魁祸首的一些详细信息,包括更改的补丁描述。后者可能会填满您的终端屏幕,因此您可能需要向上滚动以查看提及罪魁祸首的消息;或者,运行 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 部分)。您之前创建的两个内核(例如,用于测试最新代码库和被认为是“良好”的版本)可能在以后验证某些内容时会派上用场 - 因此最好保留它们,除非您的存储空间真的很少。

    要移除内核模块,其内核发布标识符为 ‘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
      
    • 如果你想测试稳定版或长期内核,请首先添加包含你感兴趣的系列的 branch(示例中为 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 将显示一个蓝色框,其中包含消息“按任意键执行 MOK 管理”。在倒计时结束之前按任意键,这将打开一个菜单。选择“更改安全启动状态”。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)。

  • 本文档在大多数地方使用 git fetch--shallow-exclude= 来指定你关心的最早版本(或更准确地说:它的 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。然后,对于每个未定义的配置选项,系统都会询问你如何进行;如果你不确定该如何回答,只需按“Enter”键即可应用默认值。但是请注意,对于二分查找,你通常希望使用默认值,否则可能会启用一个新功能,该功能会导致看起来像回归的问题(例如,由于安全限制)。

有时,尝试在较旧的主线版本(尤其是如果它较旧 (例如 5.15))上使用为某个内核(例如 6.1)准备的配置文件时,会出现奇怪的事情。这也是为什么本指南的前一步让你启动一切正常工作的内核的原因之一。因此,如果手动添加 .config 文件,你要确保它来自正常工作的内核,而不是来自显示回归的内核。

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

[返回逐步指南]

修剪内核的构建配置

禁用对于你的设置明显多余的任何内核模块。 [...]

正如逐步指南中已经简要解释的那样:使用 localmodconfig,你的自构建内核很容易缺少你至少在以前使用此 make 目标之前没有执行过的任务的模块。当某个任务需要内核模块时,只有在你第一次执行该任务时才会自动加载这些模块。因此,当你自启动内核以来从未执行过该任务时,将不会加载这些模块——并且从 localmodonfig 的角度来看,它们显得多余,因此会禁用它们以减少要编译的代码量。

你可以尝试通过执行通常会加载额外内核模块的典型任务来避免这种情况:启动 VM、建立 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

[返回逐步指南]

单独调整

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

此时,您可以使用类似 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 激活详细输出,这可能需要查看实际错误。为了更容易发现错误,此命令还省略了之前使用的 -j $(nproc --all) 以利用系统中每个 CPU 核心来执行作业——但这种并行性也会导致发生故障时出现一些混乱。

几秒钟后,构建过程应该会再次遇到错误。现在尝试找到描述问题的最关键的一行。然后,在互联网上搜索该行中最重要且非通用的部分(比如 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 压缩的 tar 包

这只是为此目的提供的一些可用的 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 会将自身标记为已污染。这就是为什么开发人员可能会忽略或轻视来自已污染内核的报告,除非当然内核在报告的错误发生时设置了标志。

这就是为什么您要检查为什么内核被污染的原因,如 受污染的内核 中所述;这样做也符合您自己的利益,因为否则您的测试可能会有缺陷。

[返回分步指南]

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

验证您新构建的内核是否出现错误。 [...]

您的错误或回归可能不会在您从最新代码库构建的内核中出现,原因有多种。这些是最常见的

  • 该错误已被修复。

  • 您怀疑是回归的原因是您的内核提供程序执行的构建配置中的更改引起的。

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

  • 如果您在稳定/长期内核中遇到回归,则可能是该系列特有的问题;本指南中的下一步将检查这一点。

[返回分步指南]

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

您是否在稳定/长期版本中面临回归,但无法使用您刚刚使用最新的主线源代码构建的内核重现它?然后检查特定系列的最新代码库是否已经解决了该问题。 [...]

如果此内核也没有显示回归,则很可能不需要进行二分。

[返回分步指南]

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

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

本节将重建一个已知的工作基础。跳过它可能很吸引人,但通常不是一个好主意,因为它做了一些重要的事情

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

仅此一点就足以花费时间在这上面,但这还不是唯一的原因。

本指南的许多读者通常运行已打补丁、使用附加模块或两者兼有的内核。因此,这些内核不被认为是“原生”的,因此,回归的事件可能在“良好”版本的原生构建中根本不起作用。

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

[返回分步指南]

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

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

如果较新内核中断的功能在您第一个自构建内核中不起作用,请在继续之前找到并解决原因。发生这种情况的原因有很多。一些查找思路

  • 检查污染状态和 dmesg 的输出,可能发生了一些不相关的事情。

  • 也许 localmodconfig 做了一些奇怪的事情,并禁用了测试该功能所需的模块?那么您可能需要基于上一个工作内核的 .config 文件重新创建 .config 文件并跳过精简它;手动禁用 .config 中的某些功能也可以减少构建时间。

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

  • 也许它是添加到您的发行版的内核中的一个功能,而当时的 vanilla Linux 根本不支持?

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

[返回分步指南]

执行二分并验证结果

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

此部分中的步骤执行并验证二分。

[返回分步指南].

开始二分

启动二分,并告诉 Git 之前确定的“良好”和“错误”版本。 [...]

这将启动二分过程;最后一个命令将使 Git 签出大约在“良好”和“错误”更改之间的中间提交以供您测试。

[返回分步指南]

从二分点构建内核

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

这里有两件事值得注意

  • 有时构建内核会失败,或者由于二分点代码中的某些问题而无法启动。在这种情况下,运行此命令

    git bisect skip
    

    然后,Git 将检出附近的其他提交,如果运气好的话,应该会更好地工作。之后,重新开始执行此步骤。

  • 这些看起来稍微有点奇怪的版本标识符在二分查找过程中可能会出现,因为 Linux 内核子系统会在其前任版本(例如 6.1)完成之前,就为新的主线版本(例如 6.2)准备更改。因此,它们会基于更早的某个时间点,比如 6.1-rc1 甚至是 6.0,然后在 6.1 发布后,不进行变基或压缩就合并到 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 或 6.0.1 时意外删除 6.0.13 内核的文件。

[返回逐步指南]

二分查找后清理

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

当你真的缺少存储空间时,按照逐步指南中的描述删除内核可能不会释放你想要的那么多空间。在这种情况下,请考虑现在也运行 rm -rf ~/linux/*。这将删除构建工件和 Linux 源代码,但会保留 Git 存储库 (~/linux/.git/),因此只需 git reset --hard 即可恢复源代码。

此时删除存储库可能是不明智的:很有可能开发者会要求你构建另一个内核来执行其他测试,例如测试调试补丁或建议的修复。有关如何执行这些操作的详细信息,请参阅可选任务:测试还原、补丁或更高版本部分。

其他测试也是你要将 ~/kernel-config-working 文件保留几周的原因。

[返回逐步指南]

测试还原、补丁或更高版本

在报告错误期间或之后,你可能想要或可能会被要求测试还原、补丁、建议的修复或其他版本。 [...]

本节中使用的所有命令都应该非常简单,因此没有太多要补充的,只有一件事:在按照指示设置内核标签时,请确保它不要比示例中使用的标签长很多,因为如果 kernelrelease 标识符超过 63 个字符,则会出现问题。

[返回逐步指南]。

附加信息

在其他机器上构建内核

要在其他系统上编译内核,请稍微更改逐步指南的说明

  • 在你要稍后安装和测试内核的机器上开始遵循指南。

  • 执行 “启动到正常工作的内核并简要使用明显损坏的功能” 后,使用 lsmod > ~/test-machine-lsmod 将加载的模块列表保存到一个文件中。然后找到正在运行的内核的构建配置(有关在哪里查找的提示,请参阅 “开始定义内核的构建配置”),并将其存储为 “~/test-machine-config-working”。将这两个文件传输到你的构建主机的 home 目录。

  • 在构建主机上继续该指南(例如,使用 “确保有足够的可用空间用于构建 [...]”)。

  • 当你到达 “开始准备内核构建配置 [...]” 时:在首次运行 make olddefconfig 之前,执行以下命令以基于测试机器的 “working” 内核配置来配置你的内核

    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 文件,其名称显示在显示的最后一行中;例如,具有 kernelrelease 标识符 “6.0.0-rc1-local-g928a87efa423” 的为 x86 机器构建的内核通常将存储为 “~/linux/linux-6.0.0-rc1-local-g928a87efa423-x86.tar.gz”。

    将该文件复制到你的测试机器的 home 目录。

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

    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- [...])。

其他阅读材料