linux内核模块

1.linux内核的整体机构已经能够非常庞大,而其包含的组件也非常多,我们怎么把需要的部分都包含的内核中呢?

一种方法就是把所有需要的功能都编译到linux kernel,这会导致两个问题,一是生成的内核会很大,二是如果我们要在现有的内核中新增或者删除功能,将不得不重新编译内核。

而linux提供一种机制,这种机制叫做模块(module).模块具有这样的特点:

1.模块本身不被编译入内核镜像,从而控制的内核的大小。

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


这里我给一个简单的实现,创建了一个内核模块hello

#include <linux/init.h>
#include <linux/module.h>


static int __initdata hello_init(void)
{
printk(KERN_INFO "Hello World init\n");
return 0;
}


static void __exitdata hello_exit(void)
{
printk(KERN_INFO"Hello world exit\n");

}


module_init(hello_init);
module_exit(hello_exit);


MODULE_LICENSE("GPL");
MODULE_ALIAS("a hello module");
MODULE_AUTHOR("Yang");
MODULE_DESCRIPTION("A simple hello world modules");


这个简单的内核模块包含内核模块加载函数,卸载函数和对GPL许可权限的声明以及一些描述信息,比如模块的作者,模块的功能描述等

内核模块用于输出的的函数是内核空间的printk而非空户空间的printf,printk的用法和printf基本相似,但printk是可以定义输出级别的。



1.2 linux内核模块程序结构

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

(1)模块加载函数(一般都需要)

当前通过insmod或者modprobe命令加载内核模块时,模块的加载函数自动被内核执行,完成本模块的相关初始化工作

(2)模块卸载函数(一般都需要)

当通过rmmod命令卸载某模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。

(3)模块的许可证声明(必须)

许可证声明描述内核的许可权限,如果不声明LICENSE,模块被加载时,将收到内核被污染的警告。

(4)模块参数(可选)

模块参数是模块被加载的时候可以被传递给它的值,它本身对应的模块内部的全局变量。

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

啮合模块可以导出符号(symol,对应于函数或者变量),这样其他的模块可以使用本地模块的变量或者函数

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



1.3模块加载函数

linux内核模块加载函数一般以__init标识声明,如下

static init __init hello_init(void)

{

}

module_init(hello_init);

模块记载函数必须以module_init(函数名)的形式被制定,它返回整形值,若初始化成功应返回0,,而在初始化失败时,应该返回错误的编码,在linux内核里错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV,-ENOMEM之类的符号值,总是返回相应的错误编码是中非常好的吸光,因为只有这样,用户程序才可以利用perror等方法把它们转换成有意义的错误信息字符串。

在内核中,可以使用request_module(const char *fmt,...)函数加载内核模块

request_module(mod...)

或者

__request_module(bool wait,const char * fmt,...)

这种灵活的方式加载其他的内核模块

在linux中,所有标识符为__init的函数在连接的时候都放在.init.text这个区段内,此外所有的__init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数阵阵叼哦那个这些__init函数,并在初始化完成后,释放init区段(包括.init.text, .initcall.init等)。



1.4模块卸载函数

linux内核模块卸载函数一般以__exit标识符声明

static void __exit hello_exit(void)

{

}

module_exit(hello_exit);

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

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

1.若模块记载函数注册了xxx,则模块卸载函数应该注销xxx。

2.若模块加载函数动态申请了内存,则模块卸载函数应该释放该内存。

3.若模块加载函数申请了硬件资源(中断,DMA通道,I/O端口和I/O内存等)的占用,则模块卸载函数应该释放这些硬件资源。

4.若模块加载函数开启了硬件,则卸载函数中一般要关闭之。

和__init一样,__exit也可以使对应函数在运行完成后自动回收内存。实际上,__init和__exit都是宏,其定义分别为:

#define __init __section(.init.text) __cold notrace

