Linux 模块
什么是 Linux 模块
Linux模块,也就是可加载内核模块(LKMs),允许在运行时动态加载到内核中。
这说明两点:
- 是内核模块, 也就是说是内核的一部分, 只能依赖内核的接口, 且必须遵循内核的规则.
- 运行时可加载, 这避免了重复编译内核和重启系统. 并且内核和模块是分开的, 部署更灵活. 同时也可以只在需要的时候加载模块, 节省内核的内存占用.
开发模块时常用的命令
insmod module.ko # 加载模块
rmmod module.ko # 卸载模块
modprobe module.ko # 智能加载模块, 会自动解决依赖
lsmod # 查看已加载的模块
modinfo module.ko # 查看模块信息
depmod -a # 更新模块依赖
dmesg # 查看内核日志
uname -r # 查看内核版本
如何实现一个 Linux 模块
-
编写模块代码
#include<linux/module.h> #include<linux/init.h> // 不能依赖c库, 只能使用内核提供的接口 MODULE_LICENSE("Dual BSD/GPL"); // 必须有,否则报错. 模块许可证,许可可选 static int __init myinit(void) // 必须有,在模块加载时调用 { printk("Hi module!\n"); return 0; } static void __exit myexit(void) // 必须有,在模块卸载时调用 { printk("Bye module!\n"); } module_init(myinit); // 必须有,指定 这是模块加载时调用的函数 module_exit(myexit); // 必须有,指定 模块卸载时调用的函数
模块参数
在模块代码中加入如下代码:#include <linux/moduleparam.h> int param = 0; // module_param(变量名, 类型, 权限) // 类型可以是byte, hexint, short, ushort, int, uint, long, ulong, charp, bool, invbool // module参数会在/sys/module/模块名/parameters/下生成一个文件, 权限就是设置这个文件的权限, 管理谁可以读取修改这个参数. module_param(param, int, S_IRUGO); int arrParam[3]; int arrNum = 3; // 有两作用, 1. 初始值用来限制用户传入的数组个数 2. 会被更改为实际传入的数组个数 module_param_array(arrParam, int, &arrSize, S_IRUGO); // 传入数组
在加载模块时可以传入参数, 例如
insmod module.ko param=1 arrParam=1,2,3
权限相关的宏 说明 S_IRUSR 所有者可读 S_IRGRP 组可读 S_IROTH 其他可读 S_IWUSR owner可写 S_IRWXUGO 所有者,组,其他可读写执行 ... 格式就是S_I[RWX][UGO] -
编写 Makefile
obj-m := module.o // 表明有一个模块要从module.o建立 module-objs := file1.o file2.o // 表明module.o的依赖, file1.o会自动根据名字找到对应的源文件file1.c(其他后缀的情况我不清楚)生成. 如果一个模块只由一个源文件生成, 可以直接写成obj-m := file1.o #generate the path CURRENT_PATH:=$(shell pwd) #the current kernel version number LINUX_KERNEL:=$(shell uname -r) #the absolute path LINUX_KERNEL_PATH:=/usr/src/linux-headers-$(LINUX_KERNEL) #complie object all: make -C $(LINUXKERNELPATH) M=$(CURRENT_PATH) modules # -C 改变目录到指定内核目录, M= 指定模块所在目录 #clean clean: make -C $(LINUXKERNELPATH) M=$(CURRENT_PATH) clean
编译模块有几个前提:
- 编译模块实际使用的是内核的Makefile, 所以必须要先下载对应的内核代码并编译成功.
- 保证你有版本足够新的编译器, 模块工具, 以及其他必要工具. 在内核Documentation/Changes 列出了需要的工具版本
-
加载/卸载模块
insmod module.ko
rmmod module.ko
modprobe module.ko
# 与insmod的区别是如果要加载的模块引用了内核中为定义的符号,modprobe会在当前模块搜索路径中寻找其他模块
Linux 模块是如何工作的
.ko文件是什么
本质是一个ELF文件, 和.o
文件差不多,都是REL(relocatable file), 只是多了一些元数据,比如模块许可证, 然后把多个.o
打包成一个.ko
, 还多了导出的符号表.
模块加载的过程
模块加载时会使用一个系统调用sys_init_module
, 在内核代码中使用SYSCALL_DEFINE3(init_module,...)
来定义这个系统调用.
主要代码是
err = copy_module_from_user(umod, len, &info);
...
return load_module(&info, uargs, 0);
copy_module_from_user
是把用户空间的模块数据拷贝到内核空间, 这部分内容需要理解内存管理机制.最终会把模块数据放到info
中.
load_module
涉及到几个部分, ELF解析,内存管理等, 暂时不深入.
怎么把用户空间数据copy到内核空间
补充: 内核模块和用户程序的区别
- 只连接到内核, 能够调用的唯一的函数是内核输出的那些.
- 错误处理
- 用户空间和内核空间的不同
- 内核的并发, 所以内核代码必须是可重入的--能在多个上下文中同时运行
- 应用程序存在于虚拟内存中, 有一个非常大的堆栈区. 内核, 相反, 有一
个非常小的堆栈,并和所有内核空间调用链共享; - 小心使用__开始的函数, 如果你调用这个函数, 确信你知道你在做什么.
- 内核代码不能做浮点运算, 使能浮点将要求内核在每次进出内核空间的时候保存
和恢复浮点处理器的状态, 增加了不必要的开销. - linux 内核头文件提供了方便来管理你的符号的可见性, 如果需要输出符号给其他模块使用需要使用
EXPORT_SYMBOL()/EXPORT_SYMBOL_GPL()
宏._GPL
表示只给GPL许可证的模块使用.
本文作者:天刚刚破晓
本文链接:https://www.cnblogs.com/tggpx/p/18730492
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步