目录, 显示框架, 无框架

第 12 章
模块


本章介绍 Linux 内核如何动态加载功能,例如文件系统,仅在需要时加载。

Linux 是一个单内核;也就是说,它是一个单一的、大型的程序,内核的所有功能组件都可以访问其所有内部数据结构和例程。另一种选择是使用微内核结构,其中内核的功能部分被分解为独立的单元,并在它们之间建立严格的通信机制。这使得通过配置过程向内核添加新组件非常耗时。假设您想为 NCR 810 SCSI 使用 SCSI 驱动程序,但您没有将其构建到内核中。您将必须配置然后构建一个新的内核才能使用 NCR 810。还有另一种选择,Linux 允许您根据需要动态加载和卸载操作系统的组件。Linux 模块是可以动态链接到内核的代码块,可以在系统启动后的任何时候进行链接。当不再需要它们时,可以从内核取消链接并移除。大多数 Linux 内核模块是设备驱动程序、伪设备驱动程序(例如网络驱动程序)或文件系统。

您可以显式地使用以下命令加载和卸载 Linux 内核模块:insmodrmmod命令,或者内核本身可以要求内核守护进程 (kerneld) 在需要时加载和卸载模块。

根据需要动态加载代码很有吸引力,因为它使内核大小保持在最小限度,并使内核非常灵活。我当前的 Intel 内核广泛使用模块,长度仅为 406K 字节。我只是偶尔使用VFAT文件系统,所以我构建我的 Linux 内核以自动加载VFAT文件系统模块,当我挂载一个VFAT分区时。当我卸载VFAT分区后,系统检测到我不再需要VFAT文件系统模块,并将其从系统中移除。模块对于尝试新的内核代码也很有用,而无需每次尝试都重新构建和重新启动内核。但是,没有什么是免费的,内核模块会带来轻微的性能和内存损失。可加载模块必须提供更多代码,这些代码和额外的数据结构会占用更多内存。还引入了一个间接级别,这使得模块访问内核资源的效率略低。

一旦 Linux 模块被加载,它就和任何正常的内核代码一样成为内核的一部分。它与任何内核代码具有相同的权利和责任;换句话说,Linux 内核模块可能会像所有内核代码或设备驱动程序一样导致内核崩溃。

为了使模块可以使用它们需要的内核资源,它们必须能够找到这些资源。假设一个模块需要调用kmalloc(),内核内存分配例程。在构建模块时,模块不知道kmalloc()kmalloc()kmalloc()在内存中的位置,因此当模块被加载时,内核必须修复模块对VFATkmalloc()的所有引用,然后模块才能工作。内核在内核符号表中保存内核所有资源的列表,以便它可以解析模块加载时对这些资源的引用。Linux 允许模块堆叠,即一个模块需要另一个模块的服务。例如,vfatVFAT文件系统模块需要的所有引用,然后模块才能工作。内核在内核符号表中保存内核所有资源的列表,以便它可以解析模块加载时对这些资源的引用。Linux 允许模块堆叠,即一个模块需要另一个模块的服务。例如,fat

文件系统模块的服务,因为

vfat

文件系统或多或少是


fat

文件系统的一组扩展。一个模块需要来自另一个模块的服务或资源的情况,与一个模块需要来自内核本身的服务和资源的情况非常相似。只是这里所需的服务位于另一个先前加载的模块中。当每个模块被加载时,内核都会修改内核符号表,将新加载的模块导出的所有资源或符号添加到其中。这意味着,当下一个模块被加载时,它可以访问已经加载的模块的服务。insmod当尝试卸载模块时,内核需要知道该模块未被使用,并且它需要某种方式通知模块它即将被卸载。这样,模块将能够在从内核中移除之前释放它已分配的任何系统资源,例如内核内存或中断。当模块被卸载时,内核会删除该模块导出到内核符号表中的任何符号。

除了加载的模块可能因编写不当而导致操作系统崩溃的能力外,它还存在另一种危险。如果您加载为早于或晚于您当前运行的内核构建的模块会发生什么?如果模块调用内核例程并提供错误的参数,这可能会导致问题。内核可以选择通过在加载模块时进行严格的版本检查来防止这种情况。

12.1  加载模块kerneld图 12.1:内核模块列表

加载内核模块有两种方法。第一种方法是使用kerneldinsmod

命令手动将其插入到内核中。第二种,也是更聪明的方法,是在需要时加载模块;这被称为按需加载。命令手动将其插入到内核中。当内核发现需要模块时,例如当用户挂载内核中不存在的文件系统时,内核将请求内核守护进程 (insmodkerneld命令手动将其插入到内核中。) 尝试加载相应的模块。

内核守护进程是一个普通的用户进程,尽管具有超级用户权限。当它启动时,通常在系统启动时,它会打开一个进程间通信 (IPC) 通道到内核。内核使用此链接向insmodkerneld发送消息,要求执行各种任务。Kerneld的主要功能是加载和卸载内核模块,但它也能够执行其他任务,例如在需要时启动通过串行线的 PPP 链接,并在不需要时关闭它。kerneld不自己执行这些任务,它运行必要的程序,例如pppinsmod来完成工作。

