内核角落 - 探索动态内核模块支持 (DKMS)

作者:Gary Lerhaupt

源码是一件美好的事物。内核树中合并的模块源码更佳。最重要的是,对源码的支持才是真正重要的。在当今 Linux 在企业中的爆炸式增长中,能够拿起电话寻求帮助至关重要。企业比以往任何时候都更积极地推动 Linux 的开发和需求。通常,这会遇到社区的怀疑和些许焦虑,但如果做得正确,每个人都能看到和感受到好处。

动态内核模块支持 (DKMS) 框架应被视为这方面的一个主要例子。DKMS 是一个旨在帮助戴尔计算机公司以受控方式向其客户分发修复程序的系统,它也加速了整个社区的驱动程序开发、测试和验证。

DKMS 框架基本上是内核树之外的一个重复树,用于保存模块源码和编译后的模块二进制文件。这种重复允许模块与内核解耦,这对于 Linux 解决方案和部署提供商来说是一个强大的工具。其强大之处在于允许以有序且可支持的方式将驱动程序放入现有内核中。反过来,这使提供商及其客户都无需受限于内核更新来解决他们的问题。相反,当驱动程序修复程序发布后,DKMS 可作为一种临时措施来分发修复程序,直到代码可以合并回内核。

从客户的角度来看,DKMS 还提供了其他优势。从源码编译、安装或摆弄可重建的源 RPM 从来都不是一件容易的事。现实情况是,越来越多的 Linux 用户经验不足,需要更简单的解决方案。DKMS 通过创建一个可执行文件来弥合这些问题,该文件可以被调用来构建、安装或卸载模块。此外,通过使用其匹配功能,在新内核上配置模块变得非常容易,因为要安装的模块可以完全基于先前运行的某些内核的配置。在生产环境中,这是一个巨大的进步,因为 IT 经理不再需要在一些预定义的解决方案堆栈或较新内核的安全性增强之间做出选择。

DKMS 也为开发人员和资深 Linux 用户提供了很多帮助。前面提到的通过重复(不是完全分离)将模块与内核解耦的想法为驱动程序开发创建了一个可行的测试平台。无需将修复程序推送到连续的内核中,这些修复程序可以立即在大范围内分发和测试。测试速度的加快转化为总体开发速度的提高。通过消除内核发布作为广泛模块代码分发的阻塞机制,其结果是经过更好测试的代码,这些代码随后可以更快地推送回内核——这对开发人员和用户来说都是双赢的。

DKMS 还通过简化与内核相关软件相关的交付过程,使开发人员的生活更加轻松。例如,过去,戴尔交付模块的主要方法是包含内核特定预编译模块的 RPM。随着内核勘误表的出现,我们经常不得不经历为这些新内核重新编译二进制文件的单调且永无止境的过程——这是任何开发人员都不想遇到的情况。然而,戴尔仍然偏爱这种交付机制,因为它最大限度地减少了客户安装模块所需的工作和/或知识。借助 DKMS,我们可以满足这些可用性要求,并从开发角度显着减少我们的工作量。DKMS 仅要求模块源代码位于用户的系统上。DKMS 可执行文件负责为用户系统上的任何内核构建和安装模块,从而消除了内核追赶游戏。

Kernel Korner - Exploring Dynamic Kernel Module Support (DKMS)

图 1. DKMS 中的模块状态

使用 DKMS

在对 DKMS 进行了如此多的前期宣传之后,或许最好深入了解一下实际如何使用该软件的具体细节。首先,对模块使用 DKMS 要求模块源码位于用户的系统上,并且位于目录 /usr/src/(模块))-((模块版本))/ 中。此外,必须存在一个 dkms.conf 文件,该配置文件中包含格式正确的指令,以告知 DKMS 诸如在哪里安装模块以及如何构建模块之类的信息。有关 dkms.conf 文件格式的更多信息,请参见本文稍后部分。一旦满足这两个要求并且系统上安装了 DKMS,用户就可以通过将模块/模块版本添加到 DKMS 树来开始使用 DKMS。示例添加命令

dkms add -m qla2x00 -v v6.04.00

会将 qla2x00/v6.04.00 添加到现有的 /var/dkms 树中。此命令包括创建目录 /var/dkms/qla2x00/v6.04.00/,创建从 /var/dkms/qla2x00/v6.04.00/source 到 → /usr/src/qla2x00-v6.04.00/ 的符号链接,以及将 dkms.conf 文件从其原始位置复制到 /var/dkms/qla2x00/v6.04.00/dkms.conf。

