|
关于打补丁的一些问题(译文)
关于打补丁的一些问题
coly译
类unix操作系统有一个很有趣的特性就是源代码级的补丁包。在windows上我们打补丁都是运行一个可执行的程序,然后就可以把补丁打完了,这对于最终用户是非常方便的,但是对我们这些求知欲比较强的Linux fans来说就有点不过瘾了,因为我们不知道里面是怎么做的。而Linux的补丁就有趣多了,我们首先获得程序的源代码和对应的补丁文件,然后给源代码打补丁,产生新的源代码文件。然后再编译这个新的源代码文件,就获得了打过补丁的新程序了。
如果你现在还不懂得如何打补丁,那没有关系,我们就一起来试试,因为在刚开始写这篇文章的时候,我也不会给自己的程序打补丁。
确切的说,这篇文章不是我写的,而是我根据Daniel P. Bovet, Marco Cesati, 和Cosimo Comella写的一篇英文文档翻译并修改得出来的。之所以这么说,我是不想落抄袭之嫌。
言归正传:
解释patch文件
我们可以使用diff命令加参数-ruN来比较两个文件并生成一个补丁文件。这个补丁文件会列出这两个不同版本文件的差异来。我们将通过一个特定例子来解释这个由diff命令生成的补丁文件(patch file)。
假定:我们对检查linux-2.2.13和linux-2.2.14这两个不同的版本的差别很感兴趣。
第一步,我们使用如下命令:
make distclean
这样可以在两个源代码目录中删去所有非文本文件。
然后我们继续第二步:
*****************************************************************************
diff -ruN linux-2.2.13 linux-2.2.14 > /tmp/patch-2.2.14
*****************************************************************************
COMMAND EXECUTION:
-r 是一个递归选项,设置了这个选项,diff会将两个不同版本源代码目录中的所有对应文件全部都进行一次比较,正如你所料,这种比较也是包括子目录中的文件的。
-N 选项表明如果一个文件存在于一个目录中,那么它就必须被认为是在这个目录中的,哪怕这个文件在对应的目录中是一个空文件。(举个例子,如果在老版本中有这么一个文件,但是在新版本中这个文件被去掉了,那么diff仍然会把它记录下来,我们打完补丁以后,在得到的新版本代码中,老版本的那个文件名仍然会存在,但是是一个空文件)
-u 选项指明正在使用的是统一的输出格式。
下面我们查看一下经过重定向后生成的补丁文件/tmp/patch-2.2.14,下面是我们从该文件中摘抄的一部分补丁信息:
*****************************************************************************
diff -ruN linux-2.2.13/arch/i386/kernel/signal.c
linux-2.2.14/arch/i386/kernel/signal.c
这里第一个版本的名字,linux-2.2.13是参考版本(就是旧版本),所有的在linux-2.2.14(新版本)中发现的问题都是和第一个版本相关的。
*****************************************************************************
DIFF HEADER:
diff命令会在补丁文件中记录这两个文件的首次创建时间,如下:
*****************************************************************************
*** linux-2.2.13/arch/i386/kernel/signal.c Tue Jun 8 01:14:20 1999
--- linux-2.2.14/arch/i386/kernel/signal.c Sun Jan 23 17:29:25 2000
*****************************************************************************
DIFF BODY:
diff 命令在这两个文件之间发现了3类差异。
a) 添加(addition):
这一行在旧版本的文件中没有,但是被添加到了新版本的文件中。
b) 置换(replacement):
在新版本文件中用连续的几行替换掉了旧版本文件中连续的几行。
c) 删除(deletion):
在旧版本文件中的一行在新版本文件中不再出现。
在每一种情况下,发生变化的行号都会被提示出来。
让我们解释一下diff用来指明这三种情况时使用的符号:
*****************************************************************************
添加(ADDITION):
看补丁文件中如下的行: (相对于arch/i386/kernel/signal.c文件的新旧两个版本)
*** 419,431 ****
--- 419,437 ----
? current->exec_domain->signal_invmap[sig]
: sig),
&frame->sig);
+ if (err)
+ goto give_sigsegv;
使用+号指明在“&frame->sig);”这一行后面要加两个新行。这两个新行就是用+符号开头的两行。
*** 419,431 ****向读者指明可以从旧文件的419行到431行来查阅这些变化;同样的,--- 419,437 ----向读者指明可以从新文件的419到437行来查阅这些变化。这样一来,新旧一比较,就可以知道哪些地方发生了什么变化。
不过在新版本的diff中,似乎并不是用这种方法来表明新旧文件对应的行号的,而使用@这个符号,对应于上例中的:
*** 419,431 ****
--- 419,437 ----
我们看到的新的标识可能是:
@@ -419,431 +419,437 @@
对于这种表示方法,我还不是很懂,如果有哪位朋友比较懂的话,非常欢迎你将这部分内容加进来。
不过,有一点需要说明一下,就是这个行号并不是完全必需的,其实这个行号在给源代码打补丁的时候是没有用的,这里提示出来主要是给开发人员比较分析时使用的。
置换(REPLACEMENT):
看补丁文件中如下的行: (相对于arch/i386/kernel/signal.c文件的新旧两个版本)
***************
*** 367,377 ****
printk("I/O APIC #%d Version %d at 0x%lX.\n",
m->mpc_apicid,m->mpc_apicver,
m->mpc_apicaddr);
! /*
! * we use the first one only currently
! */
! if (ioapics == 1)
! mp_ioapic_addr = m->mpc_apicaddr;
}
mpt+=sizeof(*m);
count+=sizeof(*m);
然后后面又紧跟着如下的行:
--- 368,376 ----
printk("I/O APIC #%d Version %d at 0x%lX.\n",
m->mpc_apicid,m->mpc_apicver,
m->mpc_apicaddr);
! mp_apics [mp_apic_entries] = *m;
! if (++mp_apic_entries > MAX_IO_APICS)
! --mp_apic_entries;
}
mpt+=sizeof(*m);
count+=sizeof(*m);
这里就指明了在旧版本文件中用!符号标识的5行被在新版本文件中用!标识的3行替换了。
由此可见,符号!就意味着替换。但是为什么会有替换,而不是先删除再添加,这里我就不清楚了。还是得请知道的朋友指点一下了。
删除(DELETION):
看补丁文件中如下的行: (相对于drivers/net/Config.in文件的新旧两个版本)
***************
*** 93,100 ****
fi
if [ "$CONFIG_EXPERIMENTAL" = "y" ]; then
tristate 'RealTek 8129/8139 (not 8019/8029!) support' CONFIG_RTL8139
- tristate 'SiS 900 PCI Fast Ethernet Adapter support' CONFIG_SIS900
- tristate 'Packet Engines Yellowfin Gigabit-NIC support' CONFIG_YELLOWFIN
fi
bool 'Other ISA cards' CONFIG_NET_ISA
if [ "$CONFIG_NET_ISA" = "y" ]; then
--- 94,99 ----
***************
在旧版本文件中用-符号标识的两行说明这两行在新版本的文件中不会再出现了,也就是说,在新版本的文件中,这两行被删除了。
建立一个自己的补丁
你现在修改并测试了一个新的Linux版本,就称它为Linux-2.4.5kh3,这个版本和你当前使用的称作Linux-2.4.5kh2的“老”版本有一些些不同。
现在你想制作一个可以将Linux-2.4.5kh2升级到Linux-2.4.5kh3的补丁程序。顺便多说句废话,理所当然的这个补丁程序要比Linux内核的源代码小的多。
这个补丁文件一般使用一张软盘就可以装下来,因此这对于升级另一台计算机上的旧的操作系统内核是非常有用的。
本质上制作补丁程序只有两个步骤,如下描述:
a) 在计算机A上产生一个补丁文件(计算机A就是那台既有新内核的源代码又有老内核的源代码的计算机)。并且将这个补丁文件复制到一张软盘上。
b) 在计算机B上读取保存有补丁文件的软盘,并利用补丁文件将计算机B上的旧内核升级为新的内核。
下面我们进行详细的说明。其中第1到第4步描述了怎样制作一个补丁文件,并把它复制到软盘上。第5到第6步描述了怎么样使用补丁文件将旧的操作系统内核升级到新的版本。
*****************************************************************************
第1步:清理两个内核的源代码文件(没有*.o的文件或者.*文件)
*****************************************************************************
我们假定这两个内核的源代码路径分别是:
/usr/src/linux-2.4.5kh2和/usr/src/linux-2.4.5kh3
运行如下命令:
cd /usr/src/linux-2.4.5kh2
make distclean
cd /usr/src/linux-2.4.5kh3
make distclean
*****************************************************************************
第2步:在两个内核源代码版本之间产生一个“context diffs”文件(这个文件指明了两个不同版本源代码之间的所有不同)。
*****************************************************************************
运行如下命令(首先是旧的内核,然后是新的内核):
cd /usr/src
diff -ruN linux-2.4.5kh2 linux-2.4.5kh3 > patch-2.4.5kh3
*****************************************************************************
第3步:检查补丁文件。
*****************************************************************************
运行如下命令查看补丁文件以确定它没有包含任何的垃圾:
less patch-2.4.5kh3
这里所说的垃圾就是非ASCI码的乱码,或控制字符。如果发现补丁文件中存在着不是文本的内容,那就是有垃圾了。这是我们需要重新操作第1至第3步
*****************************************************************************
第4步:将补丁文件复制到一张软盘上。
*****************************************************************************
mount /flp
cp /usr/src/patch-2.4.5kh3 /flp
umount /flp
由于我们的补丁文件一般都很小,所以我们不需要压缩它。现在我们拿着这张带有补丁程序的软盘转移到计算机B前面去。
*****************************************************************************
第5步:从软盘中读取补丁文件。
*****************************************************************************
cd /usr/src
mount /flp
cp /flp/patch-2.4.5kh3 patch-2.4.5kh3
umount /flp
*****************************************************************************
第6步:使用这个补丁文件将旧内核的源代码升级到新内核的版本。
*****************************************************************************
a) 执行奇妙的patch命令:
patch -p0 < patch-2.4.5kh3
patch命令作用在输入的补丁文件patch-2.4.5kh3上,并将对应的老版本的内核源代码中所有的文件和子目录升级到对应的新版本(当然这里的老版本一定要和我们在计算机A上运行diff命令时的那个老版本是一样的)。在我们的例子中,旧的内核源代码版本是Linux- 2.4.5kh2。这里参数–p0用来保证文件名不被改变(既不被修改,也不被删除)。
b) 重新命名内核源代码:
mv linux-2.4.5kh2 linux-2.4.5kh3
*****************************************************************************
撤消一个补丁
*****************************************************************************
如果你对新打的补丁不很满意,而你想回复到以前较早的内核版本去,那么我们从上面描述的第6步开始逆顺序操作即可(为什么要采取撤消补丁的方式来回到先前版本的理由很多,我们就不说了):
a) 将内核源代码的版本恢复到先前的名字:
mv linux-2.4.5kh3 linux-2.4.5kh2
b) 执行那条奇妙的patch命令:
patch -RE -p0 < patch-2.4.5kh3
这里,-E选项说明如果发现了空文件,那么就删除它;-R选项说明在补丁文件中的“新”文件和“旧”文件现在要调换过来了(实际上就是给新版本打补丁,让它变成老版本,但是这里是否一定要先更改源代码的目录名我还不明确,需要作实验)。
这里有一个疑问,就是为什么要进行代码目录的改名?
*****************************************************************************
避免make distclean操作
*****************************************************************************
根据文档/usr/src/linux/Documentation/SubmittingPatches所描述的,运行一个没有make distclean的递归diff,使用如下命令:
diff -ruN -X dontdiff linux-2.4.5kh2 linux-2.4.5kh3 > patch.diff
dontdiff是一个可执行文件,它可以从下面这个网址进行下载:
http://www.moses.uklinux.net/patches/dontdiff
但是,就我而言,我还是不知道为什么要这么做,或者说不进行make distclean的目的是什么。如果有那位朋友知道的话,非常感谢你告诉我一声,或者是把这部分内容补充到这份文档中。
制作一个官方的补丁文件
你已经安装了一个新的Linux,我们称它为Linux-2.4.2。但是差不多每个月,都会有新的Linux补丁推出,这些补丁或者修补了一些问题,或者支持了一些新的硬件或其它新的特性。所以,当你安装了Linux-2.4.2几个月以后,就会有新的稳定发行版推出,我们称它为Linux- 2.4.5。
我们将要描述的过程解释了你如何最简便的将自己的Linux从2.4.2升级为最新的2.4.5。
关键的窍门在于使用一系列的补丁文件。
*****************************************************************************
第0步:理解官方的Linux补丁文件是如何制作的。
*****************************************************************************
每一个官方的Linux补丁文件都是一个使用diff工具在两个连续的Linux版本中所有文件间产生的diff文件。
作为一个例子,我们假定这个补丁文件是用来将Linux-2.4.2升级到Linux-2.4.5的,这个补丁文件通过如下方法获得:
cd /usr/src/linux-2.4.2
make distclean
cd /usr/src/linux-2.4.3
make distclean
ln -s linux-2.4.2 linux
diff -ruN linux linux-2.4.3 > patch_2.4.3
gzip patch_2.4.3
这些补丁文件按照顺序使用gzip压缩,以节省Linux发行版本站点的下载时间。例如我们生成的补丁文件——patch_2.4.3.gz,就是一个经过压缩的补丁文件。
Linux官方补丁总是认为缺省的源代码子树名字为“Linux”。这种方法可能有点混淆,但是它允许一些(版本)连续的补丁不用修改源代码子树的名字就可以连续的被修改到源代码子树上。(下面我们可以看到)
这些补丁文件的名字都是标准的:
patch_2.4.3 记录着linux-2.4.3 和 linux-2.4.2之间的版本差异。
patch_2.4.4 记录着linux-2.4.4 and linux-2.4.3之间的版本差异。
patch_2.4.5 记录着linux-2.4.5 and linux-2.4.4之间的版本差异。
在我们的例子中,我们正好需要这3个补丁文件,他们的名字是patch_2.4.3.gz、patch_2.4.4.gz和patch_2.4.5.gz,用来将Linux-2.4.2升级到Linux-2.4.5。
*****************************************************************************
第1步:升级Linux源代码目录的名字和Linux符号连接。
*****************************************************************************
假定内核源代码的路径是:/usr/src/linux-2.4.2/
并且所有需要的压缩补丁文件都已经存放在目录/usr/src 中了。
执行下列命令:
cd /usr/src
mv linux-2.4.2 linux-2.4.5
rm linux
ln -s linux-2.4.5 linux
*****************************************************************************
第2步:清理源代码目录(删除中间目标文件和配置文件)。
*****************************************************************************
执行下列命令:
cd linux
make distclean
*****************************************************************************
第3步:解压缩补丁文件。
*****************************************************************************
in our example, execute:
cd /usr/src
gunzip patch-2.4.3.gz
gunzip patch-2.4.4.gz
gunzip patch-2.4.5.gz
*****************************************************************************
第4步:重复执行patch命令。
*****************************************************************************
执行下列命令:
for i in 3 4 5; do
patch -p0 < patch_2-4.$i
done
其中
patch -p0 < patchfile
命令作用在输入的补丁文件上,并将对应的老版本的内核源代码中所有的文件和子目录升级到对应的新版本(在我们的例子中,老版本的内核源代码目录是 /usr/src/linux)。在我们的例子中,旧的内核源代码版本是Linux-2.4.5kh2。这里参数–p0用来保证文件名不被改变(既不被修改,也不被删除)。
目标目录是Linux,就是说旧版本的内核源代码经过patch以后,就被升级成了新版本的内核源代码。 |
|