Linux内核模块基础

主要涉及Linux设备驱动的一些基础知识, 小技巧等

什么是内核模块

内核模块指的是Linux内核代码以模块的形式存在, 可以动态加载和卸载, 也叫Linux设备驱动, 但一般Linux设备驱动
至少需要实现Linux设备中的一种设备, 所以并不是所有的内核模块都可以算得上是Linux设备驱动, 有些内核模块仅仅
包含一些逻辑或是算法代码, 并不会创建一个设备, 个人认为这样的内核模块并不能算得上是严格意义上的Linux设备驱动,
本文更多的偏向于介绍Linux设备驱动相关的知识且会不定时更新

内核模块文件(*.ko)

查看内核模块文件信息

modinfo命令可以查看ko的一些信息, 比如内核模块的Lisence、Author、Linux Version Magic、模块参数等等

查看和内核模块相关的信息

lsmod: 查看当前系统已安装的模块, 内部实现是读取的/proc/modules文件
cat /proc/device: 查看当前系统上的所有设备(字符设备和块设备)
/dev目录下存放的是当前系统里所有的设备文件

安装和加载

安装时需要指定文件的路径:insmod filename.ko
卸载时只需要指定模块名即可:rmmod module_name

特别的,安装内核模块可以使用命令modprobe, 与insmod命令不同的是modprobe会查看要加载的模块, 看是否它引用了当前内核没有定义的符号. 如果发现有, modprobe 在定义相关符号的当前模块搜索路径中寻找其他模块. 当 modprobe 找到这些模块( 要加载模块需要的 ), 它也把它们加载到内核. 如果你在这种情况下使用 insmod , 命令会失败, 在系统日志文件中留下一条 “ unresolved symbols “消息

内核模块编译

内核模块编译时需要内核源码树, 而不仅仅是内核头文件

对内核模块代码预处理

对内核代码预处理主要是需要找到内核代码编译时使用的命令, 获取这个命令的有两种方法

  1. 在内核模块目录make后找到.xxx.o.cmd文件, 打开后第一行就是xxx.c编译成xxx.o的命令
  2. 使用内核Makefile的变量V, 当定义V=1时, 内核makefile会输出详细编译过程, 然后找到xxx.c编译的命令即可

找到内核代码编译时的命令之后将里面的-c xxx.c -o xxx.o改成-E -P xxx.c -o xxx.i, 然后执行即可获得预处理之后的代码, 添加-P选项将不会展开头文件

内核模块在ISO C99标准下编译

ISO C90标准只允许变量声明在一个函数的开始处,
内核打印此警告只是因为gcc编译选项-Wdeclaration-after-statement, 这个编译选项在内核顶层Makefile里可以找到, 如下所示

# warn about C99 declaration after statement
KBUILD_CFLAGS += $(call cc-option,-Wdeclaration-after-statement,)

所以在内核模块的Makefile里将ccflags-y变量定义成-Wno-declaration-after-statement即可

ccflags-y变量在Documentation/kbuild/makefiles.txt里有说明
另外如果要使用C99,那么最好使用gnu99,因为内核的许多代码是依赖于gcc的扩展语法的

内核模块编写

需要包含的头文件

linux/module.h, 定义了module_init, module_exit等宏
linux/init.h, 定义了__init, __exit等数据段的宏

编写内核模块时需要注意的

内核中无法使用strerror来打印错误码对应的字符串
printk里面使用的KERN_ALERT等消息等级和消息字符串中间必须要有空格,否则dmesg程序可能无法正常解析这条信息的日志等级

内核符号表

在2.6版本的内核中符号表被统一管理,如果当前模块需要共享全局变量给其他模块使用,
则需要使用宏EXPORT_SYMBOL(var_name)或EXPORT_SYMBOL_GPL(var_name),
宏EXPORT_SYMBOL_GPL声明的符号只对GPL许可的模块可用,私有许可的模块无法使用此符号
另外,共享了全局变量给其他模块使用的模块一定要在使用了此全局变量的模块前面加载,否则
使用了此全局变量的模块会加载失败,dmesg提示Unknown symbol