内核模块代码解析与模块的传参和依赖

news/2024/12/2 20:47:05/

一、内核模块基础代码解析

Linux内核的插件机制——内核模块

类似于浏览器、eclipse这些软件的插件开发,Linux提供了一种可以向正在运行的内核中插入新的代码段、在代码段不需要继续运行时也可以从内核中移除的机制,这个可以被插入、移除的代码段被称为内核模块。

主要解决:

  1. 单内核扩展性差的缺点

  2. 减小内核镜像文件体积,一定程度上节省内存资源

  3. 提高开发效率

  4. 不能彻底解决稳定性低的缺点:内核模块代码出错可能会导致整个系统崩溃

内核模块的本质:一段隶属于内核的“动态”代码,与其它内核代码是同一个运行实体,共用同一套运行资源,只是存在形式上是独立的。

具体代码解析:

#include <linux/module.h> //包含内核编程最常用的函数声明,如printk
#include <linux/kernel.h> //包含模块编程相关的宏定义,如:MODULE_LICENSE
​
/*该函数在模块被插入进内核时调用,主要作用为新功能做好预备工作被称为模块的入口函数__init的作用 : 
1. 一个宏,展开后为:__attribute__ ((__section__ (".init.text")))   实际是gcc的一个特殊
链接标记
2. 指示链接器将该函数放置在 .init.text区段
3. 在模块插入时方便内核从ko文件指定位置读取入口函数的指令到特定内存位置
*/
int __init myhello_init(void)
{/*内核是裸机程序,不可以调用C库中printf函数来打印程序信息,Linux内核源码自身实现了一个用法与printf差不多的函数,命名为printk (k-kernel)printk不支持浮点数打印*/printk("#####################################################\n");printk("#####################################################\n");printk("#####################################################\n");printk("#####################################################\n");printk("myhello is running\n");printk("#####################################################\n");printk("#####################################################\n");printk("#####################################################\n");printk("#####################################################\n");return 0;
}
​
/*该函数在模块从内核中被移除时调用,主要作用做些init函数的反操作被称为模块的出口函数__exit的作用:
1.一个宏,展开后为:__attribute__ ((__section__ (".exit.text")))   实际也是gcc的一个特殊链接标记
2.指示链接器将该函数放置在 .exit.text区段
3.在模块插入时方便内核从ko文件指定位置读取出口函数的指令到另一个特定内存位置
*/
void __exit myhello_exit(void)
{printk("myhello will exit\n");
}
​
/*
MODULE_LICENSE(字符串常量);
字符串常量内容为源码的许可证协议 可以是"GPL" "GPL v2"  "GPL and additional rights"  "Dual BSD/GPL"  "Dual MIT/GPL" "Dual MPL/GPL"等, "GPL"最常用
​
其本质也是一个宏,宏体也是一个特殊链接标记,指示链接器在ko文件指定位置说明本模块源码遵循的许可证
在模块插入到内核时,内核会检查新模块的许可证是不是也遵循GPL协议,如果发现不遵循GPL,则在插入模块时打印抱怨信息:myhello:module license 'unspecified' taints kernelDisabling lock debugging due to kernel taint
也会导致新模块没法使用一些内核其它模块提供的高级功能
*/
MODULE_LICENSE("GPL");
​
/*
module_init 宏
1. 用法:module_init(模块入口函数名) 
2. 动态加载模块,对应函数被调用
3. 静态加载模块,内核启动过程中对应函数被调用
4. 对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.initcall段),方便系统初始化统一调用。
5. 对于动态加载的模块,由于内核模块的默认入口函数名是init_module,用该宏可以给对应模块入口函数起别名
*/
module_init(myhello_init);
​
/*
module_exit宏
1.用法:module_exit(模块出口函数名)
2.动态加载的模块在卸载时,对应函数被调用
3.静态加载的模块可以认为在系统退出时,对应函数被调用,实际上对应函数被忽略
4.对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.exitcall段),方便系统必要时统一调用,实际上该宏在静态加载时没有意义,因为静态编译的驱动无法卸载。
5.对于动态加载的模块,由于内核模块的默认出口函数名是cleanup_module,用该宏可以给对应模块出口函数起别名
*/
module_exit(myhello_exit);

模块三要素:入口函数 、出口函数 、MODULE__LICENSE

二、内核模块的多源文件编程

ifeq ($(KERNELRELEASE),)
​
ifeq ($(ARCH),arm)
KERNELDIR ?= 目标板linux内核源码顶层目录的绝对路径
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) INSTALL_MOD_PATH=$(ROOTFS) modules_install
​
clean:rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions
​
else
obj-m += hello.o
​
endif

Makefile中:

obj-m用来指定模块名,注意模块名加.o而不是.ko

可以用 模块名-objs 变量来指定编译到ko中的所有.o文件名(每个同名的.c文件对应的.o目标文件)

一个目录下的Makefile可以编译多个模块:

添加:obj-m += 下一个模块名.o

三、 内核模块信息宏

