目录
以下基于宋宝华老师的《Linux设备驱动开发详解》
示例代码
#include <linux/init.h>
#include <linux/module.h>
static int __init hello_init(void)
{
printk(KERN_INFO "Hello World enter\n");
return 0;
}
module_init(hello_init);
static void __exit hello_exit(void)
{
printk(KERN_INFO "Hello World exit\n");
}
module_exit(hello_exit);
MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A simple Hello World Module");
MODULE_ALIAS("a simplest module");
模块相关命令
- 加载模块:
insmod xxx.ko - 卸载模块:
rmmod xxx.ko - 查看所有已加载的模块以及模块间的依赖关系:
lsmod- 实质上是读取并分析文件:
/proc/modules - 已加载模块的信息也存在于目录下:
/sys/module
- 实质上是读取并分析文件:
- 更强大的命令:
modprobe- 该命令在加载某一个模块时,会同时加载该模块所依赖的其他模块,卸载同理。
- 加载:
modprobe xxx - 卸载:
modprobe -r xxx - 注意,这个命令似乎是从配置的指定路径下面去寻找的,不能手动的指定路径,所以没有
insmod灵活
- 加载:
- 模块之家的依赖关系存放在文件:
/lib/modules/<kernel-version>/modules.dep中- 由
depmod工具在整体编译内核时生成
- 由
- 获得模块的信息:
modinfo xxx.ko
- 该命令在加载某一个模块时,会同时加载该模块所依赖的其他模块,卸载同理。
Linux内核模块程序结构
- 模块加载函数
- 模块卸载函数
- 模块许可证声明
- 可接受的许可证(LICENSE)包括:
GPL、GPL V2、GPL and additional rights、Dial BSD/GPL、Dual MPL/GPL、Proprietary,是否可以采用非GPL是有争议的,大多数情况下声明为GPL v2
- 可接受的许可证(LICENSE)包括:
- 模块参数(可选)
- 模块导出符号(可选)
- 模块作者等信息声明(可选)
模块加载函数
通用示例如下
static int __init initialization_function(void)
{
}
module_init(initialization_function);
- 一般以
__init标识声明 - 以
module_init(函数名)的形式被指定。- 返回整型值,0代表成功,其他代表错误编码,错误编码应该为接近于0的负值,在
<linux/errno.h>中定义
- 返回整型值,0代表成功,其他代表错误编码,错误编码应该为接近于0的负值,在
- 在内核中,可以通过
request_module(const char* fmt,...)函数加载其他内核模块 - 所有标识为
__init的函数如果直接被编译进入内核,那么在连接时,就会放在.init.text这个区段内#define __init __attribute__((section__(".init.text")))- 相应的函数指针保存在:
.initcal.init中 - 初始化完成后,会释放init区段(包括了上面这两个)的内存
- 数据也可以被定义为
__initdata:主要针对于只是初始化阶段需要的数据。- 定义为
__initdata后,内核在初始化完毕后,也会释放它们所占用的内存
- 定义为
模块卸载函数
通用示例如下
static void __exit cleanup_function(void)
{
}
module_exit(cleanup_function);
- 模块卸载的时候执行,不返回任何值
- 必须以
module_exit(函数名)指定 - 如果被
__exit修饰的函数,在相关模块被编译进入了内核,那么被修饰的函数会被直接省略。 - 只有退出阶段采用的数据也可以用
__exitdata来修饰
模块参数
- 定义一个模块参数:
module_param(参数名,参数类型,参数读/写权限) - 在装载内核模块时,可以向模块传递参数:
insmod/modprobe 模块名 参数名=参数值- 不传递就使用默认值
- 如果模块是被编译进入了内核,也就是被内置,那么可以通过
bootloader在bootargs里面设置模块名.参数名=值
- 模块参数数组
- 定义一个模块参数数组:
module_param_array(数组名,数组类型,数组长,参数读/写权限)
- 定义一个模块参数数组:
- 模块加载后,
/sys/module下将出现对应的目录- 如果模块存在
参数读/写权限不为0的模块参数,那么会出现parameters目录,且该目录下包含参数读/写权限不为0的模块参数文件节点- 文件内容为参数的值
- 如果模块存在
- 逗号分隔输入的数组元素
导出符号(导出函数)
- 内核符号表(函数表)文件:
/proc/kallsyms- 该文件记录了符号以及符号所在的内存地址
- 导出函数到内核函数表中:
EXPORT_SYMBOL(符号名/函数名)EXPORT_SYMBOL_GPL(符号名/函数名):仅仅适用于包含GPL许可权的模块
模块声明与描述
- 声明模块的作者:
MODULE_AUTHOR() - 声明模块的描述:
MODULE_DESCRIPTION() - 声明模块的版本:
MODULE_VERSION() - 声明模块的设备表:
MODULE_DEVICE_TABLE() - 声明模块的别名:
MODULE_ALIAS()
声明USB/PCI等设备驱动模块所支持的设备
static struct usb_device_id skel_table[] = {
USB_DEVICE(USB_SKEL_VENDOR_ID,
USB_SKEL_PRODUCT_ID)},
{}
};
MODULE_DEVICE_TABLE(usb,skel_table)
模块使用计数
- Linux 2.6以前,使用
MOD_INC_USE_COUNT和MOD_DEC_USE_COUNT宏管理自己被使用的计数 - Linux2.6以后,使用
- 2.6以后,为不同类型的设备定义了
struct module * owner域 try_module_get(dev->owner):增加模块使用计数module_put(dev->owner):减少模块使用计数
- 2.6以后,为不同类型的设备定义了
- 使用这个主要是为了让设备在使用时,不被卸载。
模块的编译:简单的Makefile
KVERS = $(shell uname -r) #当前内核的版本
# Kernel modules
#哪一个模块
obj-m += book.o
# Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0 #开启这里得到包含调试信息的模块
build: kernel_modules
kernel_modules:
# 将模块扔到/lib/modules/$(KVERS)/build目录下去编译
# M=$(CURDIR) : 模块在哪个目录,CURDIR为当前目录
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
注意,该Makefile得到的xxx.ko是只能在PC上运行,如果需要让xxx.ko能装载到ARM上,需要交叉编译内核源码,以及在Makefile中指定make所使用的交叉编译工具
本文围绕Linux内核模块展开,介绍了模块相关命令,如加载、卸载、查看依赖等。详细阐述了内核模块程序结构,包括加载函数、卸载函数、参数、导出符号等内容,还说明了模块声明与描述、使用计数等要点,最后提及简单Makefile编译及交叉编译相关注意事项。

被折叠的 条评论
为什么被折叠?



