LV10-01-内核模块-02-模块参数与依赖

本文主要是内核模块的参数与依赖相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
Linux开发板 华清远见 底板: FS4412_DEV_V5 核心板: FS4412 V2
u-boot 2013.01
点击查看本文参考资料
参考方向 参考原文
------
点击查看相关文件下载
文件下载链接
------

一、模块传参

还记得main函数有两个参数吗,我们在执行可执行文件的时候,可以直接在命令行向可执行程序传递参数,那模块呢?是没有main函数的,我们动态加载模块的时候想要给它传递参数怎么办呢?

1. module_param()

1.1 函数说明

我们可以通过以下命令来查看在linux源码中含有该函数的文件:

1
grep module_param -r -n ~/5linux/linux-3.14include/

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
/* 需包含的头文件 */
#include <linux/moduleparam.h>

/* 函数声明 */
#define module_param(name, type, perm) \
module_param_named(name, name, type, perm)

【函数说明】该函数,严格来说应该是一个宏,它将指定的全局变量设置成模块参数。

【函数参数】

  • name:全局变量名。
  • type:变量类型,c中的变量类型与.ko模块函数使用的类型符号对应关系如下:
点击查看对应关系
函数使用符号实际类型传参方式
boolbool insmod xxx.ko 变量名=0 或 1
invbool bool insmod xxx.ko 变量名=0 或 1
charpchar * insmod xxx.ko 变量名="字符串内容"
shortshort insmod xxx.ko 变量名=数值
intint insmod xxx.ko 变量名=数值
longlong insmod xxx.ko 变量名=数值
ushortunsigned shortinsmod xxx.ko 变量名=数值
uintunsigned int insmod xxx.ko 变量名=数值
ulongunsigned long insmod xxx.ko 变量名=数值
  • perm:给对应文件 /sys/module/name/parameters/变量名 指定操作权限。

模块插入成功后,模块的变量都会在/sys/module/name/parameters/目录下生成一个以变量名为名称的文件,我们使用模块参数的话,需要给这些文件指定一个权限,我们一般会设置为0664

【返回值】none

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
7
8
9
/* 需要包含的头文件 */
#include <linux/moduleparam.h>

/* 至少应该有的语句 */
int gx = 10;
char *gstr = "fanhua";

module_param(gx, int, 0664);
module_param(gstr, charp, 0664);

【注意事项】none

1.2 使用实例

暂无

2. module_param_array()

2.1 函数说明

我们可以通过以下命令来查看在linux源码中含有该函数的文件:

1
grep module_param_array -r -n ~/5linux/linux-3.14include/

经过查找,我们可以得到如下信息:

1
2
3
4
5
6
/* 需包含的头文件 */
#include <linux/moduleparam.h>

/* 函数声明 */
#define module_param_array(name, type, nump, perm) \
module_param_array_named(name, name, type, nump, perm)

【函数说明】该函数,严格来说应该是一个宏,它将指定的全局变量设置成模块参数。

【函数参数】

  • name:全局变量名。
  • type:指数组中元素的类型,c中的变量类型与.ko模块函数使用的类型符号对应关系如下:
点击查看对应关系
使用符号实际类型传参方式
boolbool数组名=元素值0,元素值1,...元素值num-1
invboolbool
charpchar *
shortshort
intint
longlong
ushortunsigned short
uintunsigned int
ulongunsigned long
  • &num:存放数组大小变量的地址,可以填NULL(确保传参个数不越界)。
  • perm:给对应文件 /sys/module/name/parameters/变量名 指定操作权限。

模块插入成功后,模块的变量都会在/sys/module/name/parameters/目录下生成一个以变量名为名称的文件,我们使用模块参数的话,需要给这些文件指定一个权限,我们一般会设置为0664

【返回值】none

【使用格式】一般情况下基本使用格式如下:

1
2
3
4
5
6
7
/* 需要包含的头文件 */
#include <linux/moduleparam.h>

