Linux内核模块HelloWorld 浅浅的花香味﹌ 2022-09-11 11:25 221阅读 0赞 ### 内核模块 ### 内核整体架构非常庞大,编译时只把需要的功能编译到内核,其他部分可以编译成模块(.ko文件),在内核运行时,可动态注册模块到内核,也可卸载模块。 本文代码:https://github.com/mxxlei/kernel\_demo/tree/master/1-module-hello #### 1. 内核模块hello world #### #include <linux/init.h> #include <linux/module.h> static int __init hello_init(void){ printk(KERN_INFO, "Hello World enter\n"); return 0; } module_init(hello_init); static void __exit hello_exit(void){ printk(KERN_INFO, "Hello World exit\n"); } module_exit(hello_exit); MODULE_AUTHOR("mxlei <xxx@gmail.com>"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("A simple Hello World Module"): MODULE_ALIAS("a simple module"); 1. 最简单的模块,只包含模块加载函数,卸载函数和模块的一些描述信息。 2. 编译生成 hello.ko目标文件 3. 使用模块 # 装载模块 insmod ./hello.ko # 查看模块列表,也可以用 cat /proc/modules lsmod # 卸载模块 rmmod hello 4. printk是内核中的输出函数,类似与用户空间的printf 内核输出日志文件 /var/log/messages 5. 装载模块后,模块信息位于 /sys/module目录下,目录结构为 ├── coresize ├── holders ├── initsize ├── initstate ├── notes │ └── .note.gnu.build-id ├── refcnt ├── sections │ ├── .exit.text │ ├── .gnu.linkonce.this_module │ ├── .init.text │ ├── .note.gnu.build-id │ ├── .rodata.str1.1 │ ├── .strtab │ └── .symtab ├── taint └── uevent 6. 其他模块相关命令 # 装载模块的同时装载依赖的其他模块,功能比insmod强大 modeprobe hello # 卸载模块的同时卸载依赖的其他模块 modeprobe -f hello # 模块之间的依赖文件: /lib/modules/\<kernel-version\>/modules.dep # 编译内核时,也是通过depmod工具生成模块依赖关系文件的 # 查看模块信息 modinfo hello #### 2. 内核初始化时添加函数、数据 #### 1. \_ \_ init标志的函数 如果模块编译进内核,在连接时都会放到.init.text这个区段内。 在.initcall.init中保留了一份函数指针,在初始化时内核会调用这些函数,初始化完成后释放init段内存 2. \_ \_ exit标志的函数 若编译成模块,模块退出时执行 3. \_ \_ initdata标志的数据 若编译到内核,初始化时使用 若编译为模块,模块初始化时使用 4. \_ \_ exitdata标志的数据 若编译到内核,初始化完成后释放数据 #### 3. Linux内核模块程序结构 #### 一个Linux内核莫由以下几个部分组成 1. 模块加载函数 // 模块装载函数,正常返回0,错误时返回负数 // 错误编码使用 <linux/errno.h>中的定义 static int __init init_module(void){ return 0; } // 传入模块装载函数指针 module_init(init_module); 在linux内核程序中,可以通过代码动态装载其他模块 // request_module(const char * fmt, ...) request_module("hello"); 2. 模块卸载函数 3. 模块许可证声明 如不声明,模块装载时,会收到内核被污染(Kernel Tainted)的警告 4. 模块参数(可选) module\_param(参数名,参数类型,参数读写权限) // 模块参数类型:byte、short、ushort、int、uint、long、ulong、charp(字符指针)、bool、invbool static char *book_name = "Linux Device Driver"; // 定义一个字符传模块参数 module_param(book_name, charp, S_IRUGO); static int book_num = 4000; // 定义一个整型模块参数 module_param(book_num, int, S_IRUGO); // 定义一个数组参数 module_param_array(数组名,数组类型,数组长,参数读写权限); 模块装载时传入的值,相当于模块全局变量。 # 装载模块时传入参数insmod hello book_name=LinuxStudy book_num=100# 数组参数,使用逗号分隔每个元素insmod hello book_names=Linux,English,Java 模块参数读写权限,模块装载后,在/sys/module目录会出现以模块名命令的模块目录。在这目录下,会有可读写的模块参数节点。如 /sys/modules/hello/parameters/book_name/sys/modules/hello/parameters/book_num 模块参数完整实例,可装载查看效果 5. 模块导出符号(可选) 模块提供函数或变量(symbol)给其他模块使用 Linux内核符号表查看文件,记录了符号和符号所在内存地址 /proc/kallsyms 在模块程序中,可以用宏导出符号到内核符号表中: // 导出内核符号EXPORT_SYMBOL(符号名);// 导出内核符号(适用于包含GPL许可证的模块)EXPORT_SYMBOL_GPL(符号名); 如下模块导出整数加减运算函数符号 #include <linux/init.h>#include <linux/module.h>int add_integer(int a, int b){ return a + b;}int sub_integer(int a, int b){ return a - b;}EXPORT_SYMBOL_GPL(add_integer);EXPORT_SYMBOL_GPL(sub_integer);MODULE_LICENSE("GPL v2"); 在/proc/kallsyms文件中,可以找到这两个内核符号的相关信息 6. 模块声明与描述(可选) MODULE_AUTHOR(...); // 模块作者MODULE_DESCRIPTION(...); // 模块描述MODULE_VERSION(...); // 模块版本MODULE_DEVICE_TABLE(...); // 模块支持的设备表MODULE_ALIAS(...); // 模块别名 #### 4. 模块使用计数 #### /// Linux2.4中使用宏调用MOD_INC_USE_COUNTMOD_DES_USE_COUNT/// Linux2.6后使用接口调用// 获取模块次数,返回0表示模块未装载int try_module_get(struct module *module);// 增加模块次数void module_put(struct module *module); #### 5. 模块的编译 #### 编写Makefile文件,用make编译模块得到ko文件 KERVS = $(shell uname -r)# 内核模块(模块名称,若模块名称和C语言文件一致且只有一个C文件,则不需要额外定义其他C文件)obj-m += hello.o# 模块编译的特殊标志# 如下标志可以得到包含调试信息的hello.ko模块EXTRA_CFLAGS=-g -O0build: kernel_moduleskernel_modules: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modulesclean: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean 若模块包含多个C文件 # 模块名称obj-m := hello.o# 模块包含多个C文件,需要声明每个文件hello-objs : file1.c file2.c #### 6. 使用模块绕开GPL #### Linux内核中很多模块都是以GPL发布的,只有同样以GPL发布的模块才能调用以GPL发布的模块。也有方法可以绕过GPL 1. 修改模块代码 把EXPORT\_SYMBOL\_GPL改为EXPORT\_SYMBOL后重新发布模块 2. 使用wrapper模块,桥接 wrapper模块使用GPL发布,wrapper中使用EXPORT\_SYMBOL发布函数符号,在wrapper的函数符号中调用其他模块的以GPL发布的函数。
还没有评论,来说两句吧...