Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1478037
  • 博文数量: 842
  • 博客积分: 12411
  • 博客等级: 上将
  • 技术积分: 5772
  • 用 户 组: 普通用户
  • 注册时间: 2011-06-14 14:43
文章分类

全部博文(842)

文章存档

2013年(157)

2012年(685)

分类: LINUX

2012-03-05 13:53:52

 

主题: linux内核模块的程序结构--模块加载函数(必须),模块卸载函数(必须)

,模块许可证声明(必须),模块参数(可选),模块导出符号(可选)

,模块作者的等信息声明(可选)

 

一个linux内核模块主要由以下几个部分组成。

1、模块加载函数"module_init()来指定"(必须)

   当通过insmodmodprobe

命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作?

?

linux模块加载函数一般以 __init表示声明。典型声明如下::

static int __init  initialization_function(void)

{

       

}

module_init(initialization_function);

    模块加载函数必须使用module_init(函数名)

的形式被指定。它返回整型值,若初始化成功,应返回0

,而初始化失败时,应返回错误编码。在linux内核中,错误编码是一个负值,在

errno.h>中定义,包括-ENODEV-ENOMEM

之类的符号值。返回相应的错误编码是种非常好的习惯,只有这样,应用程序才能利用

perror等方法把他们转换成有意义的错误信息字符串。

    2.6内核中,可以使用“request_module(const char *fmt,...)

函数”加载内核模块(注意:前面加载模块都是通过insmodmodprobe

来实现的),驱动开发人员可以通过调用::

request_module(module_name);

request_module("char-major-%d-%d",MAJOR(dev),MINOR(dev));

来加载其他内核模块。

   linux内核中,所有表示为__init的函数在连接的时候放在.init.text

这个区段内,此外,所有的__init函数在段.initcall.init

中还保存了一份函数指针,在初始化时,内核会通过这些指针调用这些__init

函数,并在初始化完成后释放init区段(.init.text,.initcall.init)

////////////////////////////////////////////////////////////////////

2、模块卸载函数"module_exit()来指定"(必须)

   当通过rmmodmodprobe -r

命令卸载内核模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功?

?

   linux内核模块于在函数一般以__exit表示说明,典型的模块卸载函数的形式如下::

static void __exit  cleanup_function(void)

{

         

}

module_exit(cleanup_function);

   模块卸载函数在模块卸载的时候执行,不返回任何值,必须以"module_exit(函数名)

"的形式来指定。

通常来说,模块卸载函数要完成与模块加载函数相反的功能,如下::

1>若模块加载函数注册了XXX,则模块卸载函数应该注销XXX

2>若模块记载函数的动态申请了内存,则模块函数应该释放该该内存。

3>若模块加载函数申请了硬件资源(中断,DMA通道、I/O端口和I/O内存等)

的占用,则模块卸载函数应该释放这些硬件资源。

4>模块加载函数一般用来开启硬件,模块卸载函数一般要关闭硬件。

 

__init一样,__exit也可以使用对应函数在运行完成后自动回收内存。实际上,__init

__exit都是宏,

分别定义为::

#define __init __attribute__((__section__(".init.text")))

#ifdef MODULE

#define __exit __attribute__((__section__(".exit.text")))

#else

#define __exit    / __attribute__used____attribute((__section__(".exit.text")))

#endif

/////////////////////////////////////////////////////////////////////

3、模块许可证声明"MODULE_LICENSE("Daul BSD/GPL")"(必须)

   模块许可证(LICENSE)声明描述内核模块的许可权限,如果不声明LICENSE

,模块被加载时,将收到内核被污染(kernel tainted)的警告。

   linux2.6内核中,可接受的LICENSE包括"GPL""GPL v2""GPL and additional

rights""Dual BSD/GPL""Dual MPL/GPL""Proprietary"

 

大多数情况下,内核模块应遵循GPL兼容许可权。linux2.6内核模块中最常见的是以

MODULE_LICENSE("Dual BSD/GPL")语句声明模块采用BSD/GPLLICENSE.

 

//////////////////////////////////////////////////////////////////////

4、模块参数(可选)

“模块参数”是“模块被加载的时候可以被传递给模块的值”,它本身对应模块内部的“?

?勘淞俊薄?

   我们可以使用"module_param(参数名,参数类型,/写权限)

"为模块定义一个参数,例如::下列代码定义了一个整型参数和一个字符指针参数。

static char *book_name="深入浅出linux设备驱动";

static int num = 4000;

module_param(num,int,S_IRUGO);

module_param(book_name,charp,S_IRGUO);

 

在装载内核模块时,用户可以向内核模块传递参数,

形式为"sudo insmod/modprobe 模块名(例如linux.ko)  

参数名=参数值",若果不传递,参数将使用模块内定义的默认值。

 

向内核模块传递参数时,参数的类型可以是byte(字节),short(短整型)ushort(

无符号短整型),int,uint(无符号int),long,ulong(无符号long)charp(字符指针)

boolinvbool(布尔的反),在模块被编译时会将module_param

中声明的类型与变量定义的类型进行比较,判断是否一致。

 

   模块被加载后,在/sys/module

目录下将出现以此模块名命名的目录。当"参数读/写权限"0时,表示此“参数不存在

sysfs文件系统下对应的文件节点”,如果此模块存在"参数读/写权限"不为0

的命令行参数,在此模块的目录下将出现parameters

目录,包含一系列“以参数名命名的文件节点”。同时,这些文件的权限就是通过传入

module_param()"参数读/写权限",而文件的内容为参数的值。

 

   除此之外,模块也可以拥有参数数组,形式为"module_param_array(数组名,数组类型

,数组长,参数读/写权限)",在2.6.0~2.6.10版本,需将数组常变量名赋给"数组长",2

.6.10

版本开始,需将数组长变量的指针赋给"数组长",当不需要保存实际输入的数组元数个数?

?梢陨柚?数组长"NULL

 

   运行insmodmodprobe命令时,应使用逗号分割输入的数组元素。

例如::

#include

#include

MODULE_LICENSE("Dual BSD/GPL");

  

static char *book_name="Dissecting Linux Device Driber";

static int num=4000;

 

static int __init book_init(void)

{

    printk(KERN_INFO"book name :%s/n",book_name);

    printk(KERN_INFO"book num :%s/n",num);

    return 0;

}

 

static void __exit book_exit(void)

{

    printk(KERN_INFO"Book module exit/n");

}

 

module_init(book_init);

module_exit(book_exit);

 

module_param(num,int,S_IRUGO);

module_param(book_name,charp,S_IRUGO);

 

MODULE_AUTHOR("chenbaihu");

MODULE_VERSION("v1.0");

MODULE_DESCRIPTION("A simple Module for testing module params");

编译该模块,Makefile::

obj-m := module_param.o

kernel_path=/usr/src/kernels/2.6.29.6-217.2.16.fc11.i686.PAE 

//内核路径

all:

    make -C $(kernel_path)  M=$(PWD) modules

clean:

    make -C $(kernel_path)  M=$(PWD) clean

然后,运行make命令,进行编译.生成module_param.ko文件.

加载该模块.

第一种方案::

    sudo insmod module_param.ko”命令时,运行结果为::

book name :Dissecting Linux Device Driber

book num :4000

第二种方案::

   sudo insmod module_param.ko num=5000”命令时,运行结果为::

book name :Dissecting Linux Device Driber

book num :5000   //参数传入了。

进入/sys/module/module_param/下,输入tree命令::

.

|-- holders

|-- initstate

|-- notes

|-- parameters

|   |-- book_name     //模块参数文件

|   `-- num           //模块参数文件

|-- refcnt

|-- sections

|   `-- __param

|-- srcversion

`-- version

//////////////////////////////////////////////////////////////////////

5、模块导出符号(可选)

内核模块可以导出符号(symbol,对应与函数或变量)

,这样其他模块可以使用本模块中的变量和函数。

 

     linux2.6"/proc/kallsyms

"文件对应这内核符号表,它记录了符号以及符号符号所在的内存地址。

模块可以使用如下宏导出符号到内核符号表::

    EXPORT_SYMBOL(符号名);

    EXPORT_SYMBOL_GPL(符合名);    //只是用于GPL许可权模块。

导出的符合将可以被其他模块使用,使用前声明以下既可以。

 

内核模块中的符号导出(例子)::

#include

#include

MODULE_LICENSE("Daul BSD/GPL");

 

int add_integar(int a,int b)

{

    return a+b;

}

int sub_integer(int a,int b)

{

    return a-b;

}

 

EXPORT_SYMBOL(add_integar);    //导出函数

EXPORT_SYMBOL(sub_integer);    //导出函数

编译后,sudo insmod export_symbol.ko将该模块加入内核。

"/proc/kallsyms"中可以找到add_integae/sub_integer相关信息。

使用"cat /proc/kallsyms|grep integar"命令,就可以看到下面的结果::

f99f8048 r __ksymtab_add_integar    [export_symbol]

f99f805c r __kstrtab_add_integar    [export_symbol]

f99f8000 T add_integar    [export_symbol]

 

6、模块作者等信息(可选)

MODULE_AUTOR("作者信息");

MODULE_DESCRIPTION("模块描述信息");

MODULE_VERSION("版本信息");

MODULE_ALIAS("别名信息");

MODULE_DEVICE_TABLE("设备表信息");

对于USB,PCI等设备驱动,通常会创建一个MODULE_DEVICE_TABLE,

表示驱动所支持的设备列表。

阅读(658) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~