MODULE_AUTHOR(字符串常量); //字符串常量内容为模块作者说明
​
MODULE_DESCRIPTION(字符串常量); //字符串常量内容为模块功能说明
​
MODULE_ALIAS(字符串常量); //字符串常量内容为模块别名

这些宏用来描述一些当前模块的信息,可选宏

这些宏的本质是定义static字符数组用于存放指定字符串内容,这些字符串内容链接时存放在.modinfo字段,可以用modinfo命令来查看这些模块信息,用法:

modinfo  模块文件名

四、模块传参

module_param(name,type,perm);//将指定的全局变量设置成模块参数
/*
name:全局变量名
type:使用符号      实际类型                传参方式bool         bool           insmod xxx.ko  变量名=0 或 1invbool      bool           insmod xxx.ko  变量名=0 或 1charp        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  
*/

可用MODULE_PARAM_DESC宏对每个参数进行作用描述,用法:

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

字符串常量的内容用来描述对应参数的作用

modinfo可查看这些参数的描述信息

五、模块依赖

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

一个模块中这些可以被其它地方使用的名称被称为导出符号,所有导出符号被填在同一个表中这个表被称为符号表。

最常用的可导出全局特性为全局变量和函数

查看符号表的命令:nm nm查看elf格式的可执行文件或目标文件中包含的符号表,用法:

nm 文件名 (可以通过man nm查看一些字母含义)

两个用于导出模块中符号名称的宏:

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模块卸载失败

补充说明: 内核符号表(直接当文本文件查看) /proc/kallsyms运行时 /boot/System.map编译后


http://www.ppmy.cn/news/471347.html

相关文章

Sony/索尼 NW-ZX300A ZX300 无损音乐播放器4.4口

https://item.taobao.com/item.htm?spma1z0d.7625083.1998302264.6.5c5f4e69ELHOcm&id557859816402 &#xff08;特价3天&#xff09;Sony/索尼 NW-ZX300A ZX300 无损音乐播放器4.4口 转载于:https://www.cnblogs.com/pengmn/p/10381174.html

【ZLR-T81 200A漏电继电器】

系列型号&#xff1a; ZLR-T81漏电继电器 ZLR-T81&#xff0b;ZCT-35漏电继电器 ZLR-T81&#xff0b;ZCT-80漏电继电器 ZLR-T81&#xff0b;ZCT-120漏电继电器 一、产品用途 ZLR-T81漏电继电器&#xff08;以下简称继电器&#xff09;适用于交流电压为380.660V.至1140V系统中…

运算放大器---虚短和虚断

运算放大器—虚短和虚断 前言 运算放大器两板斧&#xff1a;“虚短”“虚断” 虚短&#xff1a;在分析运算放大器处于线性状态时&#xff0c;可把两输入端视为等电位&#xff0c;这一特性称为虚假短路&#xff0c;简称虚短&#xff1b;当某一端接地的时候&#xff1a;V- V …

在职族必看!不露痕迹面试,拿到心仪offer的4个绝密技巧!求职软件屏蔽功能根本毫无作用!...

为了无缝衔接&#xff0c;许多人都会选择在职找工作&#xff0c;但如果被当前公司发现&#xff0c;很可能会酿成职业生涯的一次悲剧。 一位网友问&#xff1a;在职面试怎么避免被当前公司发现&#xff1f;boss直聘上的屏蔽功能是不是没用&#xff1f; 网友扎心回复&#xff1a;…

运放(一)-虚短虚断与深度负反馈

一、深度负反馈 一个运算放大器&#xff0c;其输出电压 其中A为运放的开环增益。 对于上图所示的负反馈电路&#xff0c;反馈系数为F&#xff0c;其负相输入电压 (1)与(2)联立可以得到 其中1AF称为反馈深度&#xff0c;当1AF远大于1时&#xff0c;称电路处于深度负反馈状态。 对…

RxJava2 背压

1 背压 在RxJava中&#xff0c;会遇到被观察者发送消息太快以至于它的操作符或者订阅者不能及时处理相关的消息&#xff0c;这就是典型的背压(Back Pressure)场景。 BackPressure经常被翻译为背压&#xff0c;背压的字面意思比较晦涩&#xff0c;难以理解。它是指在异步场景下&…

如何让Ubuntu系统支持LDAC,APTX,AAC编码(提升蓝牙音质)

开始 一开始一直都以为电脑对LDA&#xff0c;ATPX&#xff0c;AAC等编码是硬件需要支持才可以的&#xff0c;不过在搜索了reddit和stackoverflow才知道这只是一种编码格式&#xff0c;所以理论上来说&#xff0c;所有的发射信号的设备都可以以这种编码格式进行数据封装。只是在…

关于Walkman NW-ZX300A利用Music Center分析歌曲使用sensme频道的方法(填坑)

前言 (2019/06/02 Music Center更新之后我也来跟新了一下文章) 现在Music Center更新之后&#xff0c;我们可以直接用这个功能Acquire unknown Properties来分析歌曲 之后确保所有歌曲都是Analyzed的 之后把歌曲拖到你的Walkman里面就可以了&#xff0c;这里不一定非要用那个…