加载 LKM 时最常见且最令人沮丧的失败是一堆关于未解决符号的错误消息,就像这样
msdos.o: unresolved symbol fat_date_unix2dos msdos.o: unresolved symbol fat_add_cluster1 msdos.o: unresolved symbol fat_put_super ... |
出现这种情况的一个原因是您没有加载另一个包含您的 LKM 需要访问的指令或数据的 LKM。modprobe 的主要目的就是避免这种失败。请参阅第 5.3 节。
可加载内核模块的设计者意识到,将内核放在多个文件中可能会有问题,这些文件可能会彼此独立地分发。如果 LKMmydriver.o是为 Linux 1.2.1 基础内核编写和编译的,然后有人尝试将其加载到 Linux 1.2.2 内核中?如果在 1.2.1 和 1.2.2 之间,内核子程序的调用方式发生了变化,会怎么样?mydriver.o这些是内部内核子程序,有什么能阻止它们在不同版本之间发生变化呢?您最终可能会得到一个损坏的内核。
为了解决这个问题,LKM 的创建者赋予了它们内核版本号。特殊的.modinfo部分mydriver.o本例中的对象文件的“.modinfo”部分包含“1.2.1”,因为它是在使用 Linux 1.2.1 的头文件编译的。尝试将其加载到 1.2.2 内核中,insmod 会注意到不匹配并失败,并告诉您存在内核版本不匹配。
但是等等。Linux 1.2.1 和 1.2.2 之间真正存在不兼容性,并且会影响mydriver.o? mydriver.o仅调用几个子程序并访问几个数据结构的可能性有多大?它们肯定不会在每个小版本中都发生变化。我们是否必须针对我们要插入的特定内核的头文件重新编译每个 LKM?
为了减轻这种负担,insmod 有一个-f选项,可以“强制”insmod 忽略内核版本不匹配并仍然插入模块。因为在任何两个内核版本之间存在显着差异的情况非常罕见,所以我建议您始终使用-f-f 选项。但是,您仍然会收到关于不匹配的警告消息。没有办法关闭它。
但是 LKM 设计者仍然希望解决偶尔会发生的不兼容更改问题。因此,他们发明了一种非常巧妙的方法,使 LKM 插入过程能够感知 LKM 使用的每个内核子程序的实际内容。这被称为符号版本控制(有时不太清楚地称为“模块版本控制”)。它是可选的,您可以在通过“CONFIG_MODVERSIONS”内核配置选项配置内核时选择它。
当您使用符号版本控制构建基础内核或 LKM 时,为 LKM 使用而导出的各种符号被定义为宏。宏的定义是相同的符号名称加上由符号命名的子程序的参数和返回值类型的十六进制哈希值(基于程序 genksyms 对子程序源代码的分析)。所以让我们看看register_chrdev子程序。register_chrdev是基础内核中的一个子程序,设备驱动程序 LKM 经常调用它。使用符号版本控制,有一个类似以下的 C 宏定义
#define register_chrdev register_chrdev_Rc8dc8350 |
这个宏定义在定义register_chrdev的 C 源文件和任何引用register_chrdev的 C 源文件中都有效,所以当您的眼睛看到register_chrdev时,C 预处理器知道该函数实际上被调用为register_chrdev_Rc8dc8350.
那个乱码后缀是什么意思?它是register_chrdev的参数和返回值数据类型的哈希值。没有两个参数和返回值类型组合具有相同的哈希值。
所以假设有人向register_chrdev在 Linux 1.2.1 和 Linux 1.2.2 之间添加了一个参数。在 1.2.1 中,register_chrdev是register_chrdev_Rc8dc8350的宏,但在 1.2.2 中,它是register_chrdev_R12f8dc01的宏。在mydriver.o中,使用 Linux 1.2.1 头文件编译,存在对register_chrdev_Rc8dc8350的外部引用,但 1.2.2 基础内核没有导出这样的符号。相反,1.2.2 基础内核导出一个符号register_chrdev_R12f8dc01.
所以,如果您尝试将这个 1.2.1mydriver.oinsmod 到这个 1.2.2 基础内核中,您将会失败。并且错误消息不是关于内核版本不匹配,而只是“未解决的符号引用”。
尽管这很聪明,但有时实际上会适得其反。genksyms 的工作方式是,它经常为本质上相同的参数列表生成不同的哈希值。
而且符号版本控制甚至不能保证兼容性。它只捕获函数定义中一小部分可能使其不向后兼容的更改类型。如果register_chrdev解释其参数之一的方式以不向后兼容的方式更改,其版本后缀不会更改——参数仍然具有相同的 C 类型。
而且像-f这样的 insmod 选项也无法解决这个问题。
因此,通常不建议使用符号版本控制。
当然,如果您的基础内核是在使用符号版本控制编译的,那么您必须同样编译所有 LKM,反之亦然。否则,您肯定会收到那些“未解决的符号引用”错误。
现在我们已经了解了对于不同的基础内核,您通常会有不同版本的 LKM,问题就出现了,对于具有多个内核版本的系统(即您可以在启动时选择内核)该怎么办。您要确保为内核 A 构建的 LKM 在您启动内核 A 时插入,而为内核 B 构建的 LKM 在您启动内核 B 时插入。
特别是,每当您升级内核时,如果您足够明智,您都会将新内核和旧内核都保留在系统中,直到您确定新内核可以工作为止。
最常见的方法是使用 modprobe 的 LKM 搜索功能。modprobe 理解 第 5.6 节中描述的传统 LKM 文件组织,并根据正在运行的内核从相应的子目录加载 LKM。
您可以通过在构建内核时编辑主内核 Makefile,并在顶部设置 VERSION、PATCHLEVEL、SUBLEVEL 和 EXTRAVERSION 变量来设置 uname --release 值,该值是 modprobe 查找的子目录的名称。
除了上面提到的校验和之外,如果符号是在为对称多处理 (SMP) 机器构建的代码中定义或引用的,则符号版本前缀包含“smp”。这意味着它是为可能具有多个 CPU 的系统构建的。您可以通过 Linux 内核配置过程(make config 等)来选择是否构建 SMP 功能,即使用 CONFIG_SMP 配置选项。
因此,如果您使用符号版本控制,如果基础内核是在启用 SMP 功能的情况下构建的,而您要插入的 LKM 不是,或者反之亦然,您将收到未解决的符号。
如果您不使用符号版本控制,那就没关系。
请注意,通常没有理由从内核中省略 SMP 功能,即使您只有一个 CPU。仅仅因为存在该功能并不意味着您必须拥有多个 CPU。但是,在某些机器上,启用 SMP 功能的内核将无法启动,因为它得出结论,CPU 数量为零!
某些内核代码的版权所有者将其程序许可给公众以制作和使用副本,但只能以受限制的方式进行。例如,许可证可能会说您只能从同样获得公共许可的程序中调用您的程序副本。
(这是否令人困惑?这是一个例子:Bob 编写了一个 LKM,为其他 LKM 提供数据压缩子程序。他根据 GNU 通用公共许可证 (GPL) 将他的程序许可给公众。根据一些解释,该许可证规定,如果您制作 Bob 的 LKM 的副本,如果 Mary 也不向世界公开她的源代码,您就不能允许 Mary 的 LKM 调用其压缩子程序。目的是鼓励 Mary 公开她的源代码)。
为了支持和执行这样的许可证,许可人可以使其程序以特殊名称导出符号,该特殊名称是符号的真实名称加上前缀“GPLONLY”。客户端 LKM 的简单加载器将无法解析这些符号。例如:Bob 的 LKM 提供 bobsService() 服务,并声明它是一个 GPL 符号。因此,LKM 以名称 GPLONLY_bobsService 导出 bobsService()。如果 Mary 的 LKM 引用 bobsService,简单的加载器将无法找到它,因此将无法加载 Mary 的 LKM。
但是,现代版本的 insmod 知道在找不到 bobsService 时检查 GPLONLY_bobsService。但是,除非 Mary 的 LKM 声明它已根据 GPL 获得公共许可,否则现代 insmod 将拒绝这样做。
此举的目的似乎是为了防止任何人意外违反许可证(或可信地声称他意外违反了许可证)。如果您想规避该限制,这并不困难。
如果您看到此失败,可能是因为您正在使用不了解 GPLONLY 的旧加载器 (insmode)。
唯一其他原因可能是 LKM 作者编写源代码的方式使其永远无法加载到任何 Linux 内核中,因此作者分发它将毫无意义。