如何给Linux内核打补丁

3 分钟读完

Linux Kernel邮件列表上问的最多的一个问题就是:如何给Linux内核打补丁,更具体点就是,诸多trees/branches的一个补丁应该应用于哪个base kernel。希望这个文档能帮你解答这些疑问。

本文除了介绍如何打补丁以及撤销补丁,还介绍了不同的内核树。

#什么是补丁?

补丁就是一个文本文件,该文件包含了一个源码树的两个不同版本之间增量变化。补丁文件是由diff命令创建的。
为了正确打一个补丁,你需要知道这个补丁是从哪个根(base)产生的,以及这个补丁会使源码树升级成哪个版本。这些应该是出现在补丁文件的元数据(metadata),或者可以从文件名来推断。

#如何应用和撤销补丁?

通过patch命令应用一个补丁,patch命令读取一个diff/patch文件,根据文件中的描述信息来改变源码树。

Linux内核补丁相对于包含内核源码目录的目录生成。

这表示补丁文件中的文件路径包含内核源代码目录名字(或者其它目录名如’a/’和’b/’).

由于它不太可能匹配你本地内核源码目录的名字(但通常是非常有用的信息,用来查看生成补丁的版本),你应该进入你的内核源码目录,然后过滤掉补丁文件中的所有文件名路径的第一个元素(patch命令的 -p1选项有这个功能)。

如果要撤销当前已经打好的补丁,使用patch命令的 -R选项。 所以,如果你使用如下命令打补丁:

	patch -p1 < ../patch-x.y.z

你可以撤销该补丁,使用下面的命令:

	patch -R -p1 < ../patch-x.y.z

#怎么给patch命令提供(feed)一个patch/diff文件?

这里有几种不同的方法(通常是Linux和其它类UNIX系统)。
在下面所有的例子中,我使用下面的语法,通过标准输入stdin提供(feed)补丁文件(未压缩形式):

	patch -p1 < path/to/patch-x.y.z

如果你的补丁文件用gzip或bzip2压缩,而你不想解压后打补丁,那么你可以这样来应用补丁:

	zcat path/to/patch-x.y.z.gz | patch -p1
	bzcat path/to/patch-x.y.z.bz2 | patch -p1

如果你想在打补丁之前手动解压补丁文件(我假定你已经进行过下面的操作),那么你可以简单的运行gunzip或者bunzip2,如下:

	gunzip patch-x.y.z.gz
	bunzip2 patch-x.y.z.bz2

上面的命令将产生一个纯文本的patch-x.y.z文件,你可以通过标准输入(stdin,加上管道)或者-i选项把这个文件输入到patch命令.

patch命令的其它有用选项:

-s	在patch执行过程中只输出错误信息。
--dry-run	只是输出操作清单。
--verbose	让patch输出尽可能多的信息。

#打补丁时的常见错误

当patch命令应用一个补丁文件时,它会用不同的方法验证补丁文件的完整性。
patch命令做的两个完整性检查:
检查文件是不是一个有效的补丁文件;
检查修改的bits附近的代码是否匹配补丁文件中相应位置上下文;

如果patch命令执行过程中遇到一些不太正确(not ‘quite right’)的问题,它有两个选择,一是拒绝修补更新变化,退出;二是尝试找到一个方法对补丁做一些小的改动。

一个例子(补丁不太正确,patch命令试图修复):上下文匹配,需要修改的行匹配,但是行号不匹配。这是可能发生的,例如,patch命令对文件做一些变动,但是由于某些原因,在文件开始处有几行需要被添加或者删除。在这种情况下,一切看起来都没问题,它只是向上或向下移动一下,patch命令通常会调整行号,然后修补补丁。

每当patch命令使用它不得不进行稍微修改的补丁文件时,它将提示”补丁通过’fuzz’被使用“来告诉你。你应该警惕这种变化,因为即使patch命令可能让补丁文件正确,它不可能总使补丁文件正确,结果有时可能会出错。

