linux驱动模块 简单示例

1、 linux 内核模块简介

     linux内核整体结构非常庞大,其包含的组件也非常多。我们怎么把需要的部分都包含在内核中呢?

一种办法是把所有的需要的功能都编译到内核中。这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或删除功能,不得不重新编译内核,工作效率会非常的低,同时如果编译的模块不是很完善,很有可能会造成内核崩溃。 

    linux提供了另一种机制来解决这个问题,这种集中被称为模块,可以实现编译出的内核本身并不含有所有功能,而在这些功能需要被使用的时候,其对应的代码可以被动态的加载到内核中。

2、 模块特点:

         1)模块本身并不被编译入内核,从而控制了内核的大小。

         2)模块一旦被加载,他就和内核中的其他部分完全一样。

3、 最简单的模块

1) 下面是一个最简单的内核模块例子:

表 1 “hello world” 2.6内核模块

#include <linux/init.h>         /* printk() */

#include <linux/module.h>     /* __init __exit */

static int  __init  hello_init(void)      

               /*模块加载函数,通过insmod命令加载模块时,被自动执行*/

{

  printk(KERN_INFO " Hello World enter\n");

  return 0;

}

static void  __exit  hello_exit(void)     

              /*模块卸载函数,当通过rmmod命令卸载时,会被自动执行*/

{

  printk(KERN_INFO " Hello World exit\n ");

}

module_init(hello_init);

module_exit(hello_exit);

MODULE_AUTHOR("dengwei");       /*模块作者,可选*/

MODULE_LICENSE("Dual BSD/GPL"); /*模块许可证明,描述内核模块的许可权限,必须*/

MODULE_DESCRIPTION("A simple Hello World Module"); /*模块说明,可选*/

MODULE_ALIAS("a simplest module");                  /*模块说明,可选*/

2) 以下是编译上述模块所需的编写的makefile

表 2 2.6内核模块编译makefile

obj-m :=hello.o           //目标文件

#module-objs := file1.o file.o      //当模块有多个文件组成时,添加本句

KDIR :=/usr/src/linux            //内核路径

PWD := $(shell pwd)            //模块源文件路径

all:

$(MAKE)  -C  $(KDIR)  SUBDIRS=$(PWD)  modules

@rm -rf *.mod.*

@rm -rf .*.cmd

@rm -rf *.o

@rm -rf Module.*

clean:

rm -rf *.ko

最终会编译得到:hello.ko文件

使用insmod hello.ko将模块插入内核,然后使用dmesg 即可看到输出提示信息。

3) 模块操作函数

4、 linux内核模块的程序结构

1) 模块加载函数:

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

static int __init myModule_init(void)

{

/* Module init code */

PRINTK("myModule_init\n");

return 0;

}

module_init(myModule_init);

模块加载函数必须以“module_init(函数名)”的形式被指定。它返回整形值,若初始化成功,应返回0,初始化失败返回负数。

linix2.6 内核中,可以使用:

request_module(module_name);

request_module(“char-marjor-%d-%d”);

来加载其它内核模块。

2)  模块卸载函数

典型的模块卸载函数形式如下:

static void __exit myModule_exit(void)

{

/* Module exit code */

PRINTK("myModule_exit\n");

return;

}

module_exit(myModule_exit);

模块卸载函数在模块卸载的时候执行,不返回任何值,需用”module_exit(函数名)”的形式被指定。

卸载模块完成与加载函数相反的功能:

若加载函数注册了XXX,则卸载函数应当注销XXX

若加载函数申请了内存空间,则卸载函数应当释放相应的内存空间

若加载函数申请了某些硬件资源(中断、DMAI/0端口、I/O内存等),则卸载函数应当释放相应的硬件资源

若加载函数开启了硬件,则卸载函数应当关闭硬件。

其中__init __exit 为系统提供的两种宏,表示其所修饰的函数在调用完成后会自动回收内存。

3) 模块参数

