驱动模块--内核模块
内核模块宏都有什么,分别有什么作用?
1.__init的作用: 展开后为:__attribute__((__section_(".init.text")))实际是gcc的一个特殊链接标记
指示链接器将该函数放置在.init.text区段,在模块插入时方便内核从ko文件指定位置读取入口函数的指令到特定内存位置
2.__exit的作用:展开后为:__attribute__ ((__section__ (".exit.text"))) 实际也是gcc的一个特殊链接标记,指示链接器将该函数放置在.exit.text区段,在模块插入时方便内核从ko文件指定位置读取出口函数的指令到另一个特定内存位置;
3.module_init 宏,静态加载模块,内核启动过程中对应函数被调用; 对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.initcall段),方便系统初始化统一调用。对于动态加载的模块,由于内核模块的默认入口函数名是init_module,用该宏可以给对应模块入口函数起别名。
4.用法:module_exit(模块出口函数名)
动态加载的模块在卸载时,对应函数被调用
对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.exitcall段),方便系统必要时统一调用,实际上该宏在静态加载时没有意义,因为静态编译的驱动无法卸载;对于动态加载的模块,由于内核模块的默认出口函数名是cleanup_module,用该宏可以给对应模块出口函数起别名。
内核模块的插入:
1.编译好test.c
2.修改Makefile obj-M += test.o
3.make
4.sudo dmesg -C 不显示 没改动的内核代码
5.sudo insmod ./test.o
6.dmesg 显示新加内核功能信息
7.sudo rmmod test 移除模块test
内核依赖
既然内核模块的代码与其它内核代码共用统一的运行环境,也就是说模块只是存在形式上独立,运行上其实和内核其它源码是一个整体,它们隶属于同一个程序,因此一个模块或内核其它部分源码应该可以使用另一个模块的一些全局特性。
一个模块中这些可以被其它地方使用的名称被称为导出符号,所有导出符号被填在同一个表中这个表被称为符号表。
最常用的可导出全局特性为全局变量和函数
查看符号表的命令:nm nm查看elf格式的可执行文件或目标文件中包含的符号表,用法:
nm 文件名
(可以通过man nm查看一些字母含义)
两个用于导出模块中符号名称的宏:
EXPORT_SYMBOL(函数名或全局变量名) EXPORT_SYMBOL_GPL(函数名或全局变量名) 需要GPL许可证协议验证
使用导出符号的地方,需要对这些符号进行extern声明后才能使用这些符号
B模块使用了A模块导出的符号,此时称B模块依赖于A模块,则:
-
编译次序:先编译模块A,再编译模块B,当两个模块源码在不同目录时,需要:i. 先编译导出符号的模块A ii. 拷贝A模块目录中的Module.symvers到B模块目录 iii. 编译使用符号的模块B。否则编译B模块时有符号未定义错误
-
加载次序:先插入A模块,再插入B模块,否则B模块插入失败
-
卸载次序:先卸载B模块,在卸载A模块,否则A模块卸载失败
补充说明: 内核符号表(直接当文本文件查看) /proc/kallsyms运行时 /boot/System.map编译后