当patch命令遇到它不能通过fuzz修补的变化时,它完全拒绝这个文件,并且生成一个扩展名为.rej的文件(一个reject file)。你能够从这个rej文件找出哪个change不能被修补,所以你能够手动修复它。

如果你没有第三方补丁来修补你的内核源码,只有从kernel.org获得的补丁,以正确的顺序打补丁,不对源文件作出修改(译者注:应该是指补丁源文件,这里要说明的意思是,你使用完整的kernel.org上的补丁文件,使用patch命令执行基本不会出问题),那么你应该看不到来自patch命令的fuzz或者reject消息。但是如果你看到了fuzz或者reject消息,那么这可能是一个高风险状况:你的本地source tree或者补丁文件损坏了。在这种情况下,你应该尝试重新下载补丁,如果问题还没有解决,建议你从kernel.org下载一个全新的source tree(译者注:应该是完整的内核源码,因为这个问题可能说明你本地内核代码被恶意篡改了).

让我们看一下patch命令可能产生的其它信息。

如果patch命令停止,出现”File to patch:”提示,说明patch命令没有找到补丁文件。你很可能忘记了指定 -p1选项,或者你的当前工作目录不对。其它情况,补丁文件需要 -p0选项而不是-p1选项(如果是这种情况,阅读补丁文件就可以发现,如果是的话,这是由写补丁的人犯的错误,这不算严重)。

如果显示”Hunk #2 succeeded at 1887 with fuzz 2 (offset 7 lines).”或者类似这个信息的,这说明patch命令不得不调整change的位置(上面这个例子中,它需要移动7行)。由于补丁文件和期望的文件不同,结果文件可能是也可能不是正确的。如果试图将一个内核版本的补丁应用到另一个不同版本的内核上,经常会出现这种提示消息。

如果显示”Hunk #3 FAILED at 2387.”,说明这个补丁文件不能够正确修补,patch命令用fuzz方法也不能正确执行。这将会产生一个.rej文件,该文件包含导致patch命令失败的change,还会产生一个.orig文件,该文件包含不能改变的原始内容。

如果显示”Reversed (or previously applied) patch detected! Assume -R? [n]”,说明patch命令检测到补丁文件中的change已经修补了内核。 如果你之前确实打过该补丁,你只是误操作,那么输入[n]o来中止patch命令。如果你之前打过该补丁,现在想撤销它,却忘记了指定-R选项,那么输出[y]es来撤销补丁。 如果补丁的作者在创建补丁时reversed the source 和目标目录,这也会发生,在这种情况下,恢复/撤销补丁将在实际中应用它。

如果显示类似”patch: ** unexpected end of file in patch” 或者 “patch unexpectedly ends in middle of line”的消息,表示patch命令不能正确解析你输入的补丁文件。或许是你下载的文件损坏了,你试图输入一个压缩的补丁文件,或许是你使用的补丁文件由于邮件客户端/邮件传输代理在传输过程中损坏了,例如,通过分隔长行为两行。往往这些警告可以很容易的修复,通过连接被分隔的两行。

正如我上面已经提到的,如果将从kernel.org获取的补丁应用到正确的未经修改的源码树上,将不会出现这些错误。所以,如果你使用kernel.org上的补丁,却还是遇到这些错误,那么应该是你的补丁文件或者你的内核源码树损坏了,建议你重新下载一个完整的内核树和你想使用的补丁文件。

#有patch命令的替代选择吗?