一旦此添加完成,模块就可以构建了。dkms build 命令要求正确的内核源码位于系统上,来自 /lib/module/内核版本/build 符号链接。用于编译模块的 make 命令在 dkms.conf 配置文件中指定。继续 qla2x00/v6.04.00 示例

dkms build -m qla2x00 -v v6.04.00 -k 2.4.20-8smp

编译模块,但不会安装它。虽然 build 命令需要一个内核版本参数,但如果省略此内核名称,则假定为当前运行的内核。但是,为当前未运行的内核构建模块也是一个可行的选择。此功能通过在执行任何模块构建之前运行的内核准备子例程来保证。这种严谨的内核准备涉及运行make mrproper,将正确的内核 .config 文件复制到内核源码目录,运行make oldconfig,最后,运行make dep。这些步骤确保正在构建的模块是针对正确的内核符号构建的。默认情况下,DKMS 在 /lib/modules/内核版本/build/configs/ 目录中查找内核 .config 文件,利用 Red Hat 的命名结构来命名这些配置文件。如果内核 .config 文件未位于此目录中,则必须在 build 命令中指定 --config 选项,并告知 DKMS 可以找到 .config 文件的位置。

成功完成构建会为此示例创建 /var/dkms/qla2x00/v6.04.00/2.4.20-8smp/ 目录,以及此目录中的 log 和 module 子目录。log 目录保存模块 make 的日志文件,module 目录保存编译后的 .o 二进制文件的副本。

构建完成后,现在可以将模块安装到为其构建的内核上。安装会将编译后的模块二进制文件复制到 /lib/modules/ 树中的正确位置,如 dkms.conf 文件中所指定。如果已在该位置找到具有该名称的模块,则 DKMS 会将其保存在其树中作为原始模块,以便在卸载较新模块后可以稍后将其放回原位。示例安装命令

dkms install -m qla2x00 -v v6.04.00 -k 2.4.20-8smp

创建符号链接 /var/dkms/qla2x00/v6.04.00/kernel-2.4.20-8smp → /var/dkms/qla2x00/v6.04.00/2.4.20-8smp。此符号链接是 DKMS 如何跟踪哪个驱动程序版本安装在哪个内核上的方式。如前所述,如果已经安装了同名的模块,DKMS 会将其副本保存在其树的 /var/dkms/模块名称/original_module/ 目录中。在这种情况下,它将被保存到 /var/dkms/qla2x00/original_module/2.4.20-8smp/。

为了完成 DKMS 周期,您还可以从树中卸载或删除您的模块。卸载会删除您安装的模块,并在适用情况下,将其替换为原始模块。在 DKMS 树中存在模块的多个版本的情况下,当卸载一个版本时,DKMS 不会尝试理解或假设应将这些其他版本中的哪个版本放在其位置。相反,如果从原始 DKMS 安装中保存了真正的 original_module,则会将其放回内核中。该模块的所有其他模块版本都保持在已构建状态。一个卸载示例是

dkms uninstall -m qla2x00 -v v6.04.00 -k 2.4.20-8smp

如果未设置内核版本参数,则假定为当前运行的内核,但 remove 命令不会发生相同的行为。Remove 和 uninstall 类似,因为 remove 命令完成与 uninstall 相同的所有步骤。但是,如果要删除的模块版本是系统上所有内核的该模块版本的最后一个实例,则在 remove 的卸载部分完成后,remove 会从 DKMS 树中物理删除该模块的所有痕迹。换句话说,当 uninstall 命令完成时,您的模块将保持“已构建”状态。但是,当 remove 完成时,您必须从 add 命令重新开始,才能再次将此模块与 DKMS 一起使用。以下是两个示例 remove 命令

dkms remove -m qla2x00 -v v6.04.00 -k 2.4.20-8smp
dkms remove -m qla2x00 -v v6.04.00 --all

使用第一个 remove 命令,将卸载模块。如果此模块/模块版本未安装在任何其他内核上,则会从 DKMS 树中删除其所有痕迹。例如,如果 qla2x00/v6.04.00 也安装在 2.4.20-8bigmem 内核上,则第一个 remove 命令会将其保留原样——它将保持在 DKMS 树中完好无损。第二个示例则不是这种情况。它将从所有内核卸载 qla2x00/v6.04.00 模块的所有版本,然后从 DKMS 树中完全删除 qla2x00/v6.04.00 的所有引用。因此,remove 是清理您的 DKMS 树的方法。

其他 DKMS 命令

