Note
此文件的目的是为让中文读者更容易阅读和理解,而不是作为一个分支。 因此, 如果您对此文件有任何意见或更新,请先尝试更新原始英文文件。 如果您发现本文档与原始文件有任何不同或者有翻译问题,请发建议或者补丁给 该文件的译者,或者请求中文文档维护者和审阅者的帮助。
- Original:
- 翻译:
袁维杰 Weijie Yuan <wy@wyuan.org>
将补丁应用到 Linux 内核¶
- 原文作者:
Jesper Juhl,2005 年 8 月
Note
本文档已经过时。绝大多数情况下,你几乎肯定应该考虑使用 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 文件。你可以按自己的喜好,
通过标准输入或 -i 参数把它交给 patch。
patch 还有几个很实用的参数:-s 会让 patch 除错误外不输出任何
信息,以免错误信息很快滚出屏幕;--dry-run 只列出将会发生的事情,
并不实际修改文件;最后,--verbose 会让 patch 输出更详细的处理
信息。
应用补丁时的常见错误¶
应用补丁文件时,patch 会用多种方式检查文件是否合理。
例如,patch 会检查文件是否像一个有效的补丁文件,也会检查待修改代码 周围的内容是否与补丁提供的上下文相符。这只是它所做的两项基本检查。
如果遇到不太对劲的地方,patch 有两种选择:拒绝应用改动并中止,或者 稍作调整,设法把补丁应用上去。
patch 会尝试调整的一种情况是:上下文和待修改的行全都匹配,只有行号 不同。例如,补丁要修改文件中部的内容,但文件开头附近由于某种原因增删 了几行,就会出现这种情况。此时所有内容其实都能对上,只是整体向前或 向后移动了一些,patch 通常会相应调整行号并应用补丁。
每当 patch 必须略作调整才能匹配时,它会提示补丁是在经过模糊匹配 (fuzz)后应用的。你应当谨慎对待这样的改动:patch 很可能处理 正确,但它并不保证每次都能正确处理,有时会得到错误的结果。
如果某项改动无法通过模糊匹配调整,patch 会直接拒绝这项改动,并留下
一个扩展名为 .rej 的文件(拒绝文件)。你可以阅读这个文件,准确
了解哪项改动未能应用,再视需要手工处理。
假如内核源码中没有第三方补丁,只有来自 kernel.org 的补丁,而且补丁 应用顺序正确,源码文件也没有经过自行修改,那么 patch 绝不应报告模糊 匹配或拒绝应用。如果还是看到这类信息,你的本地源码树或补丁文件很可能 已经损坏。此时应先尝试重新下载补丁;如果问题依旧,建议从 kernel.org 重新下载完整的全新源码树,从头再来。
下面进一步看看 patch 可能输出的几种信息。
如果 patch 停下来并显示 File to 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 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://kernel.org/ 获取。网站首页提供了最新补丁的链接, 各类补丁也有各自固定的存放位置。
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 补丁。
下面是两个示例:
# 从 5.6 更新到 5.7
$ cd ~/linux-5.6 # 进入内核源码目录
$ patch -p1 < ../patch-5.7 # 应用 5.7 补丁
$ cd ..
$ mv linux-5.6 linux-5.7 # 重命名源码目录
# 从 5.6.1 更新到 5.7
$ cd ~/linux-5.6.1 # 进入内核源码目录
$ patch -p1 -R < ../patch-5.6.1 # 撤销 5.6.1 补丁
# 源码目录现在是 5.6
$ patch -p1 < ../patch-5.7 # 应用新的 5.7 补丁
$ cd ..
$ mv linux-5.6.1 linux-5.7 # 重命名源码目录
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 # 进入内核源码目录
$ patch -p1 -R < ../patch-5.7.2 # 撤销 5.7.2 补丁
$ patch -p1 < ../patch-5.7.3 # 应用新的 5.7.3 补丁
$ cd ..
$ mv linux-5.7.2 linux-5.7.3 # 重命名内核源码目录
增量补丁¶
增量补丁则不同:它们不应用到基础 5.x 内核之上,而应用到前一个稳定版 内核(5.x.y-1)之上。
下面是应用增量补丁的示例:
$ cd ~/linux-5.7.2 # 进入内核源码目录
$ patch -p1 < ../patch-5.7.2-3 # 应用新的 5.7.3 补丁
$ cd ..
$ mv linux-5.7.2 linux-5.7.3 # 重命名内核源码目录
-rc 内核¶
这些是候选发布版内核。每当 Linus 认为当前的 git 树(git 是内核的 源码管理工具)处于相当合理、足以测试的状态时,就会发布这样的开发版内核。
这些内核并不稳定;如果打算运行,就要预料到它们偶尔会出故障。不过, 在几个主要开发分支中,这是最稳定的一个,而且它最终会成为下一个稳定版 内核,所以让尽可能多的人参与测试十分重要。
如果你想帮助测试开发版内核,但不想运行真正具有实验性的内容,这个分支 很合适(有关真正实验性的内容,请参阅下文介绍 -next 和 -mm 内核的 章节)。
-rc 补丁不是增量补丁;与上文介绍的 5.x.y 补丁一样,它们应用到基础 5.x 内核之上。-rcN 后缀前面的内核版本号,表示这个 -rc 内核最终会 成为哪个版本。
因此,5.8-rc5 表示它是 5.8 内核的第五个候选发布版,这个补丁应当 应用到 5.7 内核源码之上。
下面是三个应用这类补丁的示例:
# 第一个示例:从 5.7 更新到 5.8-rc3
$ cd ~/linux-5.7 # 进入 5.7 源码目录
$ patch -p1 < ../patch-5.8-rc3 # 应用 5.8-rc3 补丁
$ cd ..
$ mv linux-5.7 linux-5.8-rc3 # 重命名源码目录
# 接着从 5.8-rc3 更新到 5.8-rc5
$ cd ~/linux-5.8-rc3 # 进入 5.8-rc3 目录
$ patch -p1 -R < ../patch-5.8-rc3 # 撤销 5.8-rc3 补丁
$ patch -p1 < ../patch-5.8-rc5 # 应用新的 5.8-rc5 补丁
$ cd ..
$ mv linux-5.8-rc3 linux-5.8-rc5 # 重命名源码目录
# 最后尝试从 5.7.3 更新到 5.8-rc5
$ cd ~/linux-5.7.3 # 进入内核源码目录
$ patch -p1 -R < ../patch-5.7.3 # 撤销 5.7.3 补丁
$ patch -p1 < ../patch-5.8-rc5 # 应用新的 5.8-rc5 补丁
$ cd ..
$ mv linux-5.7.3 linux-5.8-rc5 # 重命名源码目录
-mm 补丁与 linux-next 树¶
-mm 补丁是 Andrew Morton 发布的实验性补丁。
过去,-mm 树还用于测试子系统补丁;现在这项工作由 linux-next (https://www.kernel.org/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,以及其他可能被我 遗漏的人,感谢他们对本文档的审阅和贡献。