购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

1.4 内核模块(module)

Linux 内核是一个庞大、完整的操作系统,它包括了操作系统功能所有需要的组件。这样的好处是功能强大、运行效率高。但其缺点就是不够灵活,不容易扩展。当我们要为内核扩展一个新的功能时,特别是某些组件需要不断更新(如设备驱动程序,随着新设备的不断推出,要不断增加新驱动程序)时,每次小的改动,都需要编译整个操作系统,这是一个非常耗时的过程。

Linux解决这个问题的方式是内核模块(module)。在需要的时候这些内核模块可以在运行时动态地加入到系统中。当不再需要的时候,可以将其从系统中移走。

图1-1给出的内核组件示例中,可以使用模块方式来扩展功能的组件有:设备驱动程序、文件系统、网络协议和网络设备驱动程序。模块的使用实际上并不仅仅局限于这些组件,它可以作为一个独立功能加入到内核中。向内核加入新功能时,内核中也需要相应的接口去通知内核的其他部件关于新功能的信息。Linux 网络体系结构中的接口和在其中扩展新功能的实现方式是本书要讨论的一个主要问题。

在本节的以下部分,我们会较详细地讨论Linux 内核模块的结构和管理,因为模块是增强Linux 网络体系结构最好,也是最灵活的方式。在网络子系统中大量使用了模块技术来扩展网络功能。

1.4.1 管理内核模块

内核模块由目标代码组成,它在运行时装载到内核地址空间并运行。在系统启动时,内核事先并不知道会有什么功能的模块会装载到系统中,所以模块必须自己通知内核,让相应的组件知道模块加载与否。当模块移走时,它也需要移走所有在内核地址空间对它的引用,释放占用的系统资源。这里有两个方法是用来完成以上任务的。

● init_模块:向内核注册由模块提供的所有功能。

● cleanup_模块:撤销任何由init_模块所做的功能。

这两个方法每个模块都需要实现。在进一步深入理解模块的工作原理之前,我们首先给出一些常规的命令,以便了解如何从内核外来管理模块,用户可以用以下工具来手动插入和卸载模块。

1.插入模块

insmod 模块name.o [arguments]:该命令用于装载一个内核模块到内核地址空间。如果成功,模块的目标代码就被链接到内核中了,这样模块就可以访问内核的符号(函数和数据结构)。发送命令insmod后会引起以下的系统调用执行。

● sys_create_模块:给模块在内核地址空间分配其驻留所需的内存。

● sys_get_kernel_syms:返回内核的符号表,解决模块中尚未连接的对内核符号的引用。

● sys_init_模块:复制模块的目标代码到内核地址空间,并调用模块的初始化函数(init_模块)执行模块的初始化功能。

2.给模块传参数

当我们装载模块时,也可以给模块传参数(例如设备的名称name,设备的中断信号irq和I/O端口地址io_addr)。在实现模块时,这些参数需要在模块中用宏MODULE_PARM(arg,type,default)来说明装载模块时可以给模块传哪些参数。当用insmod命令装载模块时,这些参数可以直接传给要调度的模块:

在上例中,我们插入了一个名为mylan_cs.o的模块,并给它传送了两个参数,一个是设备ID,一个是设备名。

3.移走模块

rmmod 模块name:从内核地址空间卸载指定的模块。为此该命令会引起系统调用函数sys_delete_模块的执行,而sys_delete_模块系统会调用模块的清除函数cleanup_模块。这样模块就从内核地址空间卸载了。

4.其他用户空间命令

● lsmod:列表当前所有装载了的模块以及它们相互的依赖关系和引用计数。

● modinfo:给出关于模块的信息(它的功能、参数和所有者等)。这些信息并不是自动产生的,它需要使用宏MODULE_DESCRIPTION,MODULE_AUTHOR在模块的源代码中定义。

1.4.2 自动装载模块

除了使用以上命令行工具外,内核模块可以在需要的时候自动装载到内核中。使用我们在前面介绍的工具装载模块和卸载模块需要用户的干预,而且出于安全原因只有根用户能使用insmod和rmmod来加载和卸载模块。虽然这样保证了安全,但却带来了不便。例如,当一个用户程序在运行过程中需要使用一个功能,但该功能的模块还没有加载到内核中,这就需要内核有能力自动调度模块到内核地址空间。