kerneld只是内核的代理,代表内核调度工作。insmod实用程序必须找到它要加载的请求的内核模块。按需加载的内核模块通常保存在/lib/modules/kernel-version

中。内核模块是链接的目标文件,就像系统中的其他程序一样,只是它们被链接为可重定位的映像。也就是说,未链接为从特定地址运行的映像。它们可以是a.outelf格式的目标文件。insmod进行特权系统调用以查找内核导出的符号。insmod这些符号保存在包含符号名称及其值(例如其地址)的对中。内核的导出符号表保存在内核维护的模块列表中的第一个insmodmoduleinsmod数据结构中,并由

module_listinsmod指针指向。只是内核的代理,代表内核调度工作。只有专门输入的符号才会被添加到表中,该表是在内核编译和链接时构建的,而不是内核中的每个符号都导出到其模块。一个示例符号是``request_irq''.

,它是驱动程序希望控制特定系统中断时必须调用的内核例程。在我当前的内核中,它的值为 0x0010cd30。您可以通过查看VFATVFAT/proc/ksyms或使用ksyms或使用实用程序轻松查看导出的内核符号及其值。ksyms实用程序可以向您显示所有导出的内核符号,或者仅显示由加载的模块导出的符号。只是内核的代理,代表内核调度工作。insmodinsmod将模块读取到其虚拟内存中,并使用内核导出的符号修复其对内核例程和资源的未解析引用。这种修复采取在内存中修补模块映像的形式。insmodinsmod

物理地将符号的地址写入模块中的适当位置。insmodinsmodinsmodinsmod修复了模块对导出的内核符号的引用后,它会向内核请求足够的空间来容纳新的内核,再次使用特权系统调用。内核分配一个新的

module只是内核的代理,代表内核调度工作。数据结构和足够的内核内存来容纳新模块,并将其放在内核模块列表的末尾。新模块被标记为VFATUNINITIALIZED的所有引用,然后模块才能工作。内核在内核符号表中保存内核所有资源的列表,以便它可以解析模块加载时对这些资源的引用。Linux 允许模块堆叠,即一个模块需要另一个模块的服务。例如,图 12.1 显示了两个模块的所有引用,然后模块才能工作。内核在内核符号表中保存内核所有资源的列表,以便它可以解析模块加载时对这些资源的引用。Linux 允许模块堆叠,即一个模块需要另一个模块的服务。例如,vfat.oVFATVFATmsdos.o只是内核的代理,代表内核调度工作。已加载到内核后的内核模块列表。图中未显示列表中的第一个模块,它是一个伪模块,仅用于保存内核的导出符号表。您可以使用命令lsmod.

列出所有加载的内核模块及其相互依赖关系。

lsmodrmmod只是重新格式化kerneld/proc/moduleskerneld,它是从内核kerneldmodulekerneld数据结构列表中构建的。内核为其分配的内存被映射到insmod进程的地址空间,以便它可以访问它。insmodinsmodinsmod将模块复制到分配的空间中并重新定位它,以便它将从已分配的内核地址运行。这必须发生,因为模块不能期望被加载到相同的地址两次,更不用说在两个不同的 Linux 系统中加载到相同的地址。同样,这种重定位涉及使用适当的地址修补模块映像。

新模块还向内核导出符号,并且VFATinsmodVFAT构建这些导出映像的表。每个内核模块都必须包含模块初始化和模块清理例程,并且这些符号被有意不导出,但是或使用insmod

Module:        #pages:  Used by:
msdos              5                  1
vfat               4                  1 (autoclean)
fat                6    [vfat msdos]  2 (autoclean)

必须知道它们的地址,以便它可以将它们传递给内核。一切顺利,insmod现在准备好初始化模块,并进行特权系统调用,将模块的初始化和清理例程的地址传递给内核。当新模块添加到内核中时,它必须更新内核的符号集,并修改新模块正在使用的模块。具有其他模块依赖于它们的模块必须在其符号表的末尾维护引用列表,并由它们的module数据结构指向。图 12.1 显示insmod现在准备好初始化模块,并进行特权系统调用,将模块的初始化和清理例程的地址传递给内核。vfatVFAT文件系统模块依赖于insmodfat

文件系统模块。因此,vfat模块包含对fatvfat模块的引用;该引用是在模块包含对vfatkerneld模块加载时添加的。内核调用模块的初始化例程,如果成功,它将继续安装模块。模块的清理例程地址存储在其vfatmodulelsmod数据结构中,当该模块被卸载时,内核将调用它。最后,模块的状态设置为模块包含对RUNNING模块包含对12.2  卸载模块

可以使用

内核守护进程是一个普通的用户进程,尽管具有超级用户权限。当它启动时,通常在系统启动时,它会打开一个进程间通信 (IPC) 通道到内核。内核使用此链接向只是内核的代理,代表内核调度工作。rmmod命令移除模块,但是当按需加载的模块不再使用时,kerneld


会自动从系统中移除它们。每次其空闲计时器到期时,
kerneld
都会发出系统调用,请求从系统中移除所有未使用的按需加载模块。计时器的值在您启动