Linux 内核的版本控制以及确保模块兼容性是开发和维护 Linux 系统时非常重要的一部分。
Linux 内核采用了语义版本控制(Semantic Versioning),通常由三个部分组成:
主版本号(major)、次版本号(minor)和补丁版本号(patch),形式为 X.Y.Z

1. 版本控制原则

  • 主版本号 (X): 当发生重大变化或不向后兼容的修改时,主版本号会增加。自 Linux 2.6 起,主版本号一直保持为 2,直到 3.0 才更新。现在最新的主版本号是 6。
  • 次版本号 (Y): 每次增加新功能但保持向后兼容时,次版本号增加。
  • 补丁版本号 (Z): 进行错误修复、性能优化等小型改动时,补丁版本号增加。

例如,Linux 5.10.12 表示:

  • 5:主版本号
  • 10:次版本号
  • 12:补丁版本号

2. 不同内核版本的变化

不同内核版本之间可能包括以下几类变化:

  • 新功能引入:添加新的内核功能或子系统,例如新的文件系统、网络协议支持、硬件支持等。
  • 性能优化:在不同版本中,内核可能会优化某些子系统的性能,例如调度器、内存管理、I/O 性能优化等。
  • API 和 ABI 改变:内核的应用程序编程接口 (API) 和应用程序二进制接口 (ABI) 可能在新版本中发生变化,特别是主版本号变更时。这会导致编写的模块可能在新版本上不兼容。

3. 确保内核模块的兼容性

确保模块在不同内核版本间的兼容性是开发内核模块时的核心工作,以下是一些常用的策略:

1. 使用内核版本号检查

内核头文件中提供了 LINUX_VERSION_CODEKERNEL_VERSION() 宏,这些宏可以帮助检查当前运行的内核版本号,从而在代码中做出针对不同版本的差异化处理:

#include <linux/version.h>

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5,10,0)
// 针对 Linux 5.10 或以上版本的代码
#else
// 针对更低版本的代码
#endif
2. 尽量使用稳定的 API

尽量使用内核提供的公共 API 和函数,而不是直接访问内核内部数据结构。Linux 内核开发者会尽量保持这些公共 API 的稳定性,这样模块在不同内核版本之间的兼容性更好。

3. 使用模块兼容层

有时不同内核版本之间 API 的变化会影响模块的编写,可以使用一种“兼容层”(compatibility layer)方法,将不同版本的 API 调用封装起来,根据内核版本动态调用对应的接口。

4. 为多个内核版本进行编译测试

如果你需要支持多个内核版本,最好针对不同版本的内核进行编译测试。这样可以确保模块在不同内核版本上能够正确编译并运行。

5. 使用 DKMS

DKMS (Dynamic Kernel Module Support) 是一种机制,能够帮助动态地构建和安装内核模块。使用 DKMS 可以在内核升级后自动重建模块,确保模块始终与当前内核兼容。
DKMS (Dynamic Kernel Module Support) 是一套用于自动构建和管理 Linux 内核模块的系统。它允许在内核升级时动态重建模块,使其能够在新内核中继续正常工作。这在内核更新频繁的情况下尤为有用,尤其是对于那些依赖特定模块的系统来说,例如显卡驱动、虚拟化工具等。

1. DKMS 的作用
  • 自动重建内核模块:当系统升级内核时,内核模块通常需要重新编译以适配新内核。DKMS 会在内核升级时自动重建这些模块,省去了手动重编译的麻烦。
  • 版本控制:DKMS 支持多个内核版本,允许用户在系统上保留多个模块版本,适配不同的内核。
  • 动态加载/卸载模块:DKMS 支持在不同内核版本间动态加载或卸载模块,确保模块与内核的兼容性。
2. DKMS 的工作原理

DKMS 的工作过程一般如下:

  1. 当你安装一个支持 DKMS 的内核模块时,系统会将模块的源代码放置在 /usr/src 目录下,并生成一个 DKMS 配置文件。
  2. 内核升级或模块更新时,DKMS 会自动重新编译并安装模块,以确保新内核中能加载该模块。
  3. 使用 dkms install 可以手动安装模块,使用 dkms remove 可以卸载模块。
3. DKMS 配置文件

每个支持 DKMS 的模块会在其源码目录中包含一个配置文件,通常命名为 dkms.conf。该文件定义了模块的相关信息,如模块名、版本号、编译步骤等。

下面是一个示例 dkms.conf 文件的内容:

PACKAGE_NAME="my_module"
PACKAGE_VERSION="1.0"
CLEAN="make clean"
MAKE="make"
BUILT_MODULE_NAME[0]="my_module"
DEST_MODULE_LOCATION[0]="/extra/my_module"
AUTOINSTALL="yes"
  • PACKAGE_NAMEPACKAGE_VERSION 定义了模块的名称和版本。
  • MAKECLEAN 定义了编译和清理模块的命令。
  • BUILT_MODULE_NAME 是生成的模块文件名。
  • DEST_MODULE_LOCATION 指定模块安装的位置。
  • AUTOINSTALL 如果设置为 yes,则内核更新时会自动安装该模块。
4. DKMS 的常用命令

使用 DKMS 时,可以通过以下一些命令来管理模块:

  1. 安装模块

    sudo dkms install my_module/1.0
    

    这会安装 my_module 版本 1.0,并将其编译和安装到当前内核。

  2. 删除模块

    sudo dkms remove my_module/1.0 --all
    

    删除所有内核中与 my_module 相关的模块。

  3. 列出已安装的模块

    dkms status
    

    这会列出所有已安装的 DKMS 模块及其版本信息。

  4. 构建模块

    sudo dkms build my_module/1.0
    

    仅构建模块而不安装。

  5. 检查模块兼容性

    sudo dkms autoinstall
    

    这会在内核升级后自动检查并重新安装所有 DKMS 管理的模块。

5. DKMS 的优势
  • 内核更新后的自动处理:通过 DKMS,模块可以在内核更新后自动重建和安装,避免了用户手动编译的麻烦。
  • 模块的跨内核兼容性:DKMS 能够管理多个不同内核版本下的模块,允许用户在系统上同时运行多个内核版本。
  • 社区支持:许多第三方驱动程序(如 Nvidia、VirtualBox 等)都通过 DKMS 提供支持,用户可以更加方便地管理这些模块。
6. 实际使用中的注意事项
  • 源码依赖:DKMS 依赖模块的源代码,因此需要确保系统中安装了必要的构建工具和头文件(如 linux-headers)。
  • 编译失败处理:如果在编译过程中出现错误,可以使用 dkms build 来手动重建模块并排查问题。
  • 内核模块的合法性:确保你安装的模块是与当前内核版本兼容的,并且源码的授权和使用符合相关法律法规。
6. 内核 ABI 兼容性检查

为了保持用户空间程序与内核的兼容性,内核提供了用户空间可用的 ABI(应用程序二进制接口)。在主版本号和次版本号变更时,ABI 可能会发生改变,因此需要确保用户空间应用依赖的内核 ABI 是稳定的。

4. 实践中的挑战

在实际开发中,内核 API 的变化和不兼容可能会引入许多挑战,特别是对于长期支持 (LTS) 内核和新发布的内核版本。因此,模块开发者应尽量关注内核的变更日志和内核邮件列表,以了解即将到来的变更并为其模块做好准备。

09-20 09:39