分类: LINUX
2010-05-14 23:38:40
模块
> 正如我们在第一章中介绍的那样,模块(module)是Linux用来高效地利用微内核的理论优点而不会降低系统性能的一种方法。
是否使用模块?
> 当系统程序员希望给Linux内核增加新功能时,就面临一个进退两难的问题:他们应该编写新代码从而将其作为一个新模块进行编译,还是应该将这些代码静态的链接到内核中?
> 通常,系统程序员都倾向于把新代码作为一个模块来实现。因为模块可以根据需要进行链接,这样内核就不会因为装载那些数以百计的很少使用的程序而变得非常庞大,这一点我们后面就会看到。几乎Linux内核的每个高层组件——文件系统、设备驱动程序、可执行格式、网络层等等——都可以作为模块进行编译。Linux的发布版,充分使用模块方式全面地支持多硬件设备。例如,发布版中会将几十种声卡驱动程序模块放在某个目录下,单是在某个计算机上只会有效加载其中一个声卡驱动程序。
> 然而,有些Linux代码必须被静态编译链接,也就是说相应组件或者被包含在内核中,或者根本不被编译。典型情况下,这发生在组件需要对内核中静态链接的某个数据结构或函数进行修改时。
> 例如,假如某个组件必须在进程描述符中插入新字段。链接一个模块并不能修改诸如task_struct之类已经定义的数据结构,因为即使这个模块使用其数据结构的修改版,所有静态链接的代码看到的仍是原来的版本,这样就很容易发生数据崩溃。对此问题的一种局部解决方法就是“静态地”把新字段加到进程描述符,从而让这个内核组件可以使用这些字段,而不用考虑组件究竟是如何被链接的。然而如果该内核组件从未被使用,那么在每个进程描述符中都复制这些额外的字段就是对内存的浪费。如果新内核组件对进程描述符的大小有很大的增加,那么,只有新内核组件被静态地链接到内核,才可能通过这个数据结构中增加需要的字段获得较好的系统性能。
> 再例如,考虑一个内核组件,它要替换静态链接的代码。显然,这样的组件不能作为一个模块来编译,因为在链接模块时内核不能修改已经在RAM中的机器码。例如,系统不可能链接一个改变页框分配方法的模块,因为伙伴系统函数总是被静态地链接到内核(注1)。
注1:你可能疑惑为什么不把你所钟爱的内核组件模块化。实际上,总的原因是软件许可证的问题,而不是技术原因。内核开发者想确保核心组件永远不会被仅发布二进制“黑盒”模块的私有代码所代替。
> 内核有两个主要的任务来进行模块的管理。第一个任务是确保内核的其他部分可以访问该模块的全局符号,例如指向模块主函数的入口。模块还必须知道这些符号在内核及其他模块中的地址。因此,在链接模块时,一定要解决模块间的引用关系。第二个任务是记录模块的使用情况,以便在其他模块或者内核的其他部分正在使用这个模块时,不能卸载这个模块。系统使用了一个简单的引用计数器来记录每个模块的引用次数。
模块许可证
> Linux内核许可证(GPL,版本2)不限制用户与企业使用其源代码,但是它严格禁止在非GPL许可证下发行相关的源代码,而这些代码起源于或大部分起源于Linux代码。也就是说,内核开发者要确保他们的代码及其衍生版本可由所有用户自由使用。
> 但是模块对这一机制造成了威胁。可能有人只发行了一个用于Linux内核的二进制格式模块;例如,厂商可能只以二进制格式模块发行一个硬件驱动程序。现在,这种情况较为少见。理论上说,Linux内核的特性和功能可能被只有二进制格式的模块极大的改变,从而把基于Linux的内核转变为商业产品。
> 因此,Linux内核开发者社团不太接受只有二进制格式的模块。Linux模块的实现就反映出这一点。一般地,使用MODULE_LICENSE,每个模块开发者应当在模块源代码中标出许可证类型。如果许可证是非GPL兼容(或根本没有标出),模块就不能使用内核的许多核心函数和数据结构。而且,使用非GPL许可证的模块会“玷污”内核,也就是说内核开发者不再考虑内核中可能的缺陷。
模块的实现
> 模块是作为ELF对象文件存放在文件系统中的,并通过执行insmod程序链接到内核中(参见后面的“模块的链接和取消”一节)。对于每个模块,系统都分配一个包含以下数据的内存区:
# 一个module对象
# 表示模块名的一个以null结束的字符串(所有模块都必须有唯一的名字)
# 实现模块功能的代码
> module对象描述一个模块,其字段如表B-1所示。一个双向循环链表存放所有module对象。链表头部存放在modules变量中,而指向相邻单元的指针存放在每个module对象的list字段中。
表B-1:module对象
Table B-1. The module object | ||
Type |
Name |
Description |
enum module_state |
state |
The internal state of the module |
struct list_head |
list |
Pointers for the list of modules |
char [60] |
name |
The module name |
struct module_kobject |
mkobj |
Includes a kobject data structure and a pointer to this module object |
struct module_param_attrs * |
param_attrs |
Pointer to an array of module parameter descriptors |
const struct kernel_symbol * |
syms |
Pointer to an array of exported symbols |
unsigned int |
num_syms |
Number of exported symbols |
const unsigned long * |
crcs |
Pointer to an array of CRC values for the exported symbols |
const struct kernel_symbol * |
gpl_syms |
Pointer to an array of GPL-exported symbols |
unsigned int |
num_gpl_syms |
Number of GPL-exported symbols |
const unsigned long * |
gpl_crcs |
Pointer to an array of CRC values for the GPL-exported symbols |
unsigned int |
num_exenTRies |
Number of entries in the module's exception table |
const struct exception_table_entry * |
extable |
Pointer to the module's exception table |
int (*)(void) |
init |
The initialization method of the module |
void * |
module_init |
Pointer to the dynamic memory area allocated for module's initialization |
void * |
module_core |
Pointer to the dynamic memory area allocated for module's core functions and data structures |
unsigned long |
init_size |
Size of the dynamic memory area required for module's initialization |
unsigned long |
core_size |
Size of the dynamic memory area required for module's core functions and data structures |
unsigned long |
init_text_size |
Size of the executable code used for module's initialization; used only when linking the module |
unsigned long |
core_text_size |
Size of the core executable code of the module; used only when linking the module |
struct mod_arch_specific |
arch |
Architecture-dependent fields (none in the 80 x 86 architecture) |
int |
unsafe |
Flag set if the module cannot be safely unloaded |
int |
license_gplok |
Flag set if the module license is GPL-compatible |
struct module_ref [NR_CPUS] |
ref |
Per-CPU usage counters |
struct list_head |
modules_which_use_me |
List of modules that rely on this module |
struct task_struct * |
waiter |
The process that is trying to unload the module |
void (*)(void) |
exit |
Exit method of the module |
Elf_Sym * |
symtab |
Pointer to an array of module's ELF symbols for the /proc/kallsyms file |
unsigned long |
num_symtab |
Number of module's ELF symbols shown in /proc/kallsyms |
char * |
strtab |
The string table for the module's ELF symbols shown in /proc/kallsyms |
struct module_sect_attrs * |
sect_attrs |
Pointer to an array of module's section attribute descriptors (displayed in the sysfs filesystem) |
void * |
percpu |
Pointer to CPU-specific memory areas |
char * |
args |
Command line arguments used when linking the module |
> state字段记录模块内部状态,它可以是:MODULE_STATE_LIVE(模块是活动的),MODULE_STATE_COMING(模块正在初始化)和MODULE_STATE_GOING(模块正在卸载)。
> 正如我们在第十章的“动态地址检查:修正代码”一节中已经介绍的那样,每个模块都有自己的异常表。该表包括(如果有)模块的修正代码地址。在链接模块时,该表被拷贝到RAM中,起开始地址保存在module对象的extable字段中。
模块使用计数器
> 每个模块都有一组使用计数器,每个CPU一个,存放在相应module对象的ref字段中。在模块功能所涉及的操作开始时递增这个计数器,在操作结束时递减这个计数器。只有所有使用计数器的和为0时,模块才可以被取消链接。
> 例如,假设MS-DOS文件系统层作为模块被编译,而且这个模块已经在运行时被链接。最开始时,该模块的引用计数器是0。如果用户装载一张MS-DOS软盘,那么模块引用计数器其中的一个就被递增1。反之,当用户卸载这张MS-DOS软盘,那么模块引用计数器其中的一个就被递减1(甚至不是刚才被递增的那一个)。模块的总的引用计数就是所有CPU计数器的总和。
导出符号
> 当链接一个模块时,必须用合适的地址替换在模块对象代码中引用的所有全局内核符号(变量和函数)。这个操作与在用户态编译程序时链接程序所执行的操作非常相似(参见第十二章“库”一节),这是委托给insmod外部程序完成的(将在后面“模块的链接和取消”一节进行介绍)。
> 内核使用一些专门的内核符号表(kernel symbol table),用于保存模块访问的符号和相应的地址。它们在内核代码段中分三节:__kstrtab节(保存符号名)、__ksymtab节(所有模块可使用的符号地址)和__ksymtab_gpl节(GPL兼容许可证下发布的模块可以使用的符号地址)。当用静态链接内核代码时,EXPORT_SYMBOL与EXPORT_SYMBOL_GPL宏让C编译器分别往__ksymtab和__ksymtab_gpl部分相应地加入一个专用符号。
> 只有某一现有的模块实际使用的内核符号才会保存在这个表中。如果系统程序员在某些模块中需要访问一个尚未导出的内核符号,那么他只要在Linux源代码中增加相应的EXPORT_SYMBOL_GPL宏就可以了。当然许可证不是GPL兼容的,它就不能为模块合法导出一个新符号。
> 已链接的模块也可以导出自己的符号,这样其他模块就可以访问这些符号。模块符号表(module symbol table)部分保存在模块代码段的__ksymtab,__ksymtab_gpl和__kstrtab部分中。要从模块中导出符号的一个子集,程序员可以使用上面描述的EXPORT_SYMBOL和EXPORT_SYMBOL_GPL宏。当模块链接时,模块的导出符号被拷贝到两个内存数组中,而其地址保存在module的syms和gpl_syms字段中。
模块依赖
> 一个模块(B)可以引用由另一个模块(A)所导出的符号:在这种情况下,我们就说B模块在A的上面,或者说A被B使用。为了链接模块B,必须首先链接模块A;否则,对于模块A所导出的那些符号的引用就不能适当地链接到B中。间而言之,在这两个模块之间存在着依赖(dependency)。
> A模块对象的modules_which_use_me字段是一个依赖链表的头部,该链表保存A使用的所有模块。链表中每个元素是一个小型module_use描述符,该描述符保存指向链表中相邻元素的指针及一个指向相应模块对象的指针。在本例中,指向B模块对象的module_use描述符将出现在A的module_which_use_me链表中。只要有模块装载在A上,module_which_use_me链表就必须动态更新。如果A的依赖链表非空,模块A就不能卸载。
> 当然,除了A和B之外,还会有其他模块(C)装载到B上,以此类推。模块的堆叠是对内核源代码进行模块化的一种有效方法,目的是为了加速内核开发。