/* 至少应该有的语句 */
int garr[5] = {1, 2, 3, 4, 5};

module_param_array(garr, int, NULL, 0664);

【注意事项】none

3. MODULE_PARAM_DESC

我们可以使用MODULE_PARAM_DESC宏对每个参数进行作用描述,一般格式如下:

1
MODULE_PARM_DESC(变量名,字符串常量);

其中字符串常量的内容用来描述对应参数的作用,查看的时候可以用以下命令查看:

1
modinfo module_name.ko

4. 使用实例

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <linux/module.h>   /* MODULE_LICENSE */
#include <linux/kernel.h> /* printk */
#include <linux/init.h> /* module_init module_exit */


int gx = 10;
char *gstr = "fanhua";
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 test_param_init(void)
{
/* code */
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");
printk("[INFO]This module is loaded!\n");
return 0;
}

/* 模块出口函数 */
void __exit test_param_exit(void)
{
printk("[INFO]This module is exited!\n");
}


/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(test_param_init);
module_exit(test_param_exit);

/* 模块信息(通过 modinfo test_param 查看) */
MODULE_LICENSE("GPL"); /* 源码的许可证协议 */
MODULE_AUTHOR("qidaink"); /* 字符串常量内容为模块作者说明 */
MODULE_DESCRIPTION("Description"); /* 字符串常量内容为模块功能说明 */
MODULE_ALIAS("module's other name"); /* 字符串常量内容为模块别名 */

动态加载的时候使用以下命令加载:

1
insmod <module_name>.ko gx=20 gstr="qidaink" garr=6,7,8,9,10

二、模块依赖

内核模块的代码与其它内核代码共用统一的运行环境,也就是说模块只是存在形式上独立,运行上其实和内核其它源码是一个整体,它们隶属于同一个程序,因此一个模块或内核其它部分源码应该可以使用另一个模块的一些全局特性。

1. 导出符号

一个模块中这些可以被其它地方使用的名称被称为导出符号,所有导出符号被填在同一个表中这个表被称为符号表。最常用的可导出全局特性为全局变量和函数 。

2. 查看符号表

我们可以通过nm命令查看elf格式的可执行文件或目标文件中包含的符号表,一般格式如下:

1
nm 文件名

我们可以使用man nm命令来查看符号表中各个符号前边的字母的含义。

3. 符号导出与使用

我们在模块B中要用到模块A中的全局变量的话,需要这样来操作:

  • 模块A中:导出模块B所需要的全局变量符号;
1
2
3
// 模块A
int gx_a = 10;
EXPORT_SYMBOL(gx_a);
  • 模块B中:先对模块A导出的符号进行extern声明,然后才可以使用。
1
2
// 模块B
extern int gx_a;

Linux为我们提供了两个宏,用于导出全局变量符号:

1
2
EXPORT_SYMBOL(函数名或全局变量名);
EXPORT_SYMBOL_GPL(函数名或全局变量名); /* 需要GPL许可证协议验证 */

4. 依赖关系

像上边所说,B模块使用了A模块导出的符号,此时称B模块依赖于A模块,则有:

  • 编译次序

当两个模块源码在相同目录时:

(1)先编译需要导出符号的模块A;

(2)再编译使用符号的模块B.

当两个模块源码在不同目录时:

(1)先编译需要导出符号的模块A

(2)拷贝A模块目录中生成的Module.symversB模块目录(否则编译B模块时会有符号未定义错误);

(3)最后编译使用符号的模块B

  • 加载次序

先插入提供导出符号的A模块,再插入使用符号的B模块,否则B模块插入会失败。

1
2
insmod A.ko # 先加载
insmod B.ko # 再加载
  • 卸载次序

先卸载使用符号的B模块,再卸载提供导出符号的A模块,否则A模块卸载会失败。

1
2
rmmod B.ko # 先卸载
rmmod A.ko # 再卸载