#ifndef __section
# define __section(S) __attribute__ ((__section__(#S)))
#endif


#define __exit          __section(.exit.text) __exitused __cold notrace

#ifndef __section
# define __section(S) __attribute__ ((__section__(#S)))
#endif

数据也可以被定义为__initdata 和__exitdata,这两个宏分别为

#define __initdata __section(.init.data)

#define __exitdata __section(.exit.data)


1.5模块参数

可以用module_param(参数名,参数类型,参数读/写权限)为模块定义一个参数,例如:

static char *module_name = "dissecting linux device driver";

static int num = 4000;

module_param(num,int,S_IRUGO);

module_param(module_name,charp,S_IRUGO);

在装载内核模块时,用户可以向模块传递参数,形式为insmode(或modprobe)模块名 参数名=参数值,如果不传递,参数将使用模块内定义的缺省值。

参数类型可以是以byte,short,ushort,int,uint,long,ulong,charp(字符指针),bool或invbool,在模块被编译时将module_param中声明的类型与变量定义的类型进行比较,判断是否一致。

模块被加载后,在sys/module/目录下将出现以次模块名命名的目录,当参数读/写权限为0时,表示此参数不存在sysfs文件系统下对应的文件节点,如果此模块存在参数读/写q权限为0的命令行参数,在此模块的目录下还将出现paramteters目录,包含一系列以参数命名的文件节诶点,这些文件的权限值就是传入module_params()的参数读/写权限,而文件的内容为参数的值。

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

下面是一个带参数的内核模块实例

#include <linux/init.h>
#include <linux/module.h>


static char *list_name = "dissecting linux device drvier";
static int num = 4000;


static int __init hello_init(void)
{
printk(KERN_INFO "list_name :%s\n",list_name);
printk(KERN_INFO "list_name :%d\n",num);
return 0;
}


static void __exitdata hello_exit(void)
{
printk(KERN_INFO"Hello world exit\n");

}


module_init(hello_init);
module_exit(hello_exit);
module_param(num,int,S_IRUGO);
module_param(list_name,charp,S_IRUGO);


MODULE_LICENSE("GPL");
MODULE_ALIAS("a hello module");
MODULE_AUTHOR("Yang");
MODULE_DESCRIPTION("A simple hello world modules");


1.6导出符号

/proc/kallsync文件对应着内核的符号表,它记录了符号以及符号所在的内存地址

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

EXPORT_SYMBOL(符号名);

EXPORT_SYMBOL_GPL(符号名);

导出符号将可以被其他的模块使用,使用前声明一下即可,EXPORT_SYMBOL_GPL()只适用于包含GPL许可权的模块,下面是例子:

#include <linux/init.h>

#incldue <linux/module.h>

MODULE_LICENSE("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);


1.7模块声明与描述

在linux内核模块中,我们可以用MODULE_AUTHOR,MODULE_DESCRIPTION,MODULE_VERSION,MODULE_DEVICE_TABLE,MODULE_ALIAS分别申明模块的作者,描述,版本,设备表和别名,例如

MODULE_AUTHOR();

MODULE_DESCRIPTION();

MODULE_VERSION();

MODULE_DEVICE_TABLE();

MODULE_ALIAS();

对于usb,pci等设备驱动,通常会创建一个MODUEL_DEVICE_TABLE,表明该驱动模块所支持的设备,代码如下:

/*对应此驱动的设备列表*/

static strct usb_device_id skel_table[]={

{USB_DEVICE(USB_SKEL_VENDOR_ID,USB_SKEL_PRODUCT_ID)}

{},/*表结束*/

};

MODUEL_DEVICE_TABLE(usb ,skel_table);


1.8模块的使用计数

模块计数管理接口try_module_get(&module)和module_put(&module)

try_module_get函数用于增加模块使用计数,若返回0表示调用失败,希望使用的模块没有被加载或正在被卸载中

module_put函数用于减少模块使用计数





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值