应用补丁到 Linux 内核¶
- 原文作者
Jesper Juhl,2005 年 8 月
注意
本文档已过时。在大多数情况下,您几乎肯定希望使用 Git 而不是手动使用 patch
。
Linux 内核邮件列表中经常被问到的一个问题是如何将补丁应用到内核,更具体地说,应该将哪个树/分支的补丁应用到哪个基础内核上。希望本文档能为您解释清楚。
除了说明如何应用和还原补丁之外,还简要描述了不同的内核树(以及如何应用其特定补丁的示例)。
什么是补丁?¶
补丁是一个小型文本文件,其中包含两个不同版本的源代码树之间的更改差异。补丁是使用 diff
程序创建的。
要正确应用补丁,您需要知道它是从哪个基础版本生成的,以及补丁会将源代码树更改为哪个新版本。这些都应该在补丁文件元数据中存在,或者可以从文件名中推断出来。
如何应用或还原补丁?¶
您可以使用 patch
程序应用补丁。patch 程序读取 diff(或补丁)文件,并对其中描述的源代码树进行更改。
Linux 内核的补丁是相对于包含内核源代码目录的父目录生成的。
这意味着补丁文件内部文件的路径包含生成补丁时所依据的内核源代码目录的名称(或其他一些目录名称,如“a/”和“b/”)。
由于这不太可能与您本地计算机上的内核源代码目录的名称匹配(但通常对于查看未标记的补丁是针对哪个版本生成的很有用),因此您应该更改到您的内核源代码目录,然后在应用时从补丁文件中的文件名中删除路径的第一个元素(patch
的 -p1
参数就是这样做的)。
要还原之前应用的补丁,请使用 patch 的 -R 参数。因此,如果您像这样应用了一个补丁
patch -p1 < ../patch-x.y.z
您可以像这样还原(撤消)它
patch -R -p1 < ../patch-x.y.z
如何将补丁/diff 文件馈送到 patch
?¶
这(像 Linux 和其他类似 UNIX 的操作系统一样)可以通过几种不同的方式完成。
在下面的所有示例中,我使用以下语法通过 stdin 将文件(以未压缩的形式)馈送到 patch
patch -p1 < path/to/patch-x.y.z
如果您只想能够遵循下面的示例,并且不想知道使用 patch 的多种方式,那么您可以停止阅读此部分。
Patch 也可以通过 -i 参数获取要使用的文件的名称,如下所示
patch -p1 -i path/to/patch-x.y.z
如果您的补丁文件使用 gzip 或 xz 压缩,并且您不想在应用之前解压缩它,那么您可以改为像这样将其馈送到 patch
xzcat path/to/patch-x.y.z.xz | patch -p1
bzcat path/to/patch-x.y.z.gz | patch -p1
如果您希望在应用之前手动解压缩补丁文件(我假设您在下面的示例中已经完成了),那么您只需对文件运行 gunzip 或 xz,就像这样
gunzip patch-x.y.z.gz
xz -d patch-x.y.z.xz
这将使您得到一个纯文本的 patch-x.y.z 文件,您可以根据需要通过 stdin 或 -i
参数将其馈送到 patch。
patch 的其他一些不错的参数是 -s
,它使 patch 保持静默,只输出错误,这很好,可以防止错误滚动出屏幕太快,以及 --dry-run
,它使 patch 仅打印将发生的事情的列表,但实际上不做任何更改。最后,--verbose
告诉 patch 打印有关正在完成的工作的更多信息。
打补丁时的常见错误¶
当 patch 应用补丁文件时,它会尝试以不同的方式验证文件的正确性。
检查该文件是否看起来像有效的补丁文件,以及检查正在修改的代码周围的代码是否与补丁中提供的上下文匹配,只是 patch 执行的两个基本正确性检查。
如果 patch 遇到看起来不太正确的内容,它有两种选择。它可以拒绝应用更改并中止,也可以尝试找到一种方法来应用补丁并进行一些小的更改。
patch 将尝试修复的一个“不太正确”的示例是,如果所有上下文都匹配,要更改的行也匹配,但行号不同。例如,如果补丁在文件中间进行更改,但由于某些原因,在文件开头附近添加或删除了几行,则可能会发生这种情况。在这种情况下,一切看起来都不错,它只是向上或向下移动了一点,patch 通常会调整行号并应用补丁。
每当 patch 应用必须稍作修改才能使其适应的补丁时,它都会通过说明补丁应用了模糊来告诉您。您应该警惕这种更改,因为即使 patch 可能做对了,它也并非 /总是/ 做对,结果有时会是错误的。
当 patch 遇到无法用模糊修复的更改时,它会直接拒绝它,并留下一个带有 .rej
扩展名的文件(一个拒绝文件)。您可以读取此文件以查看哪些更改无法应用,这样您就可以在需要时手动修复它。
如果您没有将任何第三方补丁应用到您的内核源代码中,而只应用了来自 kernel.org 的补丁,并且您按照正确的顺序应用了补丁,并且您自己没有对源文件进行任何修改,那么您应该永远不会看到来自 patch 的模糊或拒绝消息。如果您仍然看到此类消息,那么您的本地源代码树或补丁文件很可能以某种方式损坏。在这种情况下,您应该尝试重新下载补丁,如果情况仍然不正常,那么建议您从 kernel.org 下载一个全新的完整树。
让我们更详细地了解一下 patch 可以生成的一些消息。
如果 patch 停止并显示 File to patch:
提示,则表示 patch 找不到要打补丁的文件。最有可能的是,您忘记指定 -p1 或您在错误的目录中。较少的情况是,您会发现需要使用 -p0
而不是 -p1
应用的补丁(读取补丁文件应该可以揭示是否是这种情况——如果是,那么这是创建补丁的人的错误,但不是致命的)。
如果您得到 Hunk #2 succeeded at 1887 with fuzz 2 (offset 7 lines).
或类似的消息,则表示 patch 必须调整更改的位置(在此示例中,它需要从预期进行更改的位置移动 7 行才能使其适应)。
生成的文件可能正常,也可能不正常,具体取决于文件与预期不同的原因。
如果您尝试应用针对与您尝试打补丁的内核版本不同的内核版本生成的补丁,通常会发生这种情况。
如果您收到类似 Hunk #3 FAILED at 2387.
的消息,则表示补丁无法正确应用,并且补丁程序无法通过模糊的方式解决。这将生成一个 .rej
文件,其中包含导致补丁失败的更改,以及一个 .orig
文件,其中显示了无法更改的原始内容。
如果您得到 Reversed (or previously applied) patch detected! Assume -R? [n]
,则表示 patch 检测到补丁中包含的更改似乎已经完成。
如果您实际上之前应用了此补丁,并且您只是错误地重新应用了它,那么只需说 [n]o 并中止此补丁。如果您之前应用了此补丁并且实际上打算还原它,但忘记指定 -R,那么您可以在此处说 [y]es,以使 patch 为您还原它。
如果补丁的创建者在创建补丁时反转了源目录和目标目录,也可能会发生这种情况,在这种情况下,还原补丁实际上会应用它。
类似于 patch: **** unexpected end of file in patch
或 patch unexpectedly ends in middle of line
的消息表示 patch 无法理解您馈送到它的文件。要么您的下载已损坏,要么您尝试将压缩的补丁文件馈送到 patch 而没有先解压缩,要么您正在使用的补丁文件在传输过程中被邮件客户端或邮件传输代理破坏,例如,通过将长行拆分为两行。通常,这些警告可以通过连接(串联)已拆分的两行来轻松修复。
正如我上面已经提到的,如果您将来自 kernel.org 的补丁应用于未经修改的正确版本的源代码树,这些错误应该永远不会发生。因此,如果使用 kernel.org 的补丁时出现这些错误,您可能应该假设您的补丁文件或源代码树已损坏,我建议您从重新下载完整的内核树和要应用的补丁开始。
除了 patch
之外,还有其他替代方案吗?¶
是的,有其他替代方案。
您可以使用 interdiff
程序 (http://cyberelk.net/tim/patchutils/) 生成一个表示两个补丁之间差异的补丁,然后应用结果。
这将允许您一步从 5.7.2 升级到 5.7.3。 interdiff 的 -z 标志甚至允许您直接以 gzip 或 bzip2 压缩形式向其馈送补丁,而无需使用 zcat 或 bzcat 或手动解压缩。
下面是如何一步从 5.7.2 升级到 5.7.3 的方法
interdiff -z ../patch-5.7.2.gz ../patch-5.7.3.gz | patch -p1
尽管 interdiff 可能会为您节省一两个步骤,但通常建议您执行额外的步骤,因为 interdiff 在某些情况下可能会出错。
另一个替代方案是 ketchup
,这是一个用于自动下载和应用补丁的 python 脚本 (https://www.selenic.com/ketchup/)。
其他好用的工具包括 diffstat,它显示补丁所做更改的摘要;lsdiff,它显示补丁文件中受影响文件的简短列表,以及(可选)每个补丁开始的行号;以及 grepdiff,它显示补丁修改的文件的列表,其中补丁包含给定的正则表达式。
在哪里可以下载补丁?¶
这些补丁可在 https://linuxkernel.org.cn/ 上找到。大多数最新的补丁都链接在首页,但它们也有特定的位置。
5.x.y (-stable) 和 5.x 补丁位于
5.x.y 增量补丁位于
-rc 补丁未存储在 Web 服务器上,而是根据 git 标签按需生成,例如
稳定的 -rc 补丁位于
5.x 内核¶
这些是 Linus 发布的基准稳定版本。编号最高的版本是最新的版本。
如果发现回归或其他严重缺陷,则将在该基准版本之上发布一个 -stable 修复补丁(见下文)。一旦发布新的 5.x 基准内核,就会提供一个补丁,它是先前 5.x 内核与新内核之间的差异。
要应用从 5.6 移动到 5.7 的补丁,您需要执行以下操作(请注意,此类补丁不适用于 5.x.y 内核,而是适用于基准 5.x 内核——如果您需要从 5.x.y 移动到 5.x+1,您需要先还原 5.x.y 补丁)。
以下是一些示例
# moving from 5.6 to 5.7
$ cd ~/linux-5.6 # change to kernel source dir
$ patch -p1 < ../patch-5.7 # apply the 5.7 patch
$ cd ..
$ mv linux-5.6 linux-5.7 # rename source dir
# moving from 5.6.1 to 5.7
$ cd ~/linux-5.6.1 # change to kernel source dir
$ patch -p1 -R < ../patch-5.6.1 # revert the 5.6.1 patch
# source dir is now 5.6
$ patch -p1 < ../patch-5.7 # apply new 5.7 patch
$ cd ..
$ mv linux-5.6.1 linux-5.7 # rename source dir
5.x.y 内核¶
具有 3 位版本号的内核是 -stable 内核。它们包含针对在给定的 5.x 内核中发现的安全问题或重大回归的小型(ish)关键修复。
对于想要最新稳定内核并且不希望帮助测试开发/实验版本的用户,这是推荐的分支。
如果没有 5.x.y 内核可用,则编号最高的 5.x 内核是当前的稳定内核。
-stable 团队提供正常补丁和增量补丁。下面是如何应用这些补丁。
普通补丁¶
这些补丁不是增量的,这意味着例如 5.7.3 补丁不是应用于 5.7.2 内核源代码之上,而是应用于基准 5.7 内核源代码之上。
因此,为了将 5.7.3 补丁应用于您现有的 5.7.2 内核源代码,您必须首先撤消 5.7.2 补丁(这样您就只剩下基准 5.7 内核源代码),然后应用新的 5.7.3 补丁。
这是一个小例子
$ cd ~/linux-5.7.2 # change to the kernel source dir
$ patch -p1 -R < ../patch-5.7.2 # revert the 5.7.2 patch
$ patch -p1 < ../patch-5.7.3 # apply the new 5.7.3 patch
$ cd ..
$ mv linux-5.7.2 linux-5.7.3 # rename the kernel source dir
增量补丁¶
增量补丁是不同的:它们不是应用于基准 5.x 内核之上,而是应用于之前的稳定内核 (5.x.y-1) 之上。
下面是如何应用这些补丁的示例
$ cd ~/linux-5.7.2 # change to the kernel source dir
$ patch -p1 < ../patch-5.7.2-3 # apply the new 5.7.3 patch
$ cd ..
$ mv linux-5.7.2 linux-5.7.3 # rename the kernel source dir
-rc 内核¶
这些是发布候选内核。这些是 Linus 在他认为当前 git(内核的源代码管理工具)树处于足够健全的状态以进行测试时发布的开发内核。
这些内核不稳定,如果您打算运行它们,则应预期会出现偶尔的损坏。然而,这是主开发分支中最稳定的,并且也是最终将成为下一个稳定内核的分支,因此由尽可能多的人进行测试非常重要。
对于那些想要帮助测试开发内核但又不想运行一些真正实验性的东西的人来说,这是一个很好的运行分支(这些人应该查看下面关于 -next 和 -mm 内核的部分)。
-rc 补丁不是增量的,它们应用于基准 5.x 内核,就像上面描述的 5.x.y 补丁一样。-rcN 后缀之前的内核版本表示此 -rc 内核最终将成为的内核版本。
因此,5.8-rc5 表示这是 5.8 内核的第五个发布候选版本,并且该补丁应应用于 5.7 内核源代码之上。
以下是如何应用这些补丁的 3 个示例
# first an example of moving from 5.7 to 5.8-rc3
$ cd ~/linux-5.7 # change to the 5.7 source dir
$ patch -p1 < ../patch-5.8-rc3 # apply the 5.8-rc3 patch
$ cd ..
$ mv linux-5.7 linux-5.8-rc3 # rename the source dir
# now let's move from 5.8-rc3 to 5.8-rc5
$ cd ~/linux-5.8-rc3 # change to the 5.8-rc3 dir
$ patch -p1 -R < ../patch-5.8-rc3 # revert the 5.8-rc3 patch
$ patch -p1 < ../patch-5.8-rc5 # apply the new 5.8-rc5 patch
$ cd ..
$ mv linux-5.8-rc3 linux-5.8-rc5 # rename the source dir
# finally let's try and move from 5.7.3 to 5.8-rc5
$ cd ~/linux-5.7.3 # change to the kernel source dir
$ patch -p1 -R < ../patch-5.7.3 # revert the 5.7.3 patch
$ patch -p1 < ../patch-5.8-rc5 # apply new 5.8-rc5 patch
$ cd ..
$ mv linux-5.7.3 linux-5.8-rc5 # rename the kernel source dir
-mm 补丁和 linux-next 树¶
-mm 补丁是 Andrew Morton 发布的实验性补丁。
过去,-mm 树也用于测试子系统补丁,但此功能现在通过 linux-next (https://linuxkernel.org.cn/doc/man-pages/linux-next.html) 树完成。子系统维护人员首先将其补丁推送到 linux-next,然后在合并窗口期间,将其直接发送给 Linus。
-mm 补丁充当新功能和其他未通过子系统树合并的实验性补丁的试验场。一旦此类补丁在 -mm 中证明其价值一段时间,Andrew 就会将其推送到 Linus 以便包含在主线中。
linux-next 树每天更新,并且包含 -mm 补丁。两者都处于不断变化之中,并且包含许多实验性功能、许多不适合主线的调试补丁等,并且是本文档中描述的分支中最具实验性的分支。
这些补丁不适合在应该稳定的系统上使用,并且运行它们的风险比任何其他分支都要大(请确保您有最新的备份 - 这适用于任何实验性内核,但对于 -mm 补丁或使用来自 linux-next 树的内核更是如此)。
非常感谢对 -mm 补丁和 linux-next 的测试,因为它们的全部意义在于在更改合并到更稳定的主线 Linus 树之前消除回归、崩溃、数据损坏错误、构建中断(以及任何其他一般错误)。
但是 -mm 和 linux-next 的测试人员应该意识到,与其他任何树相比,中断更为常见。
本文总结了对各种内核树的解释列表。我希望您现在清楚如何应用各种补丁并帮助测试内核。
感谢 Randy Dunlap、Rolf Eike Beer、Linus Torvalds、Bodo Eggert、Johannes Stezenbach、Grant Coady、Pavel Machek 以及其他可能被我遗忘的人员对本文档的审阅和贡献。