一个Linux驱动工程师必知的内核模块知识
引言:
在Linux操作系统中,内核模块是扩展内核功能的重要组成部分。了解如何编写和使用内核模块是每个Linux驱动工程师的基本技能。本文将通过诙谐的语言和实例,详细介绍内核模块的基本概念、加载和卸载方法、以及如何处理内核参数等实用技巧。通过对这些知识的学习,可以更深入地理解Linux系统的内部工作原理,为开发复杂的驱动程序打下坚实的基础。
相关问题1:内核模块中的打印函数和标准库函数有什么不同?
相关答案1:在内核模块中,常用的打印函数是printk,它类似于标准库中的printk,但是不支持浮点数的打印,这通常会导致编译时的警告和模块加载失败。而应用程序可以自由使用标准库函数,这是因为内核实际上是运行在一个受限的环境中,需要保证高度的稳定性和安全性。
相关问题2:如何在模块中声明和使用模块参数?
相关答案2:模块参数可以通过module_param和module_param_array宏来声明,这些参数可以是布尔型、字符串指针、整数等类型,并且可以在加载模块时通过命令行传入。这使得模块更加灵活,可以根据不同的运行环境进行配置。
相关问题3:内核模块如何与外部设备通信?
相关答案3:内核模块通常通过设备文件与外部设备进行通信。每个设备文件都有一个主次设备号,应用程序可以通过这些设备文件与内核模块进行数据交换。此外,还可以使用中断、DMA等方式来提高数据的传输效率。
通过上述的介绍,我们可以看到,编写和使用内核模块是一项既重要又复杂的技术活动。它不仅需要深厚的编程技能,还需要对操作系统的底层机制有深刻的理解。希望通过本文的介绍,读者能够对Linux内核模块有更全面的认识,并在实际工作中运用这些知识来解决具体的问题。
最简单的驱动
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>static int __init my_init(void)
{printk("my_init\\n");return 0;
}static void __exit my_exit(void)
{printk("my_exit\\n");
}module_init(my_init);
module_exit(my_exit);
加载卸载模块命令
模块加载
insmod
:加载指定目录下的一个.ko文件到内核。例如:
# insmod drv.ko
modprob
:自动加载模块到内核,相对于insmod来讲更智能。在执行该命令前最好运行一次depmod命令来更新模块的依赖信息,使用modprobe不指定路径和后缀,例如:
# depmod
# modprobe drv
模块卸载
rmmod
:卸载模块。例如:
# rmmod drv
模块信息
modinfo
:查看模块的信息。例如:
# modinfo drv
filename: /lib/modules/3.13.0-32-generic/drv.ko
srcversion: 533BB7E5866E52F63B9ACCB
depends:
vermagic: 3.13.0-32-generic SMP mod_unload modversions 686
多个源文件编译生成一个内核模块
例如,将hello.c和world.c两个c文件编译生成一个叫hello_world.o的目标文件,则在Makefile
中添加以下两句:
obj-m := hello_world.o
hello_world-objs = hello.c world.c
资料直通车:Linux内核源码技术学习路线+视频教程内核源码
学习直通车:Linux内核源码内存调优文件系统进程管理设备驱动/网络协议栈
内核污染
insmod
ko模块时,可能出现如下提示:
loading out-of-tree module taints kernel
几个可能原因:
- 模块没有声明
GPL
协议 - 当前linux内核版本和编译模块使用的内核版本不一致
- 使用内核源代码未包含的树外模块
printk和printf
在内核中的打印函数是printk
,printk
和printf
的行为非常相似,但是通常printk不支持浮点数,例如要打印一个浮点变量,在编译时通常会出现如下警告,并且模块也不会加载成功:
WARNING:"__extendsfdf2" [/home/ubuntu/driver/user.ko] undefined!
WARNING:"__truncdfsf2" [/home/ubuntu/driver/user.ko] undefined!
WARNING:"__divdf32" [/home/ubuntu/driver/user.ko] undefined!
WARNING:"__floatsidf" [/home/ubuntu/driver/user.ko] undefined!
内核模块参数
在加载一个.ko模块时,也可以像应用程序那样,通过命令行传入一些参数,这个过程发生在调用模块初始化函数之前。
内核支持的参数类型有:bool
、invbool
(反转值bool类型)、charp
(字符串指针)、short
、int
、long
、ushort
、uint
、ulong
。这些类型又可以复合成对应的数组类型。
具体用法,在驱动中定义三个变量baudrate
、port
、name
:
static int baudrate = 9600;
static int port[4] = {0,1,2,3};
static char *name = "user";module_param(baudrate, int, S_IRUGO);
module_param_array(port, int, NULL, S_IRUGO);
module_param(name, charp, S_IRUGO);
使用module_param
和module_param_array
宏声明这些变量为模块参数。说明:
module_param(name,type,perm)
module_param_array(name,type,nump,perm)
name
:变量的名字
type
:变量或数组元素的类型
nump
:数组元素个数的指针,可选
perm
:在sysfs文件系统中对应文件的权限属性。
权限的取值参考<linux/stat.h>
头文件。
修改这三个变量的值,即加载模块时传参:
insmod user.ko baudrate=115200 port=1,2,3,4 name="virtual-serial"
C库
内核模块处于C函数库之下,自然就不能调用C库函数(内核源码会实现类似的函数).
而应用程序则可以随意调用C库函数。