支持多个内核版本
我在欧洲核子研究中心 (CERN) 的 Atlas 实验中工作。Atlas 中的许多小组开始转向 Linux,将其作为下一代粒子物理实验的操作系统。在数据采集团队中,通常需要特定版本的 Linux 内核。通常,团队会使用特殊的 PC 卡,例如 ATM 卡,这些卡的驱动程序可能尚未适用于所有内核版本,或者必须首先对内核应用补丁。SMP 和单处理器机器都在使用,团队成员希望 SMP 和单处理器机器使用具有相同补丁的相同内核。他们还希望共享软件并有意义地比较结果。
本文介绍了我的团队如何支持这种需求。我假设您熟悉配置和编译内核的基本过程。
我们需要一个环境,可以在其中以连贯和一致的方式配置、构建、打包分发以及稍后安装一系列内核。最终的结果是我们的“内核仓库”,其中包含应用了不同补丁的多个内核版本的源代码 tar 文件、已编译内核的 tar 文件以及用于编译和安装内核的一组脚本。我们希望确保完整记录配置和构建步骤,并且即使构建环境的详细信息已更改,也可以在以后重现任何内核配置。此外,我们希望跟上较新的内核版本(当时,2.2.0 即将发布),因此我们尝试使添加新版本变得容易。我们还希望分发易于安装,以便人们可以在不了解详细信息的情况下使用它们。
我们的内核仓库及其所有相关工具都可以在 WWW 上访问,网址为 www.cern.ch/Atlas/project/kernels/www/kernels.html。内核以预编译二进制文件和源代码的单独分发包形式提供。随附一份技术说明,其中更详细地介绍了某些要点,并有助于形成本文的基础。
原始内核源代码是从 Web 下载的。每个内核都解压缩到一个子目录—/linux 中。由于用户可能希望同时拥有多个内核源代码树,因此我们将此目录重命名为 /linux-内核版本号.orig,并使用 tar 和 bzip2 重新打包该树。举一个清晰的例子,如果我下载了内核版本 2.2.0 的 tar 文件,我将使用以下命令重新打包它
cat linux-2.2.0.tar.bz2 | bzip2 -d | tar xf - mv linux linux-2.2.0.orig tar cf - linux-2.2.0.orig | bzip2 >linux-2.2.0.orig.tar.bz2
内核仓库包含脚本,这些脚本将为您获取和重新打包内核。
每当应用任何补丁时,都会重命名相应的源代码树以反映补丁,有时还会反映补丁的版本。例如,应用了“bigphysarea”补丁的 2.0.36 版本被打包为 linux-2.0.36.bphys.tar.bz2。
为了配置内核,我编写了一个名为 KernelConfig.exp 的 Expect 脚本。Expect 是一种用于自动化交互过程的工具(请参阅 Vinnie Saladino 在 1998 年 10 月的 Linux Journal 上发表的“使用 Expect 自动化任务”),它非常适合这项任务。KernelConfig.exp 在内核源代码树的顶部运行 make config 并为您回答问题。通过 Expect 脚本控制配置的优点在于,它对使用的内核版本不敏感。此脚本应该能够配置任何 Linux 内核版本。我已经在从 2.0.33 到 2.2.7 的所有稳定内核以及许多 2.1 系列内核上运行过它。虽然它可能无法为任何内核提供最佳配置(无论那可能意味着什么),但它确实提供了连贯且可重现的配置。
某些配置选项在脚本中是硬编码的,例如
支持 EXT2 和 Minix 文件系统,以便与大多数可用的救援磁盘兼容。
支持 RAM 磁盘和初始 RAM 磁盘。这至关重要,因为内核的大部分功能都使用模块,并且需要初始 RAM 磁盘支持才能在最广泛的硬件上启动。
为奔腾级处理器编译内核—我们没有 386 或 486 机器。
支持 NFS 和 root-on-NFS。最终,通过网络启动可能会很有用,在这种情况下,此选项是强制性的。
启用“实验性代码”选项。这可能看起来很危险,但需要它来包含 ATM 代码。
模块版本控制已禁用。虽然这意味着用户少了一层安全网,但如果我们必须移动模块,这确实使生活更轻松。
支持所有类别的网卡、SCSI 卡、声卡和其他类别的硬件已启用。各个卡的支持编译为模块。
某些硬件支持被明确排除,即使它可以作为模块使用,因为在我们使用的范围内的内核中,至少有一个无法编译。此类问题通常会被迅速发现和修复,但如果该软件对我们不重要,我将保持禁用状态以保持一致性。这种情况发生在一些声卡、红外设备(在 2.2.6 中已损坏)以及至少一个我们不使用的 ATM 卡上。随着生成更多内核,此列表可能会增长,因此您应该查看配置日志或脚本以查看其作用。对于所有其他选项,脚本将选择尽可能将代码编译为模块;否则,它将使用配置提供的默认值。配置的输出存储在当前目录的 KernelConfig.log 文件中。如果此文件已存在,则输出将附加到该文件,而不是覆盖现有信息。
有两点需要注意。脚本会对默认值做出反应,因此,如果默认值更改,它将做出不同的反应。对于某些代码,默认值会随着时间推移而更改。在 2.0.x 系列中仅作为内置或从内核中排除的代码,在 2.2.x 系列中可能作为模块提供。在这种情况下,根据默认值,它将被构建为 2.0.x 系列的内置或排除,但肯定会被构建为 2.2.x 系列的模块。如果您在启动时需要该功能,请注意。
其次,默认值取自 .config 文件(如果存在),或者取自 arch/i386/defconfig 文件(如果源代码从未配置过)。如果您在运行 KernelConfig.exp 之前手动配置内核并设置了一些选项,它将接受这些设置作为默认值。为了获得真正一致的结果,请在运行 KernelConfig.exp 之前运行 make mrproper。
SMP 支持很棘手。在 2.0.x 系列中,必须通过编辑 Makefile 或使用命令 make SMP=1 bzImage 或类似的命令构建内核来启用 SMP 支持。在 2.2.x 系列中,SMP 支持是一个配置时选项,Makefile 不再需要更改。KernelConfig.exp 启用或禁用 SMP 支持,具体取决于用户定义的环境变量 SMP_SUPPORT 的值。如果此变量未定义或为空,则脚本将不启用 SMP 支持。如果它为非空,则脚本将在 2.2.x 系列中启用 SMP 支持。
对于 2.0.x 系列,这还不够,在编译内核时,而不是在配置内核时,make 宏 SMP 的值必须为 true。我通过将 SMP_SUPPORT 定义为值 SMP=1 来解决这个问题。然后,我可以运行 KernelConfig 来配置内核,然后在之后运行 make $SMP_SUPPORT bzImage。对于 2.0.x 系列内核,SMP_SUPPORT 的 值 确保在编译时启用 SMP 构建内核。对于 2.2.x 系列,变量被定义的这一事实导致 KernelConfig.exp 在配置时启用 SMP 支持。这为 2.0.x 和 2.2.x 系列内核提供了对 SMP 的一致方法。
我使用一个名为 KernelBuild.sh 的 Bash 脚本来编译内核并生成二进制分发包。它接受一个参数,即内核源文件的名称(不带“.tar.bz2”扩展名—例如,./KernelBuild.sh linux-2.0.36.bphys)。它首先定义一些环境变量
MY_WORK 是我的工作目录。在这里,将解压缩内核源代码,并将构建分发包。KernelBuild.sh 希望在目录 $MY_WORK/bin 中找到它需要的所有脚本和工具。它还将确保存在正确的子目录结构以构建分发包。构建的内核安装在此处,而不是在根目录下,并且分发 tar 文件在此级别创建。用户可以在其客户端计算机的真实根目录中解压缩它。
MY_SRC 是包含准备好的内核源代码的目录。我将此设置为一个单独的变量,以便允许在另一个工作目录中自定义编译脚本,而无需将源代码与它们一起复制。例如,希望构建自己的内核版本的单独团队可以将 MY_SRC 设置为公共仓库中的源目录,并直接从该位置使用源代码。
MY_ROOT 是从中设置到正在编译的内核源代码的链接的根目录。换句话说,KernelBuild.sh 设置从 $MY_ROOT/linux 到指向 $MY_WORK/linux-2.0.36.bphys 的软链接—或它正在编译的任何版本。在正常的 Linux 环境中,MY_ROOT 将是 /usr/src,链接将从 /usr/src/linux 设置到实际的源代码树。通过允许它成为另一个目录,可以在另一个目录中以普通用户身份而不是以 root 身份编译内核。您以 root 身份将链接 /usr/src/linux 设置为指向 $MY_ROOT/linux 一次。然后,您可以根据编译内核的用户意愿,经常更改链接 $MY_ROOT/linux。
KERNEL_VERSION 只是输入参数。
如果您愿意,MY_* 环境变量可以在外部定义,并且不会被脚本覆盖。KERNEL_VERSION 将始终从输入参数设置。
KernelBuild.sh 实际上并不执行编译内核的工作。为此,它使用了另外两个脚本,Meanwhile.pl 和 KernelBuild.cmds。Meanwhile.pl 是一个 Perl 脚本,它将在后台执行 Bash 脚本,记录所有输出,并在完成后发送电子邮件消息。
真正的主力是 KernelBuild.cmds,它可以作为独立脚本执行,尽管通常您会使用 KernelBuild.sh。它解压缩源代码树,使用 KernelConfig.exp 配置它,编译内核的单处理器版本,将其打包到二进制分发文件中,将头文件打包到头文件分发包中,然后为 SMP 版本重复该过程。
KernelConfig.exp 确定如何配置内核,但 KernelBuild.cmds 确定如何构建和安装内核。两者之间的界限有点模糊,因为 SMP 支持从 2.0.x 系列更改为 2.2.x 系列的方式,如前所述。如果您希望自定义构建,则需要更改这两个脚本。
KernelBuild.cmds 利用了存储在内核源代码顶层名为 .name 的文件中的任何内容都将合并到内核名称中,并且可以使用命令 uname -v 或 cat /proc/version 稍后检索。我使用它来记录内核版本字符串,包括单处理器版本和 SMP 版本之间的区别。对于 2.2.x 系列,Makefile 包含此区别,但对于 2.0.x 系列则不包含。KernelBuild.cmds 使用一些 sed、障眼法来消除差异。
最后,二进制文件和头文件分发包存储在 /dist 目录中,使用 tar 打包,并使用 bzip2 压缩。二进制分发包包含内核映像、System.map 和所有模块。它还包含 KernelConfig.exp 的副本,因此,如果此脚本被更新,您仍然可以访问用于编译内核的任何特定分发包的确切版本。出于同样的原因,配置的日志文件也打包在分发包中。安装分发包后,这些文件将进入 /log/内核版本 目录。
可以使用 InstallKernel.pl 脚本安装内核。InstallKernel.pl 将内核分发包文件的完整名称作为输入,带有“.tar.bz2”扩展名。首先,它检查分发包是否会覆盖任何现有文件—如果是,它将中止执行,除非您明确告诉它继续执行。它安装内核及其模块,并为此内核向 /etc/lilo.conf 添加一个条目。它在执行此操作时非常小心。它创建 /etc/lilo.conf 的备份副本,然后逐行扫描它,直到找到 root= 条目。它使用此条目为新内核设置根目录。如果它找到稍后的 root= 条目,该条目指定了不同的根分区,它将警告您,但将继续使用它找到的第一个条目。如果它找到此内核映像的现有条目,它将不会添加条目。它做的最后一件事是向您显示保存的 lilo.conf 与它刚刚创建的 lilo.conf 之间的差异。InstallKernel.pl 不会为您运行 LILO—您必须自己执行此操作。
另一个脚本 InstallHeaders.pl 将负责为您安装头文件。头文件安装为 /usr/src/linux-headers 的子目录。如果您将链接 /usr/src/linux 设置为指向这些已安装的头文件集之一,则可以为与您实际运行的内核版本不同的内核版本编译您的驱动程序或程序。我利用这一点为我支持的所有内核编译 ARLA AFS 克隆,而无需重新启动我的机器。
无论您使用哪个 Linux 发行版,您可能都必须修改它决定使用哪组内核模块的方式。详细信息因发行版而异,因此无法在此处描述所有必要的更改。
由于这些内核在很大程度上依赖于模块的使用,因此您可能还需要为您的特定机器创建初始 RAM 磁盘。如果您有基于 SCSI 的系统,则肯定如此。有关详细信息,请参阅 mkinitrd 命令的 man 页面。
为了克隆仓库以构建您自己的内核,请复制 /bin 和 /source 目录的内容,并根据需要修改它们。KernelBuild.sh 将需要修改以正确设置 MY_* 变量。KernelConfig.exp 可能还需要修改以启用或禁用任何特定选项—这可能不是一项简单的任务。如果您希望实际更改内核的构建方式,则需要修改 KernelBuild.cmds。其他脚本永远不需要更改。
Tony Wildish 于 1989 年在伦敦帝国学院获得高能粒子物理学博士学位。他的职业生涯从在欧洲核子研究中心工作期间使用 Fortran 编程发展到 C 和 C++。四年前,他成为一名系统管理员,并在家工作时发现了 Linux 作为他工作的一种手段。目前,他在欧洲核子研究中心为一个实验工作,为计划于 2005 年投入使用的大型强子对撞机做准备。他喜欢希腊葡萄酒、希腊海滩和希腊美食,以及阅读,并且特别喜欢 Terry Pratchets 的 Discworld 系列。他的人生目标是去度假并在那里待下去。可以通过电子邮件 tony.wildish@cern.ch 联系 Tony。