《1》 module_init ( xxx ) 模块在装载到内核时调用,module_exit( xxx ) 模块在被移除时调用。
《2》 内核API中看到具有两个下划线的前缀(__)的函数名称,具有这种名称的函数通常是接口的底层组件,应谨慎使用。
《3》 在构造内核模块之前,应确保具备了正确版本的编译器,模块工具和其他必要的工具。内核文档目录中的Documentation/Changes文件列出了需要的工具版本。
《4》 modprobe也用来将模块装入到内核中。它和insmod的区别在于,它会考虑要装载是否引用了一些当前内核不存在的符号。如果有这类引用,modprobe会在当前模块搜索路径中查找定义了这些符号的其他模块。如果modprobe找到了这些模块(即要装载的模块所依赖的模块),它会同时将这些模块装载到内核。在加载模块失败的时候,可以通过查看/var/log/messages或者系统配置使用的文件。
《5》 针对内核版本的查看,在linux/version.h中相关的定义,这个头文件自动包含于linux/module.h,并定义了下面的宏:
UTS_RELEASE
宏UTS_RELEASE扩展至为一个描述内核版本的字符串,例如 “2.6.10”。
LINUX_VERSION_CODE
宏LINUX_VERSION_CODE扩展为内核版本的二进制表示,版本发行号中的每一部分对应一个字节。例如,2.6.10对应的LINUX_VERSION_CODE是132618(即0x02060a)。使用这个宏,我们很容易确定正在使用的内核版本.
KERNEL_VERSION(major , minor , release)
宏KERNEL_VERSION以组成版本号的三部分(三个整数)为参数,创建整数的版本号。例如, KERNEL_VERSION(2 ,6 ,10)扩展为132618.这个宏在我们需要将当前版本和一个已知的检查点比较时非常有用。
《6》 如果一个每模块需要向其他模块导出符号,则应该使用下面的宏。EXPORT_SYMBOL(name) ; EXPORT_SYMBOL_GPL(name) ; _GPL版本使得要导出的模块只能被GPL许可证下的模块使用。
《7》 所有模块代码都需要包含这两行代码:
#include <linux/module.h> 包含有可装载模块需要的大量符号和函数定义。
#include <linux/init.h> 包含init.h的目的是指定初始化和清除函数。
大部分模块还包括moduleparam.h头文件,这样可以在装载模块时向模块传递参数。
《8》 虽然不是严格要求模块代码使用许可证,但还是需要考虑使用的,例如:MODULE_LECENSE(" GPL") ; 内核可以识别的许可证有“GPL”(任一版本的GNU通用公关许可证),“GPL v2”(GPL版本2), “GPL and additional rights ”(GPL及附加权利) “,” Dual BSD/GPL (GPL双重许可证)“,”Dual MPL/GPL(MPL/GPL双重许可证)“ 以及 ”Proprietary(专有)的“,没有显示标记为上述可识别许可证,则会被假定是专有的,而内核转载这种内核就会被"污染"。
《9》 模块中可以包含的其他描述性定义包括MODULE_AUTHOR(描述模块作者),MODULE_DESCRIPTION(用来说明模块用途的简短描述),MODULE_VERSION(代码修订号;有关版本字符串的创建惯例,参考<linux/module.h>中的注释),MODULE_ALIAS(模块的别名)以及MODULE_DEVICE_TABLE(告诉用户空间模块所支持的设备)。
《10》模块初始化函数通常如下:
/*__=2个_*/
static int __init initialization_function(void) _ _ init 给内核暗示,在模块被装载之后,模块装载器就会将初始化函数扔掉,释放内存。
{
/* 初始化代码*/
}
module_init(initialization_function) ;
static void __exit cleanup_function(void)
{
/* 这里是清除代码*/
}
module_exit(cleanup_function); //帮助内核找到模块的清除函数是必须的
清除函数没有返回值, _ _exit 修饰词标记该代码仅用于模块卸载, 这样的模块只能在卸载或者关闭时被调用,其他的任何用法都是错误的。
《11》当我们在内核中注册设施时,要时刻铭记注册可能会失败,当注册失败的时候,我们要撤销之前的任何注册工作。
示例:
int __init my_init_function (void)
{
int err;
err = register_this(ptr1 , "skull");
if(err) goto fail_this;
err = register_that(ptr2 , "skull");
if(err) goto fail_that;
err = register_those(ptr3 , "skull");
if(err) goto fail_those;
return 0 ; /*success*/
fail_those: unregister_that(ptr2 , "skull");
fail_that : unregister_this(ptr1 , "skull");
fail_this : return err;
}
不支持goto的使用的人,就是记录任何成功注册的措施,然后再出错的时候调用模块清除函数。
显然,清除函数需要撤销初始化函数所注册的所有设施,并且在习惯上(但不是必须的),以相反于注册的顺序撤销设施:
void __exit my_cleanup_function(void)
{
unregister_those(ptr3 , “skull”);
unregister_that(ptr2 , “skull”);
unregister_this(ptr1, skull);
return ;
}
《12》可以再insmod 或 modprobe 命令加载模块的时候传人参数,参数必须使用module_param宏来声明,这个宏在moduleparam.h中定义。这个宏必须是放在任何函数之外, 通常是源文件的头部。
主体的部分就是: static char *whom = “world” ;
static int howmany = 1 ;
module_param (howmany , int , S_IRUGO) ;
module_param (whom , charp , S_IRUGO) ;
使用的命令比如: insmod hellop howmany=10 whom=“Mom”
内核所能支持的模块参数类型如下:
bool
invbool /* 布尔值(取true 或 false ), 关联的变量应该是 int 型。 invbool 类型反转其值, 也就是说, true 值变成 false ,而false 变成 true 。*/
charp /*字符指针值。内核会为用户提供的字符串分配内存, 并相应设置指针。*/
int long short uint ulong ushort 具有不同长度的基本整数值。以u开头的类型用于无符号值。
模块装载器也支持数组参数, 要声明数组参数需要使用下面的宏:
module_param_array(name , type , num , perm); /*name 数组名称 type 数组类型 num 为用户提供的值得个数 perm 常见的访问许可值。具体的细节请参阅moduleparam.h 文件*/ module_param 中最后一个参数是访问许可值, 我们应使用<linux / stat . h> 中存在的定义。这个值用来控制谁能够访问sysfs中对模块参数的表述。如果perm被设置成0 ,就不会有对应的sysfs入口项 ; 否则参数会在 /sys/module 中出现,并设置为给定的访问许可。如果对参数使用S_IRUGO , 则任何人均可读取该参数, 但不能修改; S_IRUGO | S_IWUSR 允许root用户修改该参数。