Linux设备驱动开发基础---Linux内核模块

一、Linux内核模块的程序结构

模块加载函数(必须) 

当通过insmod或 modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。 
模块卸载函数(必须) 。 
当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。 
模块许可证声明(必须) 。 
模块许可证(LICENSE)声明描述内核模块的许可权限,如果不声明 LICENSE,
模块被加载时,将收到内核被污染 (kernel tainted)的警告。 
在 Linux  2.6 内核中,可接受的 LICENSE 包括“GPL” 、 “GPL  v2” 、 “GPL  and additional rights” 、 “Dual BSD/GPL” 、 “Dual MPL/GPL”和“Proprietary” 。 大多数情况下,内核模块应遵循 GPL 兼容许可权。Linux  2.6 内核模块最常见的是以 MODULE_LICENSE(  "Dual  BSD/GPL" )语句声明模块采用 BSD/GPL 双LICENSE。 
模块参数(可选) 。 
模块参数是模块被加载的时候可以被传递给它的值,它本身对应模块内部的全局变量。 
模块导出符号(可选) 。 
内核模块可以导出符号(symbol,对应于函数或变量) ,这样其他模块可以使用本模块中的变量或函数。 
模块作者等信息声明(可选) 。 

1、模块加载函数

Linux内核模块加载函数一般以_ _init标识声明,典型的模块加载函数的形式如下:

static int _ _init initialization_function(void) 
    {  
       /* 初始化代码 */ 
    } 
module_init(initialization_function); 

模块加载函数必须以“module_init(函数名)”的形式被指定。它返回整型值,若初始化成功,应返回0。而在初始化失败时,应该返回错误编码。

在Linux 2.6 内核中,可以使用request_module(const  char *fmt, …)函数加载内核
模块,驱动开发人员可以通过调用 

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、模块卸载函数

Linux内核模块卸载函数一般以_ _exit标识声明, 典型的模块卸载函数的形式如下:

static void _ _exit cleanup_function(void) 
  { 
           /* 释放代码 */ 
   } 
module_exit(cleanup_function); 

模块卸载函数在模块卸载的时候执行,不返回任何值,必须以“module_exit(函数名)”的形式来指定。

 和_ _init一样,_ _exit也可以使对应函数在运行完成后自动回收内存。实际上,_ _init和_ _exit都是宏,数据也可以被定义为_initdata 和_exitdata。 

3、模块参数

我们可以用“module_param(参数名,参数类型,参数读/写权限)”为模块定义一个参数,例如下列代码定义了一个整型参数和一个字符指针参数: 

static char *book_name = "Linux Device Drivers, Third Edition"; 
static int num = 4000; 
module_param(num, int, S_IRUGO); 
module_param(book_name, charp, S_IRUGO); 

在装载内核模块时,用户可以向模块传递参数,形式为“insmode(或 modprobe)
模块名  参数名=参数值” ,如果不传递,参数将使用模块内定义的默认值。 
参数类型可以是byte、short、ushort、int、uint、long、ulong、charp(字符指针) 、bool 或 invbool(布尔的反) ,在模块被编译时会将 module_param 中声明的类型与变量定义的类型进行比较,判断是否一致。 

除此之外,模块也可以拥有参数数组,形式为“module_param_array(数组名,数组类型,数组长,参数读/写权限)” 。

4、导出符号

Linux 2.6的“/proc/kallsyms”文件对应着内核符号表,它记录了符号以及符号所在的内存地址。 
模块可以使用如下宏导出符号到内核符号表: 
EXPORT_SYMBOL(符号名); 
EXPORT_SYMBOL_GPL(符号名); 
导 出的符号将可以被其他模块使用 , 使用前声明一下即可 。

5、模块声明与描述

在Linux内核模块中, 我们可以用MODULE_AUTHOR、 MODULE_DESCRIPTION、MODULE_ VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分别声明模块的
作者、描述、版本、设备表和别名,例如: 

MODULE_AUTHOR(author); 
MODULE_DESCRIPTION(description); 
MODULE_VERSION(version_string); 
MODULE_DEVICE_TABLE(table_info); 
MODULE_ALIAS(alternate_name); 

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

6、模块的编译 

我们以hello.c为例的模板编写一个简单的 Makefile,如下所示: 
obj-m := hello.o 
并使用如下命令编译Hello World模块,如下所示: 
make -C /usr/src/linux-2.6.15.5/ M=/driver_study/ modules 
如果当前处于模块所在的目录,则以下命令与上述命令同等: 
make –C /usr/src/linux-2.6.15.5 M=$(pwd) modules 
其中-C后指定的是Linux内核源代码的目录, 而M=后指定的是hello.c和Makefile所在的目录。 

如果一个模块包括多个.c 文件(如 file1.c、file2.c) ,则应该以如下方式编写
Makefile: 
obj-m := modulename.o 
module-objs := file1.o file2.o  

7、模块与 GPL

 对于自己编写的驱动等内核代码,如果不编译为模块则无法绕开GPL,编译为模块后企业在产品中使用模块,则公司对外不再需要提供对应的源代码,为了使公司产品所使用的Linux 操作系统支持模块,需要完成如下工作。 
在内核编译时应该选上“Enable  loadable module  support” ,嵌入式产品一般不需要动态卸载模块,所以“可以卸载模块”不用选,当然选了也没关系。如果有项目被选择“M” ,则编译时除了make bzImage以外,也要 make modules。  

将我们编译的内核模块.ko文件放置在目标文件系统的相关目录中。 

在使用中用户可使用 insmod命令手动加载模块,如 insmod xxx.ko。 但是一般而言,产品在启动过程中应该加载模块,在嵌入式 Linux的启动过程中,加载企业自己的模块的最简单的方法是修改启动过程的rc脚本,增加insmod  /.../xxx.ko 这样的命令。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值