参考书籍《Linux设备驱动第三版》
一、源码解析
--hello_module.c--
#include
#include
static int __init hello_init(void)
{
printk(KERN_ALERT "Hello, world.\n");
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world.\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
1、2行,moudle.h 包含了大量加载模块需要的函数和符号的定义;init.h 指定你的初始化和清理函数;有时候还需要包含moudleparam.h, 使得可以在模块加载时传递参数给模块(参考第四部分)。
这里用到了当前内核源码树的位置,这也是在开始Linux内核开发之前必需的环境。本文使用的内核为安装在VMware上的Ubuntu14.04LTS,可以通过在终端中输入
% uname -r4~8行为hello_init定义部分:
初始化函数应当声明成静态的, 因为它们只在当前文件可见;
10~13行为hello_exit清理函数定义部分,当模块从系统中卸载时,它负责回收之前模块申请的资源(这里并没有申请),清理函数没有返回值, 因此它被声明为 void。
18行 MODULE_LICENSE 告知内核该模块带有一个自由的许可证,Linux通常都是遵守GPL授权的,这里至少应该是 MODULE_LICENSE("GPL") ,内核中的模块必须满足内核的授权权限。二、Makefile解析
--Makefile--
KERN_VER = $(shell uname -r)
KERN_DIR = /lib/modules/$(KERN_VER)/build
obj-m += hello_module.o
all:
make -C $(KERN_DIR) M=`pwd` modules
.PHONY: clean
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
第1行,获取内核版本信息
第4行,将hello_module.c编译成一个模块(m—模块,y-静态链接到zImage中,-n表示不编译)
这一行是必不可少的,实际上Makefile也只需要这一行就能搞定。假如有个新的模块module.ko,它依赖于两个文件file1.c和file2.c的话,这一行应该改为:
obj-m +=module.o
module-objs := file1.o file2.o
6、7行,all 目标为 make modules 表示编译为模块其中的vermagic之中就记录了版本信息和处理器体系,它们必须与当前的系统的版本信息一致
三、测试
1、先将系统切换到超级用户
% su
2、安装前先查看系统已有模块
% lsmod
3、安装模块
% insmod xxx.ko
install module.当我们的模块引用了当前内核中没有定义的符号或者没有安装的模块时,也可以使用命令 %modprobe来加载。照理shell应该打印 “Hello, world.” 这条信息,这里由于Ubuntu将其保存在系统日志中,并没有实时打印出来,所以我们看不到,使用命令
% dmesg
可以打开日志文件。模块是添加到链表头的
4、卸载模块
% rmmod xxx
remove module,同理使用dmesg可以显示卸载信息
卸载之后,hello_module已经不在系统模块里面了。
四、模块参数
当我们希望在使用insmod 或者 modprobe 加载驱动时附带参数的时候,可以用 "module_param(参数名, 参数类型, 参数读写权限) " 为模块定义一个参数(modprobe 也可以从它的配置文件(/etc/modprobe.conf)读取参数的值),此时的命令格式为
% insmode(或modprobe)模块名 参数名1=参数值 参数名2=参数值 …
如果没有输入参数值,则使用文件中定义的默认值。
其中的,参数类型可以是byte, short, ushort, int, uint, long, ulong, charp(字符指针), bool或invbool(布尔取反); 参数读/写权限,应当使用在
除此之外,模块也可以拥有参数数组,形式为“module_param_array(数组名,数组类型,数组长,参数读/写权限)"。从2.6.0~2.6.10版本,需将数组长变量名赋给”数组长“,从2.6.10版本开始,需将数组长变量的指针赋给”数组长“,当不需要保存实际输入的数组元素个数时,可以设置”数组长“为NULL。
运行insmod/modprobe命令时,应使用逗号分隔输入的数组元素。
更改后的 hello_module.c
#include
#include
static char *name = "xxx";
static uint num = 1;
static int __init hello_init(void)
{
printk(KERN_ALERT "num = %d. Hello, %s.\n", num, name);
return 0;
}
static void __exit hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world.\n");
}
module_init(hello_init);
module_exit(hello_exit);
module_param(name, charp, S_IRUGO);
module_param(num, uint, S_IRUGO);
MODULE_LICENSE("Dual BSD/GPL");
增加了4、5、20、21行,第9行稍作修改,用以显示参数值。
重现make,加载
% insmod hello_module.ko
这里先没有输入参数值,只打印日志中最后一行
% dmesg | tail -1
卸载,重新加载时携带参数值
% insmod hello_module.ko num=5 name=xiaoming
当然,也可以只传入一个参数
五、模块调用
1、复制当前驱动到ch2p文件夹中
2、修改hello_module.c为hello_module2.c,在里面添加如下4条语句(其余不变)
void fun(void)
{
printk("come from ch2p.\n");
}
EXPORT_SYMBOL(fun);
3、修改Makefile文件中对应语句
obj-m += hello_module2.o
4、make,之后 insmod hello_module2.ko
5、修改ch2文件夹下的hello_module.c
extern void fun(void);
static int __init hello_init(void)
{
fun();
printk(KERN_ALERT "num = %d. Hello, %s.\n", num, name);
return 0;
}
相当于添加了两行 extern void fun(void); 和 fun(); 其余不变。
6、需要注意的是,这里直接make是找不到fun函数的。这里有三种方法可以解决,参考:http://blog.csdn.net/xhz1234/article/details/44278137
总结来说,
调用时,被调用者通过EXPORT_SYMBOL或EXPORT_SYMBOL_GPL导出符号;调用者通过extern声明该符号。
加载时,先加载被调用者所在模块,再加载调用者所在模块。
卸载时,先卸载调用者所在模块,再卸载被调用者所在模块。