DKMS 还带有一个功能齐全的 status 命令,该命令返回有关当前位于您的树中的信息。如果未设置任何参数,它将返回找到的所有信息。从逻辑上讲,返回的信息的特异性取决于传递给 status 命令的参数。返回的每个状态条目都是 added、built 或 installed 状态。如果已保存原始模块,也会显示此信息。一些示例 status 命令包括

dkms status
dkms status -m qla2x00
dkms status -m qla2x00 -v v6.04.00
dkms status -k 2.4.20-8smp
dkms status -m qla2x00 -v v6.04.00 -k 2.4.20-8smp

DKMS 的另一个主要功能是 match 命令。match 命令获取一个内核的 DKMS 安装模块的配置,并将其应用于另一个内核。当 match 完成时,为一个内核安装的相同模块/模块版本随后会安装在另一个内核上。当您从一个内核升级到下一个内核,但又想为新内核保留相同的 DKMS 模块时,这很有帮助。在示例中

dkms match --templatekernel 2.4.20-8smp
↪-k 2.4.20-9smp

--templatekernel 是从中获取配置的匹配器内核。-k kernel 是配置被应用到的被匹配内核。

出于系统管理目的,DKMS 还添加了命令 mktarball 和 ldtarball。这些命令允许用户分别在 DKMS 树中创建和加载 tarball 存档,以方便在存在许多类似系统的部署中使用 DKMS。这允许系统管理员仅在一个系统上构建模块。无需在每个其他系统上构建相同的模块,可以将构建的二进制文件直接应用于其他系统的 DKMS 树。具体来说,mktarball 为给定的模块/模块版本创建源 tarball。然后,它存档为该模块/模块版本构建了模块的每个内核版本的 DKMS 树。考虑以下示例

dkms mktarball -m qla2x00 -v v6.04.00
↪-k 2.4.20-8smp,2.4.20-8

根据 -k kernel 参数,mktarball 仅存档为指定的内核编译的某些二进制文件。如果未给出内核参数,则它会存档该模块/模块版本的所有已构建模块二进制文件。

使用 ldtarball,DKMS 只是解析使用 mktarball 创建的存档,并将找到的所有内容应用于该系统的 DKMS 树。这使所有模块都处于已构建状态;然后可以使用 dkms install 命令将模块二进制文件放入 /lib/modules 树中。在正常操作下,ldtarball 不会覆盖系统 DKMS 树中已存在的任何文件。但是,可以使用 --force 选项强制存档覆盖树中的内容。一个 ldtarball 示例

dkms ldtarball --config
↪qla2x00-v6.04.00-kernel2.4.20-8smp.tar.gz

最后一个其他 DKMS 命令是 mkdriverdisk。顾名思义,mkdriverdisk 获取 DKMS 树中的正确源,并创建一个驱动程序磁盘映像,该映像可以为 Linux 发行版安装提供更新的驱动程序。一个 mkdriverdisk 示例可能如下所示

dkms mkdriverdisk -d redhat -m qla2x00
↪-v v6.04.00 -k 2.4.20-8BOOT

目前,唯一受支持的发行版驱动程序磁盘格式是 Red Hat,但这很容易通过社区的帮助来扩展,以了解每个发行版的驱动程序磁盘要求和格式。有关 DKMS 创建 Red Hat 驱动程序磁盘所需的额外必要文件及其格式的更多信息,请参阅 people.redhat.com/dledford。这些文件应放在您的模块源码目录中。

dkms.conf 配置文件格式

对于 DKMS 软件包的维护者来说,dkms.conf 配置文件是将您的源 tarball 准备好用于 DKMS 的唯一辅助部分。conf 文件的格式是 DKMS 在处理您的软件包时获取的一系列 shell 变量。例如,来自 qla2x00/v6.04.00 dkms.conf 文件的摘录

MAKE="make all
↪INCLUDEDIR=/lib/modules/$kernelver/build/include"
MAKE_smp="make SMP=1 all
↪INCLUDEDIR=/lib/modules/$kernelver/build/include"
LOCATION="/kernel/drivers/addon/qla2200"
REMAKE_INITRD="yes"
MODULE_NAME="qla2200.o:qla2200_6x.o
↪qla2300.o:qla2300_6x.o"
CLEAN="make clean"
MODULES_CONF_ALIAS_TYPE="scsi_hostadapter"
MODULES_CONF0="options scsi_mod
↪scsi_allow_ghost_devices=1"

表明每个 shell 变量指令都应以全部大写字母编码。当前此规则的一个例外是 MAKE_ 指令。DKMS 使用通用的 MAKE= 命令来构建您的模块。但是,如果存在 MAKE_内核正则表达式文本 命令,并且 MAKE_ 之后的文本与正在为其构建模块的内核(作为子字符串)匹配,则使用此备用 make 命令。在上面的示例中,您可以看到 DKMS 如何在为其构建此模块的任何 smp 内核上使用 MAKE_smp 指令。也存在类似的 PATCH_ 命令。当下划线后的文本与正在为其构建模块的内核匹配时,首先将该补丁应用于模块源码。这允许开发人员分发一个源 tarball,其中包含一个 dkms.conf 和多个补丁。然而,可以根据需要将不同的补丁应用于源码,以确保所有模块在所有内核上都能正常运行。

另请注意,dkms.conf 接受 $kernelver 变量,该变量在构建时会被替换为正在为其构建模块的内核版本。这尤其重要,以便在为当前未运行的内核编译模块时引用正确的 include 目录。

结合 RPM 使用 DKMS

DKMS 和 RPM 实际上可以很好地协同工作。唯一的技巧是,为了使其正常运行,您必须创建一个安装源的 RPM。虽然通常的做法是仅使用源 RPM 安装源,但源 RPM 不一定与 DKMS 兼容;除了安装源之外,它不会让您做太多事情。相反,您的源 tarball 需要包含在您的 RPM 中,以便您的源可以放置在 /usr/src/模块-模块版本/ 中,并且可以调用正确的 DMKS 命令。%post 和 %preun 基本上是 DKMS 命令。这是一个示例 .spec 文件

%define module qla2x00

Summary: Qlogic HBA module
Name: %module_dkms
Version: v6.04.00
Release: 1
Vendor: Qlogic Corporation
Copyright: GPL
Packager: Gary Lerhaupt <gary_lerhaupt@dell.com>
Group: System Environment/Base
BuildArch: noarch
Requires: dkms gcc bash sed
Source0: qla2x00src-%version.tgz
Source1: dkms.conf
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root/

%description
This package contains Qlogic's qla2x00 HBA module meant
for the DKMS framework.

%prep
rm -rf qla2x00src-%version
mkdir qla2x00src-%version
cd qla2x00src-%version
tar xvzf $RPM_SOURCE_DIR/qla2x00src-%version.tgz

%install
if [ "$RPM_BUILD_ROOT" != "/" ]; then
	rm -rf $RPM_BUILD_ROOT
fi
mkdir -p $RPM_BUILD_ROOT/usr/src/%module-%version/
install -m 644 $RPM_SOURCE_DIR/dkms.conf
$RPM_BUILD_ROOT/usr/src/%module-%version
install -m 644 qla2x00src-%version/*
$RPM_BUILD_ROOT/usr/src/%module-%version

%clean
if [ "$RPM_BUILD_ROOT" != "/" ]; then
        rm -rf $RPM_BUILD_ROOT
fi

%files
%defattr(0644,root,root)
%attr(0755,root,root) /usr/src/%module-%version/

%pre

%post
/sbin/dkms add -m %module -v %version
/sbin/dkms build -m %module -v %version
/sbin/dkms install -m %module -v %version
exit 0

%preun
/sbin/dkms remove -m %module -v %version --all
exit 0


后续步骤

由于 DKMS 是一个最近提出的框架,因此可以根据社区的决定添加、删除或重新编码许多内容。有关 DKMS 的最新项目信息,请访问 www.freshmeat.net/projects/dkms。您可以通过加入 DKMS-devel 邮件列表提出问题并提供反馈,网址为 lists.us.dell.com/mailman/listinfo

在 2002 年 12 月号的 Linux Journal 中,Linus Torvalds 曾被引述说:“基本上,所有商业人士都有自己的议程,这非常健康,因为您希望拥有这些经常冲突的议程,以将系统推向真正对每个人都有效的事物。” 作为 Linux 产品的商业经销商,戴尔有兴趣找到一个解决正在进行的模块/内核问题的良好方案,既要支持社区,又要为他们的客户创造更好的 Linux 体验。DKMS 的设计就考虑到了这一点。

Gary Lerhaupt (gary_lerhaupt@dell.com) 是戴尔 Linux 开发团队的软件工程师。他还参与了在 Red Hat Linux 上部署的戴尔 Oracle9i Real Application Clusters (RAC) 计划的协作。Gary 拥有俄亥俄州立大学计算机科学与工程学士学位。

加载 Disqus 评论