5. 如何插入和移除 LKMs

用于插入和移除 LKMs 的基本程序是 insmodrmmod。 详细信息请参阅它们的手册页。

从概念上讲,插入 LKM 很简单:只需以超级用户身份键入如下命令:

insmod serial.o
(serial.o包含用于串行端口 (UART) 的设备驱动程序。

但是,如果我说这个命令总是有效,那我就是在误导你。 命令经常会失败,这非常普遍且令人恼火,失败信息通常是关于模块/内核版本不匹配或大量未解析的符号。

但是,如果它确实有效,那么为了向自己证明你了解自己在做什么,可以查看/proc/modules第 5.5 节 中所述。

请注意,本节中的示例来自 Linux 2.4。 在 Linux 2.6 中,加载 LKM 的技术方面有很大不同,最明显的表现是 LKM 文件的后缀是“.ko”而不是“.o”。 然而,从用户的角度来看,它看起来非常相似。

现在让我们看一个更困难的插入示例。 如果你尝试

insmod msdos.o
你可能会收到大量如下错误消息:
  msdos.o: unresolved symbol fat_date_unix2dos
  msdos.o: unresolved symbol fat_add_cluster1
  msdos.o: unresolved symbol fat_put_super
  ...

这是因为 msdos.o 包含对提到符号的外部符号引用,而内核没有导出这些符号。 为了证明这一点,执行

cat /proc/ksyms
以列出内核导出的每个符号(即,可用于绑定到 LKM)。 你会看到 'fat_date_unix2dos' 不在列表中。

(在 Linux 2.6 中,没有/proc/ksyms。 使用/proc/kallsyms代替; 格式类似于 nm 的输出:查找标记为 “t” 的符号)。

如何将其添加到列表中? 通过加载另一个 LKM,该 LKM 定义并导出这些符号。 在这种情况下,它是文件中的 LKMfat.o。 所以执行

  insmod fat.o
然后查看 “fat_date_unix2dos” 是否在/proc/ksyms。 现在重做
insmod msdos.o
它就可以工作了。 查看/proc/modules并查看两个 LKM 都已加载,并且一个依赖于另一个
msdos                   5632   0 (unused)
fat                    30400   0 [msdos]

我怎么知道fat.o是我缺少的模块? 只需一点点创造力。 解决此问题的更可靠方法是使用 depmodmodprobe 而不是 insmod,如下所述。

当你的符号看起来像 “fat_date_unix2dos_R83fb36a1” 时,问题可能比仅仅加载先决条件 LKM 更复杂。 请参阅 第 6 节

当错误消息为 “kernel/module version mismatch” 时,请参阅 第 6 节

通常,在插入 LKM 时,你需要向其传递参数。 例如,设备驱动程序想知道它要驱动的设备的地址和 IRQ。 或者网络驱动程序想知道你希望它执行多少诊断跟踪。 这是一个例子:

insmod ne.o io=0x300 irq=11

在这里,我正在加载我的类 NE2000 以太网适配器的设备驱动程序,并告诉它在 IO 地址 0x300 驱动以太网适配器,该地址在 IRQ 11 上生成中断。

LKMs 没有标准参数,约定也很少。 每个 LKM 作者决定 insmod 将为其 LKM 接受哪些参数。 因此,你将在 LKM 的文档中找到它们的文档。 本 HOWTO 还在 第 15 节 中汇编了大量 LKM 参数信息。 有关 LKM 参数的常规信息,请参阅 第 8 节

要从内核中移除 LKM,命令如下:

rmmod ne

有一个命令 lsmod 可以列出当前加载的 LKM,但它所做的只是转储/proc/modules的内容,带有列标题,因此你可能只想直接查看源文件而忘记 lsmod

5.1. 找不到内核版本...

一个常见的错误是尝试插入一个不是 LKM 的目标文件。 例如,你配置你的内核,使 USB 核心模块绑定到基本内核中,而不是作为 LKM 生成。 在这种情况下,你最终会得到一个文件usbcore.o,它看起来与usbcore.o当你将其构建为 LKM 时得到的文件非常相似。 但是你无法 insmod 该文件。

那么你会收到错误消息,告诉你应该配置内核以使 USB 核心功能成为 LKM 吗? 当然不会。 这是 Unix,解释性的错误消息被视为软弱的标志。 错误消息是

$ insmod usbcore.o
usbcore.o: couldn't find the kernel version this module was compiled for

insmod 告诉你的是,它在usbcore.o中查找任何合法的 LKM 都应该有的信息——LKM 旨在使用的内核版本——但它没有找到。 我们现在知道它没有找到的原因是该文件不是 LKM。 请参阅 第 10.2 节,了解如何查看 insmod 正在查看的内容,并确认该文件实际上不是 LKM。

如果这是一个你自己创建的模块,并且打算将其作为 LKM,那么你接下来的问题是:为什么它不是 LKM? 最常见的原因是你没有包含linux/module.h在你的源代码的顶部,和/或没有定义MODULE>宏。MODULE旨在通过编译命令设置 (-DMODULE) 并确定编译是生成 LKM 还是用于绑定到基本内核的目标文件。 如果你的模块像大多数现代模块一样,只能作为 LKM 构建,那么你应该在你的源代码中定义它 (#define MODULE) 在你包含之前include/module.h.

5.2. LKM 加载时会发生什么

所以你已经成功加载了一个 LKM,并通过/proc/modules验证了这一点。 但是你如何知道它是否正在工作? 这取决于 LKM,并且因 LKM 的类型而异,但以下是 LKM 加载时的一些更常见的操作。

设备驱动程序 LKM 加载后(如果模块绑定到基本内核,则模块在启动时也会执行此操作)做的第一件事通常是在系统中搜索它知道如何驱动的设备。 它如何执行此搜索因驱动程序而异,并且通常可以通过模块参数来控制。 但在任何情况下,如果驱动程序找不到任何它可以驱动的设备,它会导致加载失败。 否则,驱动程序将其自身注册为特定主设备号的驱动程序,并且你可以开始通过指定该主设备号的设备特殊文件来使用它找到的设备。 它还可以将自身注册为设备使用的中断级别的处理程序。 它还可以向设备发送设置命令,因此你可能会看到指示灯闪烁或类似情况。

你可以在文件中看到设备驱动程序是否已注册自身/proc/devices。 你可以在/proc/interrupts.

中看到设备驱动程序是否正在处理设备的中断。 一个好的设备驱动程序会发出内核消息,告知它找到并准备驱动哪些设备。(在大多数系统中,内核消息最终会出现在控制台和文件中/var/log/messages。 你还可以使用 dmesg 程序显示最近的消息)。 然而,有些驱动程序是静默的。 一个好的设备驱动程序还会在(内核消息中)在你未能找到设备时提供一些搜索的详细信息,但许多驱动程序只是在没有解释的情况下加载失败,而你得到的是 insmod 对问题可能是什么的猜测列表。

网络设备(接口)驱动程序的工作方式类似,只是 LKM 注册了它选择的设备名称(例如eth0)而不是主设备号。 你可以在/proc/net/dev

文件系统驱动程序在加载时,将其自身注册为某种名称的文件系统类型的驱动程序。 例如,msdos驱动程序将其自身注册为名为msdos的文件系统类型的驱动程序。(LKM 作者通常将 LKM 命名为与其将驱动的文件系统类型相同的名称)。

5.3. 智能加载 LKM - Modprobe

一旦你使用 insmodrmmod 弄清楚了模块的加载和卸载,你就可以通过使用更高级别的程序 modprobe 让系统为你做更多的工作。 有关详细信息,请参阅 modprobe 手册页。

modprobe 主要做的事情是自动加载你请求的 LKM 的先决条件。 它借助你使用 depmod 创建并保存在系统上的文件来完成此操作。

示例

modprobe msdos

这将执行 insmodmsdos.o,但在那之前执行 insmodfat.o,因为你必须先加载fat.o才能加载msdos.o.

modprobe 为你做的另一件主要事情是找到包含 LKM 的目标模块,只需给出 LKM 的名称即可。 例如,modprobe msdos 可能会加载/lib/2.4.2-2/fs/msdos.o。 实际上,modprobe 的参数可能是一个完全符号化的名称,你将其与某个实际模块关联。 例如,modprobe eth0 加载适当的网络设备驱动程序以创建和驱动你的eth0设备,假设你在modules.conf中正确设置了它。 查看 modprobe 的手册页和配置文件modules.conf(通常/etc/modules.conf),了解 modprobe 使用的搜索规则的详细信息。

modprobe 特别重要,因为默认情况下,它是内核模块加载器用于按需加载 LKM 的程序。 因此,如果你使用自动模块加载,你将需要设置modules.conf正确设置,否则事情将无法正常工作。 请参阅 第 5.4 节

depmod 扫描你的 LKM 目标文件(通常是所有.o文件在适当的/lib/modules子目录中),并找出哪些 LKM 预先需要(引用符号在)其他 LKM 中。 它生成一个依赖关系文件(通常命名为modules.dep),你通常将其保存在/lib/modules中,供 modprobe 使用。

你也可以使用 modprobe 来移除 LKM 堆栈。

通过 LKM 配置文件(通常是/etc/modules.conf),你可以微调依赖关系并执行其他高级操作来控制 LKM 选择。 你可以指定在插入和移除 LKM 时运行的程序,例如初始化设备驱动程序。

如果你正在维护一个系统且内存不短缺,那么可能更容易避免使用 modprobe 及其需要的各种文件和目录,而只需在启动脚本中执行原始 insmod

5.4. 自动 LKM 加载和卸载

5.4.1. 自动加载

你可以使 LKM 在内核首次需要它时自动加载。 你可以使用内核模块加载器(它是 Linux 内核的一部分)或其旧版本kerneld守护程序来完成此操作。

例如,假设你运行一个程序,该程序对 MS-DOS 文件系统中的文件执行 open 系统调用。 但是你没有 MS-DOS 文件系统的文件系统驱动程序绑定到你的基本内核中或作为 LKM 加载。 因此,内核不知道如何访问你在磁盘上打开的文件。

内核识别到它没有 MS-DOS 的文件系统驱动程序,但是两种自动模块加载工具之一可用,并使用它来导致 LKM 被加载。 然后内核继续执行 open 操作。

在大多数现代系统中,自动内核模块加载实际上不值得增加复杂性。 它在内存非常小的系统中可能很有意义,因为你可以仅在需要时才将内核的某些部分保留在内存中。 但是,这些模块使用的内存量在今天非常便宜,因此你通常最好通过启动脚本加载所有你可能需要的模块,并保持它们加载状态。

Red Hat Linux 通过内核模块加载器使用自动模块加载。

内核模块加载器和kerneld都使用 modprobe,即 insmod,来插入 LKM。 请参阅 第 5.3 节

5.4.1.1. 内核模块加载器

在文件中有一些关于内核模块加载器的文档Documentation/kmod.txt在 Linux 2.4 源代码树中。 本节比该文件更完整和准确。 你还可以在kernel/kmod.c.

内核模块加载器是 Linux 内核的可选部分。 如果你在构建时配置内核时选择 CONFIG_KMOD 功能,你就会得到它。

当具有内核模块加载器的内核需要 LKM 时,它会创建一个用户进程(由超级用户拥有,尽管如此),该进程执行 modprobe 以加载 LKM,然后退出。 默认情况下,它将 modprobe 查找为/sbin/modprobe,但是你可以通过将其文件名写入/proc/sys/kernel/modprobe来将你喜欢的任何程序设置为 modprobe。 例如

# echo "sbin/mymodprobe" >/proc/sys/kernel/modprobe

内核模块加载器将以下参数传递给 modprobe:参数零是 modprobe 的完整文件名。 常规参数是-s, -k,以及内核想要的 LKM 的名称。-s是用户不友好的形式的--syslog; -k是表示--autoclean的隐晦方式。 即,来自 modprobe 的消息将发送到 syslog,并且加载的 LKM 将设置 “autoclean” 标志。

modprobe 调用中最重要的部分当然是模块名称。 请注意,modprobe 的 “模块名称” 参数不一定是真实的模块名称。 它通常是表示模块所扮演角色的符号名称,你使用alias语句在modules.conf中来告知要加载哪个 LKM。 例如,如果你的以太网适配器需要3c59xLKM,你可能需要以下行

alias eth0 3c59x
/etc/modules.conf中。 以下是内核模块加载器在一些更常见的情况下使用的模块名称(内核调用内核模块加载器加载模块大约有 20 种情况)

  • 当你尝试访问设备且没有设备驱动程序注册来服务于该设备的主设备号时,内核会请求模块,名称为block-major-Nchar-major-N,其中N是十进制的主设备号,没有前导零。

  • 当你尝试访问网络接口(可能是通过针对它运行 ifconfig)并且没有网络设备驱动程序注册来服务于该名称的接口时,内核会请求与接口名称相同的模块(例如eth0)。 这适用于非物理接口的驱动程序,例如ppp0也是如此。

  • 当你尝试访问协议族中的套接字,但没有协议驱动程序注册来驱动时,内核会请求名为net-pf-N的模块,其中N是协议族号(十进制,没有前导零)。

  • 当你尝试 NFS 导出目录或通过 NFS 系统调用访问 NFS 服务器时,内核会请求名为nfsd.

  • ATA 设备驱动程序(名为ide)通过名称加载 ATA 设备类的相关驱动程序ide-disk, ide-cd, ide-floppy, ide-tape,以及ide-scsi.

内核模块加载器使用以下环境变量(仅限)运行 modprobe:HOME=/; TERM=linux; PATH=/sbin:/usr/sbin:/bin:/usr/bin.

内核模块加载器在 Linux 2.2 中是新的,旨在取代kerneld。 然而,它不具备 的所有功能kerneld.

在 Linux 2.2 中,内核模块加载器直接创建上述进程。 在 Linux 2.4 中,内核模块加载器将模块加载工作提交给 Keventd,它作为 Keventd 的子进程运行。

内核模块加载器是一个非常奇怪的野兽。 它违反了 Unix 程序员通常理解的分层,因此不灵活、难以理解且不健壮。 许多系统设计人员会仅仅因为它的 PATH 是硬编码的而感到恼火。 你可能更喜欢使用kerneld代替,或者根本不考虑自动加载 LKM。

5.4.1.2. Kerneld

kerneld在 Kerneld mini-HOWTO 中有详细解释,可从 Linux Documentation Project 获取。

kerneld是一个用户进程,它运行来自 modutils 包的 kerneld 程序。kerneld与内核建立 IPC 消息通道。 当内核需要 LKM 时,它会通过该通道向 发送消息kerneld并且kerneld运行 modprobe 以加载 LKM,然后向内核发回消息,说明已完成。

5.4.2. 自动卸载 - Autoclean

5.4.2.1. Autoclean 标志

每个加载的 LKM 都有一个 autoclean 标志,可以设置或取消设置。 你可以使用 的参数来控制此标志init_module系统调用。 假设你通过 insmod 执行此操作,则可以使用--autoclean选项。

你可以在/proc/modules中看到 autoclean 标志的状态。 任何设置了该标志的 LKM 都会有图例autoclean在它旁边。

5.4.2.2. 移除 Autoclean LKM

autoclean 标志的目的是让你自动移除一段时间(通常为 1 分钟)未使用的 LKM。 因此,通过使用自动模块加载和卸载,你可以仅保持加载当前需要的部分内核,并节省内存。

与以前相比,这不再那么重要,因为内存变得便宜得多。 如果你不需要节省内存,则不应为模块加载器进程的复杂性而烦恼。 只需通过初始化脚本加载你可能需要的所有内容,并保持加载状态即可。

有一种形式的delete_module系统调用,它表示,“移除所有设置了 autoclean 标志且一段时间未使用的 LKM。” Kerneld 通常每分钟调用一次。 你可以使用 rmmod --all 命令显式调用它。

由于内核模块加载器不执行任何 LKM 移除操作,因此如果你使用它,你可能需要有一个 cron 作业定期执行 rmmod --all

5.5. /proc/modules

要查看当前加载的 LKM,请执行

cat /proc/modules

你将看到类似如下的行

serial                   24484   0

左列是 LKM 的名称,通常是你从中加载它的目标文件的名称,减去“.o”后缀。 但是,你可以使用 insmod 上的选项选择你喜欢的任何名称。

“24484” 是 LKM 在内存中的大小(以字节为单位)。

“0” 是使用计数。 它告诉当前有多少事物依赖于 LKM 的加载。 典型的“事物”是打开的设备或挂载的文件系统。 这很重要,因为除非使用计数为零,否则你无法移除 LKM。 LKM 本身维护此计数,但模块管理器使用它来决定是否允许卸载。

上述使用计数的描述有一个例外。 你可能会在使用计数列中看到 -1。 这意味着此 LKM 不使用使用计数来确定何时可以卸载。 相反,LKM 注册了一个子例程,模块管理器可以调用该子例程,该子例程将返回是否可以卸载 LKM 的指示。 在这种情况下,LKM 应该为你提供一些自定义接口和一些文档,以确定何时可以自由卸载 LKM。

不要将使用计数与“依赖关系”混淆,后者在下面描述。

这是另一个示例,包含更多信息

lp                      5280   0 (unused)
parport_pc              7552   1
parport                 7600   1 [lp parport_pc]
方括号中的内容(“[lp parport_pc]”)描述了依赖关系。 在这里,模块lp并且parport_pc都引用模块中的地址parport(通过 导出的外部符号parport导出)。 所以lp并且parport_pc“依赖于”(并且是“的依赖关系”)parport.

你不能卸载具有依赖关系的 LKM。 但是你可以通过卸载依赖的 LKM 来移除这些依赖关系。

“(unused)” 图例表示 LKM 从未使用过,即它从未处于无法卸载的状态。 内核跟踪此信息有一个简单的原因:协助自动 LKM 卸载策略。 在自动加载和卸载 LKM 的系统中(请参阅 第 5.4 节),你不想自动加载一个 LKM,然后在需要加载它的家伙有机会使用它之前,因为它未使用而卸载它。

这是你通常不会看到的东西

mydriver                8154   0 (deleted)
这是一个处于 “deleted” 状态的 LKM。 这有点用词不当——它的意思是 LKM 正在卸载过程中。 你不能再加载依赖于它的 LKM,但它仍然存在于系统中。 卸载 LKM 通常几乎是瞬间完成的,因此如果你看到此状态,你可能有一个损坏的 LKM。 它的清理例程可能进入了无限循环或停顿或崩溃(导致内核 oops)。 如果是这种情况,清除此状态的唯一方法是重新启动。

还有类似的状态 “initializing” 和 “uninitialized”。

“(autoclean)” 图例指的是 autoclean 标志,在 第 5.4 节 中讨论过。

5.6. 我的 LKM 文件在系统上的什么位置?

LKM 世界非常灵活,你需要加载的文件几乎可以位于系统上的任何位置,但是大多数系统都遵循一个约定:LKM .o 文件位于目录中/lib/modules,分为子目录。 每个内核版本都有一个子目录,因为 LKM 特定于内核(请参阅 第 6 节)。 每个子目录都包含一套完整的 LKM。

子目录名称是你从 uname --release 命令获得的值,例如2.2.19第 6.3 节 介绍了如何控制该值。

当你构建 Linux 时,标准的 make modulesmake modules_install 应该将所有作为 Linux 一部分的 LKM 安装到正确的发布子目录中。

如果你构建了很多内核,另一种组织方式可能更有帮助:将 LKM 与基本内核和其他内核相关文件一起保存在 /boot 的子目录中。 唯一的缺点是你不能让 /boot 驻留在很小的磁盘分区上。 在某些系统中,/boot 位于一个特殊的微型 “boot 分区” 上,并且仅包含足够的文件以使系统启动到可以挂载其他文件系统的程度。