如何验证错误和二分查找回归¶
本文档描述了如何检查某些 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 更改作为问题的根源。
检出最新的主线代码
cd ~/linux/ git switch --discard-changes --detach mainline/master
构建、安装和启动内核
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
检查该内核是否也出现此问题。
**段落 2**:确保“良好”内核也是“正常工作”的内核。
这尤其验证了精简的 .config 文件是否确实运行良好,否则使用它进行二分查找将是浪费时间
首先检出“良好”版本的源代码
cd ~/linux/ git switch --discard-changes --detach v6.0
如前面的*段落 1,b 节*中所述,构建、安装和启动内核——只需随意跳过 ‘du’ 命令,因为您已经有了一个粗略的估计。
确保在“损坏”内核中回归的功能确实与此内核一起工作。
**段落 3**:执行并验证二分查找。
检索“损坏”版本的源代码
git remote set-branches --add stable linux-6.1.y git fetch stable
初始化二分查找
cd ~/linux/ git bisect start git bisect good v6.0 git bisect bad v6.1.5
如前面的*段落 1,b 节*中所述,构建、安装和启动内核。
如果由于无关的原因导致构建或启动内核失败,请运行
git bisect skip
。在所有其他结果中,检查回归功能是否与新构建的内核一起工作。如果有效,请通过执行git bisect good
告诉 Git;如果无效,请改为运行git bisect bad
。所有三个命令都会使 Git 检出另一个提交;然后重新执行此步骤(例如,构建、安装、启动和测试内核,然后将结果告诉 Git)。一遍又一遍地这样做,直到 Git 显示哪个提交破坏了内容。如果您在此过程中磁盘空间不足,请查看下面的“补充任务:在过程中和之后清理”部分。
完成二分查找后,将一些内容收起来
cd ~/linux/ git bisect log > ~/bisect-log cp .config ~/bisection-config-culprit git bisect reset
尝试验证二分查找结果
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 和 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.
如果您执行了二分查找并成功验证了结果,请随意删除在实际二分查找期间构建的所有内核(段落 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,另一种在不可靠的互联网连接下效果更好。
执行以下命令以检索新的主线代码库,同时准备好添加稳定/长期系列的 branch
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 密钥、令牌,...),快速启动 VM,并启动 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
[详细信息]
检查您是否可能想要或需要调整其他一些内核配置选项
重新处理最新调整后的 .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
如果您的系列未列出或带有 ‘end of life’ 标签,则不受支持。在这种情况下,您可能想要检查后继系列(例如 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)/
写下或记住这两个值以供以后使用:它们使您能够在二分查找期间防止意外耗尽磁盘空间。
[详细信息]
显示并存储您刚刚构建的内核的版本标识符
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
现在检查此内核是否按预期工作;如果不是,请查阅参考部分以获取更多说明。
[详情]
第三步:执行二分查找并验证结果¶
在完成了所有的准备和预防措施后,您现在可以开始二分查找了。这将使您构建相当多的内核——通常大约 15 个,如果您在更新到较新系列时遇到回归(例如从 6.0.13 到 6.1.5)。但请不要担心,由于之前创建的精简构建配置,这比许多人想象的要快得多:总的来说,在普通的 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 将显示一个蓝色框,其中包含一条消息“Press any key to perform MOK management”。 在倒计时公开之前按某个键,这将打开一个菜单。 选择“Change Secure Boot state”。 Shim 的“MokManager”现在会要求您输入之前指定的一次性密码中的三个随机选择的字符。 提供它们后,确认您确实要禁用验证。 之后,允许 MokManager 重新启动机器。
[返回分步指南]
启动最后一次工作的内核¶
启动到上次工作的内核中,并简要地重新检查回归的功能是否真的有效。 [...]
这将使稍后涵盖创建和修剪配置的步骤做正确的事情。
[返回分步指南]
空间要求¶
确保有足够的可用空间来构建 Linux。 [...]
提到的数字是粗略的估计值,具有很大的额外收费以确保安全,因此您通常需要更少。
如果您有空间限制,请务必注意 关于调试符号的步骤 及其 随附的参考部分,因为禁用它们将减少相当多千兆字节的已用磁盘空间。
[返回分步指南]
二分查找范围¶
确定本指南中被认为是“良好”和“不良”的内核版本。 [...]
确定要检查的提交范围大多很简单,除非在从一个稳定系列的版本切换到更高系列的版本时发生回归(例如从 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 源代码。 关于这一点没有什么可说的——但有两种替代方法来检索源代码,这些方法可能更适合您
使用包下载 Linux 主线源代码¶
使用以下命令来检索 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
现在加深克隆的历史记录到“良好”版本的 mainline 版本的第二个前身。 如果后者是 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
。 然后,对于每个未定义的配置选项,您都会被问到如何继续; 如果您不确定要回答什么,只需按“Enter”键以应用默认值。 但请注意,对于二分查找,您通常希望使用默认值,因为否则您可能会启用一个新功能,该功能会导致看起来像回归的问题(例如由于安全限制)。
有时,尝试在较旧的主线版本上使用为某个内核(例如 6.1)准备的配置文件时,会发生奇怪的事情——尤其是在它太旧时(例如 5.15)。 这是本指南中的上一步告诉您启动一切正常运行的内核的原因之一。 因此,如果您手动添加 .config 文件,您需要确保它来自正在运行的内核,而不是来自显示回归的内核。
如果您想为另一台机器构建内核,请找到其内核构建配置; 通常,ls /boot/config-$(uname -r)
将打印其名称。 将该文件复制到构建机器并将其存储为 ~/linux/.config; 之后运行 make olddefconfig
进行调整。
[返回分步指南]
修剪内核的构建配置¶
禁用对于您的设置来说显然多余的任何内核模块。 [...]
正如分步指南中已经简要解释的那样:使用 localmodconfig 很容易发生的情况是,您自己构建的内核会缺少用于您在使用此 make 目标之前至少执行过一次的任务的模块。 当任务需要仅在您第一次执行它时才自动加载的内核模块时,就会发生这种情况。 因此,如果您自从启动内核以来从未执行过该任务,则这些模块将不会被加载——并且从 localmodconfig 的角度来看,它们看起来是多余的,因此会禁用它们以减少要编译的代码量。
您可以通过执行通常会自动加载其他内核模块的典型任务来尝试避免这种情况:启动 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
删除对证书文件的陈旧引用,否则会导致你的构建失败
./scripts/config --set-str SYSTEM_TRUSTED_KEYS ''
或者,下载所需的证书并使该配置选项指向它,正如 Debian 手册更详细地解释 -- 或者生成你自己的证书,正如 内核模块签名工具 中所解释的那样。
[返回逐步指南]
单独的调整¶
如果你想影响配置的其他方面,现在就去做。 [...]
此时,你可以使用类似 make menuconfig
或 make 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 压缩的 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 或回归可能不会出现在你从最新代码库构建的内核中的原因有很多。以下是最常见的原因:
该 bug 已被修复。
你怀疑是回归的原因是由你的内核提供程序进行的构建配置更改引起的。
你的问题可能是一个竞争条件,不会出现在你的内核中;精简的构建配置、调试符号的不同设置、使用的编译器以及各种其他因素都可能导致这种情况。
如果你在使用稳定版/长期支持版内核时遇到回归,那么它可能是特定于该系列的问题;本指南的下一步将对此进行检查。
[返回逐步指南]
检查从最新的稳定版/长期支持版代码库构建的内核¶
你是否在稳定版/长期支持版中遇到回归,但未能使用你刚刚使用最新的主线源代码构建的内核重现它?然后检查特定系列的最新代码库是否已修复该问题。 [...]
如果此内核也没有显示回归,那么很可能不需要进行二分查找。
[返回逐步指南]
确保“良好”版本真正运行良好¶
检查你构建的内核是否运行良好。 [...]
本节将重新建立一个已知的工作基础。跳过它可能很吸引人,但通常是一个坏主意,因为它会做一些重要的事情:
它将确保你之前准备的 .config 文件实际上可以按预期工作。这符合你自己的利益,因为精简配置并非万无一失 -- 并且你可能会构建和测试十个或更多的内核,然后才开始怀疑构建配置可能存在问题。
仅凭这一点就足以花费时间来完成此操作,但这不是唯一的原因。
本指南的许多读者通常运行已打补丁、使用附加模块或两者兼而有之的内核。因此,这些内核不被认为是“原始”内核 -- 因此,可能回归的内容可能从未在“良好”版本的原始版本中工作过。
对于那些注意到不同系列的稳定版/长期支持版内核之间存在回归(例如,6.0.13..6.1.5)的人来说,还有第三个原因:它将确保你在该过程中早些时候假设为“良好”的内核版本(例如,6.0)实际上可以正常工作。
[返回逐步指南]
构建你自己的“良好”内核版本¶
构建你自己的工作内核变体,并检查回归的功能是否按预期工作。 [...]
如果使用较新内核损坏的功能不适用于你的第一个自行构建的内核,请在继续之前找到并解决该原因。发生这种情况的原因有很多。以下是一些查找位置:
检查污染状态和
dmesg
的输出,可能发生了无关的事情。也许 localmodconfig 做了奇怪的事情并禁用了测试该功能所需的模块?然后你可能想要基于上次工作内核中的文件重新创建一个 .config 文件,并跳过精简它;手动禁用 .config 中的某些功能也可以减少构建时间。
也许这不是内核回归,而是由某种侥幸、损坏的 initramfs(也称为 initrd)、新固件文件或更新的用户空间软件引起的?
也许它是添加到你的发行版的内核的功能,而 vanilla Linux 在那时从未支持过?
请注意,如果你发现并修复了 .config 文件的问题,则需要使用它从最新的代码库构建另一个内核,因为你之前使用受影响的稳定版/长期支持版的 mainline 和最新版本的测试很可能存在缺陷。
[返回逐步指南]
执行二分查找并验证结果¶
完成所有准备和预防构建后,你现在可以开始二分查找了。 [...]
本段中的步骤执行并验证二分查找。
[返回逐步指南].
开始二分查找¶
开始二分查找并告诉 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 文件放在一边,因为很有可能开发人员会在你报告回归后要求它。
[返回逐步指南]
尝试还原罪魁祸首¶
尝试在最新代码库之上还原罪魁祸首,看看这是否可以修复你的回归。 [...]
这是一个可选步骤,但只要有可能,你应该尝试一下:很有可能开发人员会在你提出二分查找结果时要求你执行此步骤。所以尝试一下,你已经进入状态,再构建一个内核在这个时候应该不是什么大问题。
逐步指南涵盖了所有相关内容,但有一件稍微罕见的事情:你是否使用稳定版/长期支持版二分查找了也发生在 mainline 中的回归,但 Git 未能在 mainline 中还原提交?然后尝试在受影响的稳定版/长期支持版中还原罪魁祸首 -- 如果成功,则测试该内核版本。
[返回逐步指南]
遵循本指南期间和之后的清理步骤¶
在本指南期间和之后,你可能希望或需要删除你安装的某些内核。 [...]
本节中的步骤描述了清理过程。
[返回逐步指南].
在二分查找期间进行清理¶
要删除你安装的内核之一,请查找其“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 文件保留几周的原因。
[返回分步指南]
测试还原、补丁或更高版本¶
在报告错误时或之后,您可能想要或可能会被要求测试还原、补丁、提议的修复或其他版本。 [...]
本节中使用的所有命令都应该非常简单,因此除了以下内容外,没有什么可补充的:在按照说明设置内核标签时,请确保它不要比示例中使用的标签长太多,因为如果 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
这将生成一个 gzipped 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- [...]
)。
其他阅读材料¶
“git bisect”的手册页和 Git 文档中的 使用“git bisect”来对抗回归。
内核开发人员 Nathan Chancellor 的 使用 git bisect。