全部博文(127)
分类: LINUX
2009-10-26 19:15:47
1. The Kernel Symbol Table (内核符号表)
在实现驱动程序模块时,在很多情况下都需要使用这些全局符号。 公用符号表能够从文件 /proc/ksyms中以文本格式读取,或者ksyms命令输出。当模块被装入内核之后,它所导出的任何符号都变成公用符号表的一部分。
如果不希望模块导出任何符号,则可以在源文件中添加如下一行宏调用来显式说明:
EXPORT_NO_SYMBOLS;
它可以出现在模块的任何地方,建议应该将它放在模块的初始化函数 init_module 中。
如果我们准备导出模块中符号的一个子集,则首先需要定义预处理宏 EXPORT_SYMTAB,而且
这个宏必须在包含 module.h 之前定义。一种很常见的定义方法是在模块编译时在 Makefile 中使
用 -D 编译选项指定。
用 EXPORT_SYMBOL(name); 来导出模块的一个符号。
2.init_module 中的出错处理
设施的注册过程中如果出现任何错误,都要将出错之前的注册工作撤销。下面几种情况下都可能发
生错误,例如,系统中没有足够的内存分配给一个数据结构,或者,请求的资源正在被其它驱动程
序使用等。 虽然错误不是经常发生, 但它仍然有可能发生, 因此我们必须做好处理这些错误的准备。
Linux 中没有记录每个模块都注册了哪些设施, 因此模块必须自己备份每步操作, 防止 init_module
在某步出错。如果由于某种原因我们未能撤销已经注册的设施,则内核会处于一种不稳定状态:一
方面,这些设施处于忙的状态,我们不能重新装入模块来再次注册设施;另一方面,我们也不能撤
销对它们的注册,因为我们失去了注册这些设施时使用的指向描述设施的数据结构的指针。想从这
种处境中恢复比较困难,通常需要重启机器并装入修改后的新模块。
3. Module的参数
参数值可由 insmod 或者 modprobe
在装载模块时设置,后者还可以从配置文件(通常是/etc/modules.conf
)中获得参数赋值。这些命令能够在命令行中接受整型和字符串型赋值。因此,如果模块需要获得一个叫做 skull_ival 的整型参数和一个叫做
skull_sval 的字符串型参数,我们可以在模块装载时以下面的方式使用 insmod 命令设置参数:
insmod skull skull_ival=666 skull_sval="the beast"
然而,在 insmod 能够改变模块参数之前,模块必须能够访问这些参数。参数由定义在 module.h 中的宏 MODULE_PARM
声明。MODULE_PARM
必须带两个参数:变量名和描述变量类型的字符串。这个宏应位于任何函数之外,通常放在源文件的起始部分。上面提到的两个参数可用下面几行来声明:
int skull_ival=0;
char *skull_sval;
MODULE_PARM (skull_ival, "i");
MODULE_PARM (skull_sval, "s");
目前模块参数只支持五种类型:b,字节(byte) ;h,短整型(short,两字节) ;i,整型(integer) ;l,长整型(long)
;s,字符串(string) 。如果是字符串值,则需要声明一个指针变量。insmod
负责为用户提供的参数分配内存并设置相应变量。类型前面的整数表明这是一个指定长度的数组,被间隔符分开的两个数字指明了数组元素数目的最大最小值。
如果我们想了解设计者对这个特征的详细描述,可以参考头文件
作为一个例子,至少有两个元素、至多不超过 4个元素的数组可定义为:
int skull_array[4];
MODULE_PARM (skull_array, "2-4i");
所有模块参数都应该赋予一个默认值,用户可以使用 insmod 来显式改变。模块通过和默认值比较来确定显式参数值。因此,可以这样设计模块自动配置:如果配置变量具有默认值则执行自动检测,否则保持当前值。要想让这 种方法生效,默认值应该是用户在装载模块时从来不会使用的参数值。
[1]Quick Reference
_KERNEL_ _ "MODULE"
预处理符号。在编译模块化内核代码时必须定义。
_SMP_ _
预处理符号。为多处理器系统编译模块时必须定义。
int init_module(void);
void cleanup_module(void);
模块入口点。在模块目标文件中必须定义。
#include
module_init(init_function);
module_exit(cleanup_function);
新版本内核中用来标记模块初始化和清除函数的新机制。
#include
必需的头文件。它必须包含在模块源代码中。
MOD_INC_USE_COUNT;
MOD_DEC_USE_COUNT;
MOD_IN_USE;
操作使用计数的宏。
/proc/modules
列出装入内核的模块列表。每个列表项包含模块名、模块占用内存大小以及使用计数等域,还有一
些附加字符串指明模块当前的活动选项。
EXPORT_SYMTAB;
预处理宏。在模块需要导出符号时定义。
EXPORT_NO_SYMBOLS;
指明模块不需要导出任何符号到内核。
EXPORT_SYMBOL (symbol);
EXPORT_SYMBOL_NOVERS (symbol);
用来导出单个符号到内核的宏。第二个宏导出的符号不带版本控制信息。
int register_symtab(struct symbol_table *);
用来指定模块中公用符号集合的函数。仅用于 2.0内核。
#include
X(symbol),
#include
2.0 内核中用于声明符号表的头文件和预处理器。
MODULE_PARM(variable, type);
MODULE_PARM_DESC (variable, description);
将一个模块变量定义为参数的宏,用户随后可在装入模块时调整这个变量值。
MODULE_AUTHOR(author);
MODULE_DESCRIPTION(description);
MODULE_SUPPORTED_DEVICE(device);
在目标文件中添加关于模块的文档信息。
#include
必需的头文件。除非 _ _NO_VERSION_ _ (见下面)被定义,否则它被
LINUX_VERSION_CODE
整数宏,用在处理版本依赖的预处理条件语句中。
char kernel_version[] = UTS_RELEASE;
每个都模块必需的变量。 除非已经定义 _ _NO_VERSION_ _ (见下一项) , 否则
必须定义它。
_ _NO_VERSION_ _
预处理器符号。用来防止在
#include
最重要的头文件之一,包含驱动程序使用的大部分内核 API 的定义,包括睡眠函数以及各种变量
声明。
struct task_struct *current;
当前进程。
current->pid
current->comm
当前进程的进程 ID 和命令名。
#include
int printk(const char * fmt, ...);
函数 printf 的内核版。
#include
void *kmalloc(unsigned int size, int priority);" "void kfree(void *obj);
函数 malloc 和 free 的内核版。使用 GFP_KERNEL 作为 priority 参数值。
#include
int check_region(unsigned long from, unsigned long extent);
struct resource *request_region(unsigned long from, unsigned long extent, const char
*name);
void release_region(unsigned long from, unsigned long extent);
注册和释放 I/O 端口的函数。
int check_mem_region (unsigned long start, unsigned long extent);
struct resource *request_mem_region (unsigned long start, unsigned long extent, const
char *name);
void release_mem_region (unsigned long start, unsigned long extent);
注册和释放 I/O 内存区域的宏。
/proc/ksyms
公用内核符号表。
/proc/ioports
系统中安装的设备所占用的 I/O 端口列表。
/proc/iomem
已分配内存区域的列表。