我们可以利用module_param(参数名、参数类型、参数读写属性为模块定义一个参数,例如:

static char *string_test = “this is a test”;

static num_test = 1000;  

module_param (num_test,int,S_IRUGO);

module_param (steing_test,charp,S_ITUGO);

在装载模块时,用户可以给模块传递参数,形式为:”insmod 模块名 参数名=参数值”,如果不传递,则参数使用默认的参数值。

参数的类型可以是:byte,short,ushort,int,uint,long,ulong,charp,bool.

模块被加载后,在sys/module/下会出现以此模块命名的目录。

当读写权限为零时:表示此参数不存在sysfs文件系统下的文件节点

当读写权限不为零时:此模块的目录下会存在parameters目录,包含一系列以参数名命名的文件节点,这些文件爱节点的权限值就是传入module_param()的“参数读/写权限“,而该文件的内容为参数的值。

除此之外,模块也可以拥有参数数组,形式为:”module_param_array(数组名、数组类型、数组长、参数读写权限等)”,当不需要保存实际的输入的数组元素的个数时,可以设置“数组长“为0

运行lsmod时,使用都好分隔输入的数组元素。

下面是一个实际的例子,来说明模块传参的过程。

/*==================================================================

    A simple kernel module: "hello world"

===================================================================*/

#include <linux/init.h>

#include <linux/module.h>

static char *string_test="this is a test";

static int num_test=1000;

static int __init hello_init(void)

{

  printk(KERN_INFO "Hello World enter\n");

  printk(KERN_INFO "the test string is %s\n",string_test);

  printk(KERN_INFO "the test num is %d\n",num_test);

  return 0;

}

static void __exit hello_exit(void)

{

  printk(KERN_INFO " Hello World exit\n ");

}

module_param(num_test,int,S_IRUGO);

module_param(string_test,charp,S_IRUGO);

module_init(hello_init);

module_exit(hello_exit);

MODULE_AUTHOR("dengwei");

MODULE_LICENSE("Dual BSD/GPL");

MODULE_DESCRIPTION("A simple Hello World Module");

MODULE_ALIAS("a simplest module");

当执行 insmod hello_param.ko 时,执行dmesg 查看内核输出信息:

Hello World enter

the test string is: this is a test

the test num is :1000

当执行 insmod  heello_param.ko num_test=2000 string_test=”edit by dengwei”,执行dmesg查看内核输出信息:

Hello World enter

the test string is: edit by dengwei

the test num is :2000

4) 导出符号

Linix 2.6 内核的“/proc/kallsyms“文件对应内核符号表,它记录了符号以及符号所在的内存地址,模块可以使用下列宏导到内核符号表中。

EXPORT_SYMBOL(符号名);       任意模块均可

EXPORT_SYMBOL_GPL(符号名);  只使用于包含GPL许可权的模块

导出的符号可以被其它模块使用,使用前声明一下即可。

下面给出一个简单的例子:将add sub符号导出到内核符号表中,这样其它的模块就可以利用其中的函数

/*=================================================================

    A simple kernel module to introduce export symbol

=================================================================*/

#include <linux/init.h>                                

#include <linux/module.h>                                

MODULE_LICENSE("Dual BSD/GPL");       

int add_integar(int a,int b)                                

{                                

return a+b;                             

                               

int sub_integar(int a,int b)   

{                                

return a-b;                             

}                            

EXPORT_SYMBOL(add_integar);

EXPORT_SYMBOL(sub_integar);

执行 cat /proc/kallsyms | grep integar 即可找到以下信息,表示模块确实被加载到内核表中。

f88c9008 r __ksymtab_sub_integar        [export_symb]

f88c9020 r __kstrtab_sub_integar         [export_symb]

f88c9018 r __kcrctab_sub_integar         [export_symb]

f88c9010 r __ksymtab_add_integar        [export_symb]

f88c902c r __kstrtab_add_integar          [export_symb]

f88c901c r __kcrctab_add_integar         [export_symb]

f88c9000 T add_integar                [export_symb]

f88c9004 T sub_integar                [export_symb]

13db98c9 a __crc_sub_integar           [export_symb]

e1626dee a __crc_add_integar           [export_symb]

5) 模块声明与描述

linux内核模块中,我们可以用MODULE_AUTHORMODULE_DESCRIPTIONMODULE_VERSIONMODULE_TABLEMODULE_ALIA,分别描述模块的作者、描述、版本、设备表号、别名等。

MODULE_AUTHOR("dengwei");

MODULE_LICENSE("Dual BSD/GPL");

MODULE_DESCRIPTION("A simple Hello World Module");

MODULE_ALIAS("a simplest module");

6) 模块的使用计数

Linux2.4内核中,模块使用MOD_INC_USE_COUNTMOD_DEC_USE_COUNT宏来管理自己被使用的次数。

Linux2.6内核中,提供了模块计数管理接口 try_module_get(&module) 和 module_put(&module),从而取代linux2.4内核中的模块使用计数管理宏。

Int try_module_get( struct *module);

该函数用于增加模块使用次数,若返回值为0,表示调用失败,希望使用的模块没有被加载或正在被加载中。

Void module_put(struct * module);

用于减少模块使用次数。

这两个函数的引入与使用与linux2.6下设备模型密切相关。Linux2.6内核为不同类型的设备定义了struct module *owner 域,用来指向管理此设备的模块。当开始使用某个设备时,使用try_module_get 函数去增加管理此设备的ower模块的使用次数,不用时调用  module_put(&module) 函数减少使用次数。这样,当设备在使用时,管理设备的模块不能被卸载,只有当设备不再不使用时,相应的模块才可以卸载。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值