英语

将补丁应用于 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 应用一个它需要稍作修改才能适应的补丁时,它会通过提示“补丁应用时带有 fuzz”来告知您。您应该警惕此类更改,因为即使 patch 很可能做对了,它也并非/总是/做对,结果有时会是错误的。

patch 遇到无法通过 fuzz 修复的更改时,它会直接拒绝,并留下一个带有 .rej 扩展名(一个拒绝文件)的文件。您可以阅读此文件以确切了解无法应用的更改是什么,这样如果您愿意,可以手动修复它。

如果您的内核源代码没有应用任何第三方补丁,而只有来自 kernel.org 的补丁,并且您按正确顺序应用补丁,并且您自己没有修改源文件,那么您应该永远不会看到 patch 发出的 fuzz 或 reject 消息。如果您仍然看到此类消息,那么您的本地源代码树或补丁文件很可能已以某种方式损坏。在这种情况下,您应该尝试重新下载补丁,如果问题仍然存在,建议您从 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. 的消息,则表示补丁无法正确应用,并且 patch 程序无法通过模糊匹配完成。这将生成一个 .rej 文件,其中包含导致补丁失败的更改,以及一个 .orig 文件,显示无法更改的原始内容。

如果您收到 Reversed (or previously applied) patch detected!  Assume -R? [n],则表示 patch 检测到补丁中包含的更改似乎已经完成。

如果您确实之前应用过此补丁,并且只是错误地重新应用了它,那么只需回答 [n]o 并中止此补丁。如果您之前应用过此补丁,并且实际上打算还原它,但忘记指定 -R,那么您可以在此处回答 [y]es,让 patch 为您还原它。

如果补丁创建者在创建补丁时颠倒了源目录和目标目录,也会发生这种情况,在这种情况下,还原补丁实际上会应用它。

类似 patch: **** unexpected end of file in patchpatch 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 补丁不存储在网络服务器上,而是根据 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 内核

带有三位版本号的内核是 -stable 内核。它们包含针对特定 5.x 内核中发现的安全问题或重大回归的少量关键修复。

这是推荐给那些想要最新稳定内核,且对测试开发/实验版本不感兴趣的用户的分支。

如果没有 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 以及我可能遗漏的其他人员对本文档的审阅和贡献。