Yes,当然有其它选择。
你可以使用interdiff命令 (http://cyberelk.net/tim/patchutils/)来生成一个表示两个补丁差别的补丁,然后使用该补丁。

这允许你一步从2.6.12.2升级到2.6.12.3。interdiff命令的-z选项允许你直接输入gzip和bzip压缩文件,而不必先使用zcat和bzcat或者解压操作。

下面命令演示了如何通过一步操作完成从2.6.12.2到2.6.12.3的升级:

	interdiff -z ../patch-2.6.12.2.bz2 ../patch-2.6.12.3.gz | patch -p1

尽管interdiff命令可以为你节省一两步操作,还是建议你加上其它操作,因为interdiff在一些情况下很容易出错。

另外一个选择是ketchup命令,它是一个Python脚本,可以自动下载补丁和打补丁(http://www.selenic.com/ketchup/).

其它不错的工具有diffstat, 它显示一个补丁文件的change概要;lsdiff,它展示在补丁文件中受影响的文件列表,并且标识(可选的)每个补丁开始的行号;grepdiff,它展示一个补丁修改文件列表,这个补丁包含一个给定的正则表达式。

#在哪里可以下载补丁?

在http://kernel.org/网站可以下载,最新的补丁程序被链接到首页,它们也有自己特定主页。

2.6.x.y (-stable) 和 2.6.x 补丁位于:

ftp://ftp.kernel.org/pub/linux/kernel/v2.6/

-rc补丁位于:

ftp://ftp.kernel.org/pub/linux/kernel/v2.6/testing/

-git补丁位于:

ftp://ftp.kernel.org/pub/linux/kernel/v2.6/snapshots/

-mm补丁位于:

ftp://ftp.kernel.org/pub/linux/kernel/people/akpm/patches/2.6/

在ftp.kernel.org服务器上,你可以使用ftp.cc.kernel.org,cc是一个国家代号(country ocde)。这样你就可以从一个地理位置离你最可能近的镜像网站下载,获得更快的下载速度,减少全球范围的带宽,降低kernel.org主服务器的负载——这都是好的事情,所以尽可能的使用镜像网站。

#2.6.x内核

这些都是由Linus发布的base stable发行版。编号最高的发行版是最新的。

如果regressions或者其它严重缺陷被发现,那么在这个base上会有一个 -stable 修补补丁发布(如下)。一旦发布一个新的2.6.x base 内核,一个补丁会在2.6.x内核和新内核之间按照增量的方式编写。

要从版本2.6.11升级到2.6.12, 执行如下操作(注意这些补丁不适用于2.6.x.y上的内核,但是2.6.x上的内核,如果你需要从2.6.x.y升级到2.6.x+1,你需要先撤销2.6.x.y的补丁)。

下面是一些例子:

# 从2.6.11升级到2.6.12
$ cd ~/linux-2.6.11			#切换到源代码目录
$ patch -p1 < ../patch-2.6.12		# 应用 2.6.12 补丁
$ cd ..
$ mv linux-2.6.11 linux-2.6.12		# 重命名源代码目录

# 从2.6.11.1升级到2.6.12
$ cd ~/linux-2.6.11.1			# 切换到源代码目录
$ patch -p1 -R < ../patch-2.6.11.1	# 撤销2.6.11.1补丁
					# 源码目录现在是 2.6.11
$ patch -p1 < ../patch-2.6.12		# 应用新2.6.12 补丁
$ cd ..
$ mv linux-2.6.11.1 linux-2.6.12	# 重命名源代码目录

#2.6.x.y内核 用4位数字命名的版本是 -stable 内核。它们包含少量的关键修复,包括对给定2.6.x中的安全问题或者重大regressions的修复。

这是为某些用户推荐的分支,这些用户想使用最新稳定内核,却不想帮助测试开发/试验版本。

如果没有可用的2.6.x.y内核,那么最高编号的2.6.x内核是当前稳定内核。

注:-stable 团队通常为最新的mainline发行版编写增量补丁,但是我下面只涉及(cover)非增量的补丁。增量补丁可以在这找到( ftp://ftp.kernel.org/pub/linux/kernel/v2.6/incr/)。

这些补丁不是递增的,意味着如2.6.12.3补丁不能在2.6.12.2内核源码上使用,而是要用在base 2.6.12内核源码上。

所以,为了在你当前的2.6.12.2内核源码上应用2.6.12.3补丁,你必须先撤回(back out)2.6.12.2补丁(这样你就剩下一个base 2.6.12内核源码),然后应用新的2.6.12.3补丁。

下面是一个实例:

$ cd ~/linux-2.6.12.2			# 切换到内核源码目录
$ patch -p1 -R < ../patch-2.6.12.2	# 撤销 2.6.12.2 补丁
$ patch -p1 < ../patch-2.6.12.3		# 使用新的 2.6.12.3 补丁
$ cd ..
$ mv linux-2.6.12.2 linux-2.6.12.3	# 重命名内核源码目录

#-rc 内核 这些都是发行候选内核。这些都是Linus发布的开发版内核,当他认为当前git(内核源码管理工具)树是在一个合理的正常状态足够测试时。

这些内核都是不稳定的,如果你打算运行它们,你应该做好偶尔breakage的准备。但是,这是主开发分支最稳定的,也是最终转变成下一代稳定内核,所以这些内核被尽可能多的人来测试是非常重要的。

这是一个好的分支,可以让一部分人来运行,这些人愿意帮助测试开发版内核,却不愿意运行真正的试验东西(这样的人应该看一下下面关于-git和-mm内核的章节)。

-rc 补丁不是增量的,它们应用于base 2.6.x内核,就像上面所说的2.6.x.y补丁一样。-rcN后缀前面内核版本表示-rc 内核将转变成的内核版本。

所以2.6.13-rc5表示这是2.6.13内核的第5个发行候选版,补丁应该应用在2.6.12内核源码上。

下面有3个实例,关于如何打这些补丁:

# 从2.6.12 升级到 2.6.13-rc3
$ cd ~/linux-2.6.12			# 切换到 2.6.12 源码目录
$ patch -p1 < ../patch-2.6.13-rc3	# 应用 2.6.13-rc3 补丁
$ cd ..
$ mv linux-2.6.12 linux-2.6.13-rc3	# 重命名内核源码目录

# 从 2.6.13-rc3 升级到 2.6.13-rc5
$ cd ~/linux-2.6.13-rc3			# 切换到 2.6.13-rc3 目录
$ patch -p1 -R < ../patch-2.6.13-rc3	# 撤销 2.6.13-rc3 补丁
$ patch -p1 < ../patch-2.6.13-rc5	# 应用 2.6.13-rc5 新补丁
$ cd ..
$ mv linux-2.6.13-rc3 linux-2.6.13-rc5	# 重命名内核源码目录

# 从 2.6.12.3 升级 2.6.13-rc5
$ cd ~/linux-2.6.12.3			# 切换到内核源码目录
$ patch -p1 -R < ../patch-2.6.12.3	# 撤销 2.6.12.3 补丁
$ patch -p1 < ../patch-2.6.13-rc5	# 应用 2.6.13-rc5 新补丁
$ cd ..
$ mv linux-2.6.12.3 linux-2.6.13-rc5	# 重命名内核源码目录

#-git内核

这些都是Linus的内核树的每日快照(daily snapshots)(由于在git仓库管理,因此得名)。

这些补丁通常每天发布一次,表示当前Linus内核树的当前状态。它们比-rc内核更加试验性,因为它们是自动生成的,如果它们是正常的,甚至都不会粗略的看一眼。

-git补丁不是增量的,可以用在 base 2.6.x内核上或者base 2.6.x-rc内核上——从它们的名字,你可以看出。命名为2.6.12-git1的补丁应用到2.6.12内核源码上,命名为2.6.13-rc3-git2应用到2.6.13-rc3内核上。

下面是一些实例,说明如何应用这些补丁:

# 从 2.6.12 升级到 2.6.12-git1
$ cd ~/linux-2.6.12				# 切换到内核源码目录
$ patch -p1 < ../patch-2.6.12-git1		# 应用 2.6.12-git1 补丁
$ cd ..
$ mv linux-2.6.12 linux-2.6.12-git1		# 重命名内核源码目录

# 从 2.6.12-git1 升级到 2.6.13-rc2-git3
$ cd ~/linux-2.6.12-git1			# 切换到内核源码目录
$ patch -p1 -R < ../patch-2.6.12-git1		# 撤销 2.6.12-git1 补丁
						# 现在有了一个 2.6.12 内核
$ patch -p1 < ../patch-2.6.13-rc2		# 应用 2.6.13-rc2 补丁
						# 内核现在是 2.6.13-rc2
$ patch -p1 < ../patch-2.6.13-rc2-git3		# 应用 2.6.13-rc2-git3 补丁
						# 内核现在是 2.6.13-rc2-git3
$ cd ..
$ mv linux-2.6.12-git1 linux-2.6.13-rc2-git3	# 重命名内核源码目录

#-mm内核 这是由Andrew Morton发布的试验版内核。 -mm树作为一种新功能和其它试验补丁的试验场。 一旦一个补丁在-mm中证明它的价值,Andrew会将它push到Linus以归入主干。

尽管它鼓励补丁通过-mm树流向Linus,但这也不是强制的。

子系统的维护者(或个人)有时直接将他们的补丁push给Linus,即便它们已经被合并,并且在–mm中测试过(有时甚至没有经过-mm先前的测试)。

你应该努力让你的补丁进入主干(mainline)通过-mm来确保最大可能的测试。

这个分支在不断变化,包含许多试验性的功能,很多调试的补丁并不适合mainline等,它是这个文档中最具实验性的分支。

这些内核都不适合在应该稳定运行的系统中使用,因为它们比其它分支有更多的风险(确保你进行了最新的备份)。

这些内核除了包含实验性补丁之外,它们通常还包含在发行时mainline -git内核的任何变化。

测试-mm内核是很赞的,因为-mm内核树的要点是在合并到更稳定的主干Linus树之前淘汰regressions, crashes, data corruption bugs, build breakage(以及任何一般bug)。

-mm内核不会按一个固定的时间表来发布,通常会在每-rc内核间发布几个版本(一般是1~3个)。
-mm内核适用于base 2.6.x内核(当没有-rc内核发布时),或者Linus的 -rc内核。

下面是一些应用-mm内核的实例:

# 从 2.6.12 升级到 2.6.12-mm1
$ cd ~/linux-2.6.12			# 切换到 2.6.12 源码目录
$ patch -p1 < ../2.6.12-mm1		# 应用 2.6.12-mm1 补丁
$ cd ..
$ mv linux-2.6.12 linux-2.6.12-mm1	# 重命名源码目录

# 从 2.6.12-mm1 升级到 2.6.13-rc3-mm3
$ cd ~/linux-2.6.12-mm1			# 切换到 2.6.12-mml 源码目录
$ patch -p1 -R < ../2.6.12-mm1		# 撤销 2.6.12-mm1 补丁
					# 现在有了 2.6.12 源码
$ patch -p1 < ../patch-2.6.13-rc3	# 应用 2.6.13-rc3 补丁
					# 现在有了 2.6.13-rc3 源码
$ patch -p1 < ../2.6.13-rc3-mm3		# 应用 2.6.13-rc3-mm3 补丁
$ cd ..
$ mv linux-2.6.12-mm1 linux-2.6.13-rc3-mm3	# 重命名源码目录

以上是对不同内核树的解释列表,我希望你现在明白了如何应用不同的补丁,并能能够帮助测试内核。

#致谢 Randy Dunlap, Rolf Eike Beer, Linus Torvalds, Bodo Eggert,Johannes Stezenbach, Grant Coady, Pavel Machek, etc.

#原文链接 https://www.kernel.org/doc/Documentation/app1ying-patches.txt

留下评论