一. 编写一个内核模块
1.1 什么是内核模块
Linux 内核是模块化组成的,它允许内核在运行时动态地向其中插入或从中删除代码。这些代码被一并组合在一个单独的二进制镜像中,即所谓的可装载内核模块中,或简称为模块。支持模块的好处是基本内核镜像尽可能的小,因为可选的功能和驱动程序可以利用模块形式再提供。模块允许我们方便地删除和重新载入内核代码,也方便了调试工作。而且当热插拔新设备时,可通过命令载入新的驱动程序。
模块是具有独立功能的程序,它可以被单独编译,但不能独立运行。它在运行时被链接到内核作为内核的一部分在内核空间运行,这与运行在用户空间的进程是不同的。模块通常由一组函数和数据结构组成,用来实现一种文件系统、一个驱动程序或其他内核上层的功能。总之,模块是一个为内核或其他内核模块提供使用功能的代码块。
1.2 最简单的内核模块
创建一个目录专门保存 linux 驱动代码,并开始编写第一个内核模块:
1 | user@ubuntu:~$ mkdir -p workspace/mini2440/driver.with.linux |
文件一:simplest_module.c
1 |
|
文件二:Makefile
1 | obj-m += simplest_module.o |
1.3 编译加载模块
执行 make 命令,编译内核模块。
1 | user@vmware:~/mini2440/driver.with.linux/module/simplest_module/$ make |
生成的 simplest_module.ko 文件就是可动态加载的内核模块。
使用 insmod 命令加载模块到内核中,将会执行 module_init(mod_init) 指定的 mod_init 函数。
1 | [root@FriendlyARM /mnt]# insmod simplest_module.ko |
使用 rmmod 命令卸载模块,将会执行 module_exit(mod_exit) 指定的 mod_exit 函数。
1 | [root@FriendlyARM /mnt]# rmmod simplest_module |
使用 modprobe -r 卸载模块,同 rmmod 功能。
1 | [root@FriendlyARM /mnt]# modprobe -r simplest_module |
二. 带参数的内核模块
2.1 前言
Linux 内核允许模块在加载的时候指定参数。模块接受参数传入的机制能够根据参数的不同提供多种不同的服务,增加了模块的灵活性。
模块参数必须使用 module_param 宏来声明,通常放在文件头部。 module_param 需要 3 个参数:变量名称、类型以及用于 sysfs 入口的访问掩码。模块最好为参数指定一个默认值,以防加载模块的时候忘记传参而带来错误。
- 内核模块支持的参数类型有: bool、 invbool、 charp、 int、 short、 long、 uint、 ushort 和 ulong。
- 访问掩码的值在
定义, S_IRUGO 表示任何人都可以读取该参数,但不能修改。
2.2 示例模块
文件一:param_module.c
1 |
|
文件二:Makefile
1 | obj-m += param_module.o |
2.3 加载模块
不带参数加载,加载失败,提示加载参数使用方法:
1 | [root@FriendlyARM /mnt]# insmod param_module.ko |
带参数加载,加载成功:
1 | [root@FriendlyARM /mnt]# insmod param_module.ko name="bean" age=20 |
三. 多个c文件编译成一个模块
3.1 前言
有的时候,一个驱动模块设计到功能过多,都写在一个文件中导致单个文件过大,文件结构复杂,不便于阅读和修改。那么如何将多个文件编译成为一个驱动模块呢?
单个文件 main.c 编译成模块,Makefile 中只需要指定 obj-m 为对应的 .o 文件即可。
1 | obj-m += main.o |
多个文件 tp_touch.c tp_gesture.c tp_firmware.c 编译成模块 tp_mstar.ko,需要这么干:
1 | obj-m += tp_mstar.o |
3.2 示例模块
文件一:tp_common.h
1 |
|
文件二:tp_touch.c
1 |
|
文件三:tp_firmware.c
1 |
|
文件四:tp_gesture.c
1 |
|
文件五:Makefile
1 | obj-m += tp_mstar.o |
3.3 加载模块
加载模块,不难发现,三个文件被编译成了一个模块,三个文件的 log 都打印出来了。
1 | [root@FriendlyARM /mnt]# insmod tp_mstar.ko |
四. 模块导出符号给另一个模块使用
4.1 前言
对于模块文件中定义的函数和变量,其他模块如果想要引用的话,被引用的函数或者变量需要使用 EXPORT_SYMBOL(x) 宏导出符号,其中 x 是导出的函数名或者变量名,而引用这个函数或者变量的模块做一下外部声明即可使用。
4.2 示例模块
文件一:test_module.c
1 |
|
文件二:calculate.c
1 |
|
文件三:Makefile
1 | obj-m += test_module.o |
4.3 加载模块
若一个模块(模块A)引用了另一个模块(模块B)导出的符号,那么要求加载模块A的时候,模块B就已经被加载进内核了。否则的话,模块A将无法成功加载进内核。
1 | [root@FriendlyARM /mnt]# insmod test_module.ko |
可以看出模块 test_module 没有加载成功,提示说找不到一些符号。
1 | [root@FriendlyARM /mnt]# insmod calculate.ko |
加载依赖的 calculate 模块之后,test_module 正常加载了。