通常,在程序运行过程中如果需要的资源或某个设备驱动程序没有注册,内核会报错。你可以事先用内核函数request_模块申请需要的组件模块。为了使用这个函数,在配置内核时需要激活选项Kernel Module Loader。request_模块会调用modprobe命令自动装载需要的模块(并调度模块依赖的其他模块)。要自动调度哪些模块需要在配置文件/etc/模块s.conf中设置。

下面给出了/etc/模块s.conf配置文件的示例。在该文件中它指明当前网络设备是由模块mylan_cs代表的,为了装载该模块,需要给它传送特定的参数。如果modprobe不能找到模块,printk会给出错误信息。

虽然这种方式可以自动装载模块,但它也只能调度系统管理员在配置文件中指定的模块。

1.4.3 模块功能的注册和取消

应用程序的作用通常是运行后完成一定的功能,模块的主要任务是为当前内核中的其他组件提供服务。某个时候内核以模块的方式增加了新功能,运行一段时间后,当不再需要这种功能时,可能将其移去。在系统启动阶段我们无从知道将会有什么功能以模块的形式加入到系统中,所以我们需要为模块提供接口来注册。内核中各组件都有相应的模块注册的接口(如注册和取消网络驱动程序、文件系统、协议等)。

这些接口很容易从其函数名识别出来,一般是以register_...和unregister_...开始的函数。表1-3中给出了一些接口的函数示例。

表1-3 内核组件模块注册和取消函数

模块的注册和初始化由模块自己的init_mudule方法来完成。如前所述,该函数是在模块成功地集成到内核中后直接被调用。init_模块需要完成所有的模块初始化任务,如申请内存空间,创建在 /proc文件系统中的入口,初始化数据结构,注册函数等。

init_模块函数执行成功后,模块的功能就被内核识别了,而且它需要的所有初始化过程都应该运行结束;如果在初始化过程的某个环节出了错,所有在此之前执行的动作都应回退。因为,当init_模块返回的是错误代码时,模块的目标代码会从内核地址空间卸载,所以任何对该模块原地址空间的访问都会导致不可预期的错误。

模块自己的方法cleanup_模块,是用于将模块从内核地址空间卸载的。它需要清除原模块的所有运行环境(注销模块的功能,释放模块占用的内存,去掉内核中模块间和部件间的相互依赖关系)。一旦调用了cleanup_模块,内核或其他模块就不应该再引用该模块了,否则会导致存储器访问出错,造成系统崩溃。

1.4.4 在模块装载时给模块传递参数

在本节的开始我们提到过,在内核模块装载时可以给它传递参数。这些参数可以在使用insmod命令时直接给出,也可以在使用modprobe命令时,在配置文件中给出。为了可以给模块传递参数,你必须事先在模块的代码中声明这些参数,以下的宏就是用于声明模块参数的。

1.为模块声明参数

MODULE_PARM(var,type):声明var是该模块的一个参数,在装载模块期间,可以给它分配一个值,type指定了参数的类型。模块的参数可以是以下的类型。

● b:字节(byte)。

● h:短整型(short两个字节)。

● i:整型数(integer)。

● l:长整型(long)。

● s:字符串(string或指向字符串的指针)。

2.模块参数描述信息

MODULE_PARM_DESC(var,desc):给参数加入描述信息。这些描述信息在使用modinfo命令时,会显示对参数的描述。

1.4.5 内核和模块的符号表

内核的模块是目标代码,在运行时加入到内核中,一旦它被嵌入到内核后,模块就在内核的地址空间了。在模块被嵌入到内核之前,有几个问题需要注意,模块中的函数可能需要调用内核的函数,也可能需要使用内核的数据结构,所以我们首先需要展开这些函数和数据结构的地址。Linux内核中包含了一个符号表ksym,表中包含了所有符号(函数名、变量名)与地址的对应关系。

模块只能访问内核符号表中列出的函数和变量。你可以用命令ksyms –a来列出内核符号表的内容。

定义在kernel/ksyms.c中的指令EXPORT_SYMBOL(xxx) 向符号表中加入内核的函数或变量,这样模块就可以访问这些符号了。除此之外,模块也可以向符号表输出模块的函数和变量的引用指针。宏EXPORT_SYMBOL允许模块向符号表加入选定的函数或数据指针。 rzdb+nZXf3C2fRcf6a96fNkB8EhBPUAhuNFadq20Bhf/ZDpQKmIFhX5Yqk5uWsOh

点击中间区域
呼出菜单
上一章
目录
下一章
×