lv13 内核模块参数和依赖

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模块,则:

  1. 编译次序:先编译模块A,再编译模块B,当两个模块源码在不同目录时,需要:i. 先编译导出符号的模块A ii. 拷贝A模块目录中的Module.symvers到B模块目录 iii. 编译使用符号的模块B。否则编译B模块时有符号未定义错误

  2. 加载次序:先插入A模块,再插入B模块,否则B模块插入失败

  3. 卸载次序:先卸载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。

相关推荐
wdxylb18 分钟前
云原生俱乐部-shell知识点归纳(1)
linux·云原生
飞雪20071 小时前
Alibaba Cloud Linux 3 在 Apple M 芯片 Mac 的 VMware Fusion 上部署的完整密码重置教程(二)
linux·macos·阿里云·vmware·虚拟机·aliyun·alibaba cloud
路溪非溪2 小时前
关于Linux内核中头文件问题相关总结
linux
Lovyk4 小时前
Linux 正则表达式
linux·运维
Fireworkitte5 小时前
Ubuntu、CentOS、AlmaLinux 9.5的 rc.local实现 开机启动
linux·ubuntu·centos
sword devil9006 小时前
ubuntu常见问题汇总
linux·ubuntu
ac.char6 小时前
在CentOS系统中查询已删除但仍占用磁盘空间的文件
linux·运维·centos
淮北也生橘127 小时前
Linux的ALSA音频框架学习笔记
linux·笔记·学习
华强笔记10 小时前
Linux内存管理系统性总结
linux·运维·网络
十五年专注C++开发11 小时前
CMake进阶: CMake Modules---简化CMake配置的利器
linux·c++·windows·cmake·自动化构建