注:内容大多摘自《Linux设备驱动开发详解》(第2版)
Linux内核模块
1.1 简介
1.特点:
- 模块本身不被编译入内核映像,从而控制了内核的大小;
- 模块一旦被加载,它就和内核中的其他部分完全一样。
2.模块程序结构
(1)模块加载函数(一般需要)
(2)模块卸载函数(一般需要)
(3)模块许可证声明(必须)
(4)模块参数(可选)
(5)模块导出符号(可选)
(6)模块作者等信息声明(可选)
3.模块的加载,卸载,查看:
- 加载:insmod命令
模块加载后,在/sys/module/目录下面将出现以此模块名命名的目录。 - 卸载:rmmod命令
- 查看:lsmod命令,查看所有加载的内核模块
4.printk函数
内核模块中用于输出的函数是内核空间的printk()而非用户空间的printf(),printk与printf用户相似,但前者可定义输出级别。printk可作为一种最基本的内核调试手段。参考:printk 使用方法
备注:测试模块例子时,终端没能输出printk的输出信息,可以通过执行dmesg命令查看。参考:printk 无法输出到控制台
1.2,简单模块例子
1,模块代码(创建hello.c):
# include <linux/init.h>
# include <linux/module.h>
static int hello_init(void)
{
printk(KERN_EMERG "Hello World enter\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_EMERG "Hello World exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
2,编译代码 (创建Makefile文件):
## 执行uname -r 输出为 4.2.0-16-generic
KVERS = $(shell uname -r)
# Kernel modules
obj-m +=hello.o
#Specify flags for the module compliation
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
3,在模块代码和Makefile的当前目录下执行make命令,生成hello.ko,调用insmod hello.ko挂载该模块(可执行lsmod查看是否挂载成功),调用rmmod hello卸载该模块。执行dmesg查看printk的输出信息。
1.3 模块声明与描述
MOUDLE_AUTHOR(author); #作者
MOUDLE_DESCRIPTION(description); #描述
MOUDLE_VERSION(version); #版本
MODULE_DEVICE_TABLE(table_info); #设备表
MODULE_ALIAS(alternate_name); #别名
执行modeinfo <模块名>可以获取模块信息,如modinfo hello.ko。
1.4 模块参数
用module_param(参数名,参数类型,参数读/写权限)为模块定义一个参数,例子如下:
static char* book_name = "dissecinting Linux Device Driver";
static int num = 4000;
module_param(book_name, charp, S_IRUGO);
module_param(num, int, S_IRUGO);
# 装载是执行命令:insmod param.ko book
参数类型可以是byte, short, ushort, int, uinit, long, ulong, charp(字符指针), bool或invbool(布尔的反),编译时会将module_param中声明的类型和变量定义的类型进行比较,判断是否一致。
模块加载后,在/sys/module/目录下面将出现以此模块名命名的目录。当读写权限为0时,表示此参数不存在sysfs文件系统下对应的文件节点;如果不为0,在此模块目录下回出现parameter目录,包含一系列以参数命名的文件节点。权限在include/linux/stat.h中有定义
使用 S_IRUGO 参数可以被所有人读取, 但是不能改变; S_IRUGO|S_IWUSR 允许 root 来改变参数. 注意, 如果一个参数被 sysfs 修改, 你的模块看到的参数值也改变了, 但是你的模块没有任何其他的通知. 你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应。
(参考:Linux之module_param()函数学习)
1.5 模块的使用
嵌入式产品一般不需要建立模块间依赖关系,所以modprobe可以不要,一般也不需要卸载模块,所以rmmod也可以不要,但insmod是必须的(在Android设备中尝试了insmod,rmmod,modinfo,lsmod等命令,都有存在,而modprobe不存在)。一般而言,产品在启动过程中应该加载模块,在嵌入式产品Linux的启动过程中,最简单的修改方法是修改启动过程的rc脚本(可能是/init.rc文件),增加insmod /…/xxx.ko这样的命令。用busybox做出的文件系统,通常修改etc/init.d/rcS文件。
1.6 导出符号
暂时不知道怎么使用,类似外部函数
参考:导出符号的意义
1.7 附录:
1,Hello World代码:
(1)hello.c代码
# include <linux/init.h>
# include <linux/module.h>
static int hello_init(void)
{
printk(KERN_EMERG "Hello World enter\n");
return 0;
}
static void hello_exit(void)
{
printk(KERN_EMERG "Hello World exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("Dual BSD/GPL");
(2)Makefile文件
## 执行uname -r 输出为 4.2.0-16-generic
KVERS = $(shell uname -r)
# Kernel modules
obj-m +=hello.o
#Specify flags for the module compliation
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
2,模块参数例子代码:
(1)文件:book.c
# include <linux/init.h>
# include <linux/module.h>
static char* book_name = "dissecinting Linux Device Driver";
static int num = 4000;
static int hello_init(void)
{
printk(KERN_EMERG "book name:%s\n", book_name);
printk(KERN_EMERG "book num:%d\n", num);
return 0;
}
static void hello_exit(void)
{
printk(KERN_EMERG "Hello World exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
module_param(book_name, charp, S_IRUGO);
module_param(num, int, S_IRUGO);
MODULE_LICENSE("Dual BSD/GPL");
(2)文件:Makefile
KVERS = $(shell uname -r)
# Kernel modules
obj-m +=book.o
#Specify flags for the module compliation
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean