1 模块传参
1.1 模块参数设置
将指定的全局变量设置成模块参数
cpp
module_param(name,type,perm);//将指定的全局变量设置成模块参数
/*
name:全局变量名
type:
使用符号 实际类型 传参方式
bool bool insmod xxx.ko 变量名=0 或 1
invbool bool insmod xxx.ko 变量名=0 或 1
charp char * insmod xxx.ko 变量名="字符串内容"
short short insmod xxx.ko 变量名=数值
int int insmod xxx.ko 变量名=数值
long long insmod xxx.ko 变量名=数值
ushort unsigned short insmod xxx.ko 变量名=数值
uint unsigned int insmod xxx.ko 变量名=数值
ulong unsigned long insmod xxx.ko 变量名=数值
perm:给对应文件 /sys/module/name/parameters/变量名 指定操作权限
#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002 //不要用 编译出错
#define S_IXOTH 00001
*/
module_param_array(name,type,&num,perm);
/*
name、type、perm同module_param,type指数组中元素的类型
&num:存放数组大小变量的地址,可以填NULL(确保传参个数不越界)
传参方式 insmod xxx.ko 数组名=元素值0,元素值1,...元素值num-1
*/
注:perm变量名操作权限一般都设置为0664,对应110(用户可读写),110(组可读写 100,(其他用户可读) 。若是数组变量,存放数组大小变量的地址,可以填NULL(确保传参个数不越界)。
1.2 示例:
testparam.c
cpp
#include <linux/module.h>
#include <linux/kernel.h>
int gx = 10; //定义全局变量
char *gstr = "hello";
int garr[5] = {1,2,3,4,5};
module_param(gx, int, 0664); //设置为模块参数
module_param(gstr, charp, 0664);
module_param_array(garr, int, NULL, 0664);
int __init testparam_init(void)
{
int i = 0;
printk("gx=%d\n",gx);
printk("gstr=%s\n",gstr);
for(i = 0;i < 5;i++)
{
printk("%d ",garr[i]);
}
printk("\n");
return 0;
}
void __exit testparam_exit(void)
{
printk("testparam will exit\n");
}
MODULE_LICENSE("GPL");
//MODULE_AUTHOR("4IOT");
//MODULE_DESCRIPTION("It is only a simple test");
//MODULE_ALIAS("HI");
module_init(testparam_init);
module_exit(testparam_exit);
修改MakeFile
cpp
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
CONFIG_MODULE_SIG=n
obj-m += testparam.o
endif
编译运行
带参数编译运行
用法:用参数控制内核某个功能是否执行
1.3 参数描述
可用MODULE_PARAM_DESC宏对每个参数进行作用描述,用法:
MODULE_PARM_DESC(变量名,字符串常量);
字符串常量的内容用来描述对应参数的作用
modinfo可查看这些参数的描述信息
2 模块依赖
2.1 模块依赖规则
既然内核模块的代码与其它内核代码共用统一的运行环境,也就是说模块只是存在形式上独立,运行上其实和内核其它源码是一个整体,它们隶属于同一个程序,因此一个模块或内核其它部分源码应该可以使用另一个模块的一些全局特性。
一个模块中这些可以被其它地方使用的名称被称为导出符号,所有导出符号被填在同一个表中这个表被称为符号表。
最常用的可导出全局特性为全局变量和函数
查看符号表的命令:nm nm查看elf格式的可执行文件或目标文件中包含的符号表,用法:
cpp
nm 文件名 (可以通过man nm查看一些字母含义)
第一列:其相对地址(即相对位置)
D代表全局变量(初始化过的)
T代表函数
B代表全局变量 (未被初始化)
R代表加了const的全局变量
两个用于导出模块中符号名称的宏:
cpp
EXPORT_SYMBOL(函数名或全局变量名)
EXPORT_SYMBOL_GPL(函数名或全局变量名) 需要GPL许可证协议验证
使用导出符号的地方,需要对这些符号进行extern声明后才能使用这些符号
B模块使用了A模块导出的符号,此时称B模块依赖于A模块,则:
-
编译次序:先编译模块A,再编译模块B,当两个模块源码在不同目录时,需要:i. 先编译导出符号的模块A ii. 拷贝A模块目录中的Module.symvers到B模块目录 iii. 编译使用符号的模块B。否则编译B模块时有符号未定义错误
-
加载次序:先插入A模块,再插入B模块,否则B模块插入失败
-
卸载次序:先卸载B模块,在卸载A模块,否则A模块卸载失败
补充说明: 内核符号表(直接当文本文件查看)
cpp
/proc/kallsyms运行时 /boot/System.map编译后
查看
cpp
vi /proc/kallsyms //运行起来就没有相对地址了,给绝对地址也不安全所以都是0
vi /boot/System.map
开发板查看符号表
cpp
vim vmlinux
2.2 示例(2个模块同目录下):
moduleA.c
cpp
#include <linux/module.h>
#include <linux/kernel.h>
int gx = 19;
EXPORT_SYMBOL(gx);
int __init moduleA_init(void)
{
printk("In module_a init gx=%d\n",gx);
return 0;
}
void __exit moduleA_exit(void)
{
printk("moduleA will exit\n");
}
//MODULE_AUTHOR("4IOT");
//MODULE_DESCRIPTION("It is only a simple test");
//MODULE_ALIAS("HI");
module_init(moduleA_init);
module_exit(moduleA_exit);
moduleB.c
cpp
#include <linux/kernel.h>
extern int gx;
int __init moduleB_init(void)
{
printk("\n");
return 0;
}
void __exit moduleB_exit(void)
{
printk("moduleB will exit\n");
}
MODULE_LICENSE("GPL");
//MODULE_AUTHOR("4IOT");
//MODULE_DESCRIPTION("It is only a simple test");
//MODULE_ALIAS("HI");
module_init(moduleB_init);
module_exit(moduleB_exit);
MakeFile(注意编译顺序)
cpp
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
CONFIG_MODULE_SIG=n
obj-m += moduleA.o //需要先编译moduleA
obj-m += moduleB.o
endif
编译
插入模块(注意插入顺序)
移除模块(注意移除顺序)
2.3 示例(2个模块不同目录)
新建两个目录moda、modb
分别把之前示例的.c和Makefile放到moda和modb目录下
修改Makefile只编译目录下的可执行文件。
编译的时候必须先编译A,否则会报错,如下图。编译完A后需要把生成的.symvers文件拷贝到B再再编译B。