📄 (ldd) ch11-kerneld和高级模块化(转载).txt
字号:
从request_module返回的值以及/proc/modules文件(在第二章的“初始化和终止”一节
中描述)均显示slave模块已经正确装载。另一方面,装载unexists的返回值255意味着
用户程序失败,退出码是255(或-1,因为它的长度为一个字节)。
我们简要的看看在卸载时会发生些什么,但在此之前先让我们手工加载slaveH:
morgana.root# insmod slaveH
morgana.root# insmod slaveH
morgana.root# cat /proc/modules
slaveH 1 0
slaveD 1 0 (autoclean)
slave 1 0 (autoclean)
master 1 0
isofs 5 0 (autoclean)
morgana.root# rmmod master
master: unloading results are 0,0,0,255
morgana.root# cat /proc/modules
slaveD 1 0 (autoclean)
isofs 5 1 (autoclean)
isofs 5 1 (autoclean)
morgana.root# sleep60;cat /proc/modules
isofs 5 1 (autoclean)
结果显示,除了unexists的卸载,一切都很正常,并且slaveD在一段时间过后也会被卸
载。
尽管提供了各种例程,你还会发现,大部分时间request_module函数都能满足你的需要
,而不要求你处理模块卸载;实际上,对不使用的模块,缺省地会自动进行卸载。绝大
大部分时候,你甚至不必检查函数的返回值,因为只需要模块提供的一些函数。下面的
实现比检查request_module的返回值更方便:
if ( (ptr = look_for_feature()) == NULL ) /* 是否没有该特性 */
request_module(modname); /* 试图装载它 */
request_module(modname); /* 试图装载它 */
if ( (ptr = look_for_feature()) == NULL ) /* 是否仍没有该特性 */
return -ENODEV; /* 出错 */
模块中的版本控制
关于模块的一个主要问题是它们的版本相关性,在第二章的“版本相关性”一节中我曾
经介绍过。针对每个要使用的版本的不同头文件都需要重新编译一次模块,当你运行好
几个定制的模块时,这是件非常痛苦的事情。如果你运行的是以二进制形式发布的商业
模块时,甚至连重新编译也是不可能的。
幸运的,内核开发者找到了一个变通的办法来处理版本问题。他们的想法是,只有改变
了内核提供的软件接口,一个模块才不能兼容不同的内核版本。然后,软件接口可以由
函数原型以及函数调用涉及到的所有数据结构的确切定义所表示。最后,可以使用一个C
RC算法把所有关于软件接口的信息映射到一个单一的32位数值*上去。
版本相关性的问题通过在每个由内核导出的符号的名字后面附加一个该符号相关信息的
校验和来得到处理。解析头文件,就可以从中取出这些信息。这种便捷性是可选的,在
校验和来得到处理。解析头文件,就可以从中取出这些信息。这种便捷性是可选的,在
编译的时候可以启动它们。
例如,当启动版本支持时,符号printk以类似printk_R12345678的形式向模块开放,这
里12345678时函数使用的软件接口的检验和的16进制表示。加载一个模块到内核时,仅
当加到内核中每个符号上的检验和匹配加到模块中相同符号上的校验和时,insmod(或mo
dprobe)才能够完成它的任务。
让我们来看看内核和模块都启动了版本支持的时候,会发生些什么:
l 内核本身并不修改符号。进程以通常的方式与内核链接,而且vmlinux文件的
符号表看起来也和以前一样。
l 公共符号表使用版本名字创建,如/proc/ksyms文件所示。
l 模块必须使用合并后的名字编译,这些名字在目标文件中是以未定义符号出现
的。
l 装载程序用模块中未定义符号匹配内核中的公共符号,因此也要使用版本信息
。
然而,上述情况只有当内核和模块都创建成支持版本化时才有效。如果有任何一方使用
了原来的符号名,insmod都会放弃版本信息,并试着用第二章的“版本相关性”一节中
描述的方式来匹配模块声明的内核版本号和内核提供的版本号。
在模块中使用版本支持
当内核已经准备(可选的)输出版本化的符号时,模块源代码只需准备好支持该选项。可
以在两处加入版本控制:在Makefile中或在源代码本身。因为modules包文档描述了在Ma
kefile中如何做,我将向你显示在C源代码中如何做。用于演示kerneld如何工作的maste
r模块能够支持版本化符号。如果用于编译模块的内核利用了版本支持的话,这种能力会
自动启动。
自动启动。
用于合并符号名字的主要工具是头文件<linux/modversions.h>,它包括了所有公共内核
符号的预处理定义。在包含这个头文件后,不管模块何时使用了内核符号,编译器都将
看到合并了的版本。modversions.h中的定义只有预先定义过MODVERSIONS才有效。
如果内核已经启动了版本支持,为了在模块中也启动它,我们必须保证在<linux/autoco
nf.h>中已经定义过CONFIG_MODVERSIONS。那个头文件控制着在当前内核中(编译时)启动
了哪些特性。每个CONFIG_宏定义声明相应选项的状态是否要激活。
这样,master.c的初始化部分包含如下部分:
#include <linux/autoconf.h> /* 检索CONFIG_*宏 */
#if defined(CONFIG_MODVERSION) && !defined(MODVERSIONS)
#if defined(CONFIG_MODVERSION) && !defined(MODVERSIONS)
# define MODVERSIONS /* 强迫打开它 */
#endif
#ifdef MODVERSIONS
# include <linux/modversions.h>
#endif
在版本化的内核上编译这个文件时,目标文件的符号表会引用版本化符号,这些版本化
符号匹配内核本身开放的那些符号。下面的屏幕快照显示了master.o中储存的符号名字
。在nm的输出中,"T"代表“文本(text)”,"D"代表“数据(data)”,"U"代表“未定义
(undefined)”。最后一个标记表示目标文件引用了但没有声明的符号。
morgana% nm master.o
000000b0 T cleanup_module
00000000 T init_module
00000000 D kernel_version
U kerneld_send_R7d428f45
U printk_Rad1148ba
morgana% egrep 'printk|kerneld_send' /proc/ksyms
00131b40 kerneld_send_R7d428f45
0011234c printk_Rad1148ba
因为加到master.o中符号名上的校验和包含了与printk和kerneld_send相关的整个接口
,模块与大部分内核版本都兼容。然而,如果与其中任一函数有关的数据结构被改变了
,模块与大部分内核版本都兼容。然而,如果与其中任一函数有关的数据结构被改变了
,insmod将因为模块与内核的不兼容而拒绝装载它。
开放版本化符号
以前的讨论中未涉及的情况是,当其它模块使用一个模块开放的符号时,会发生写什么
。如果依赖版本信息来获得模块的可移植性,那么我们希望能把CRC校验码加到我们自己
的符号上去。这个问题比仅仅链接到内核技巧性更高一些,因为我们需要将合并后的符
号名向其它模块开放;我们需要一种办法来生成校验和。
分析头文件和生成校验和的任务是由随modules包一起发行的一个工具genksyms来做的。
这个程序在自身的标准输入上接受C预处理器的输出,并在标准输出上打印出一个新的头
文件。这个输出文件定义了原来那个源文件开放出来的每个符号的带检验和的版本。gen
ksyms的输出通常以后缀.ver保存;下面我将遵循同样的惯例。
为了显示如何开放符号,我生成了两个名为export.c和import.c的虚构的模块。export
开放了一个名为export_function的简单函数,并且该函数会被第二个模块import.c使用
。这个函数接收两个整数参数并返回它们的和--我们对这个函数并不感兴趣,而是对链
接过程更感兴趣。
misc-modules目录下的Makefile文件有从export.c生成export.ver文件的规则,因此exp
ort_function的检验和符号可以被import模块使用:
ifdef MODVERSIONS
export.o import.o: export.ver
endif
export.ver: export.c
$(CC) -I$(INCLUDEDIR) -E -D__GENKSYMS__ $^|genksyms > $@
这几行演示了如何生成export.ver,并且只有定义过了MODVERSIONS才会把它加到两个目
这几行演示了如何生成export.ver,并且只有定义过了MODVERSIONS才会把它加到两个目
标文件的依赖关系中去。如果内核启动了版本支持,还要添加几行到Makefile中负责定
义MODVERSIONS,但并不值得在这里展示它们。
然后,源文件必须为每个可能的预处理流程声明正确的预处理符号:不论是给genksyms
的输入和真正编译过程,不论是启动还是关闭了版本支持。进一步,export.c应当能够
象master.c那样自动检测内核中的版本支持。下面几行向你显示了如何成功地做到这一
点:
#ifndef EXPORT_SYMTAB
# define EXPORT_SYMTAB /* 需要这个定义是因为我们要开放符号*/
#endif
#include <linux/autoconf.h> /* 检索CONFIG_* 宏 */
#if defined(CONFIG_MODVERSIONS)&& !defined(MODVERSIONS)
# define MODVERSIONS
#endif
/*
* 将内核符号和我们的符号的版本化定义包含进来,*除非*我们正在
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -