内核模块¶
模块(modules)是一种特殊的机制,可以不编译进内核而动态加载,使得内核的维护与开发变得更加灵活。
最简单的内核模块hello.c
#include <linux/modul.h\>
#include <linux/init.h\>
static int __init hello_init(void) {
printk(KERN_INFO "Hello world init\n");
return 0;
}
static void __exit hello_exit(void) {
printk(KERN_INFO "Hello world exit\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tom");
MODULE_DESCRIPTION("A simple hello world module");
这个模块只包含了模块的加载和卸载函数,编译后产生hello.ko目标文件,通过insmod
命令加载到内核,输出"Hello world init";rmmod
命令卸载,输出"Hello world exit"。
使用modprobe
命令除了加载该模块,还会将依赖一并加载。
使用modinfo
命令可以获得模块的信息。
模块程序结构¶
一个Linux内核模块主要由以下几个部分组成:
- 模块加载函数
- 模块卸载函数
- 模块许可证声明
- 模块参数
- 模块导出符号
- 模块作者信息
- 模块描述信息
编写内核模块时,必须要包含的头文件为linux/module.h
和linux/init.h
。
模块加载函数¶
模块加载函数以__init
宏作为前缀,带有此标识的函数如果在编译时进入内核,在链接时会放在.init.text段中。
在模块加载失败时,应返回正确的值,同时释放在加载的过程中申请的资源。
模块卸载函数¶
模块卸载函数以__exit
宏作为前缀,主要用于资源的释放。
模块参数¶
内核模块可以在加载、系统启动时或者系统运行时动态设置其参数。
模块参数以module_param(参数名,参数类型,读写权限)
作为前缀,参数类型可以是byte、short、ushort、int、uint、long、ulong、charp、bool、invbool。参数数组形式为module_param_array(参数名,参数类型,数组长度,读写权限)
。
动态修改模块参数
在加载模块时,可以通过insmod hello.ko num=10
来修改num的值。
导出符号¶
模块导出符号以EXPORT_SYMBOL(符号名)
作为前缀,导出的符号可以在其他模块中被访问。EXPORT_SYMBOL_GPL(符号名)
导出的符号在模块加载时会受到GPL协议的约束。
如果需要引用别的模块导出的符号,则需要在 Makefile 中指定 KBUILD_EXTRA_SYMBOLS 变量,并将其他模块的.ko 文件所在的路径添加到该变量中。
模块的声明与描述¶
我们可以用MODULE_AUTHOR
、MODULE_DESCRIPTION
、MODULE_LICENSE
、MODULE_ALIAS
、MODULE_DEVICE_TABLE
、MODULE_VERSION
分别声明模块的作者、描述、许可证、别名、设备表、版本号。
模块的编译¶
一个典型的Makefile文件示例如下:
ifneq ($(KERNELRELEASE),)
obj-m := hello.o
else
PWD := $(shell pwd)
KDIR :=/lib/modules/$(shell uname -r)/build
all:
$(MAKE) -C $(KDIR) M=$(PWD)
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
endif
模块管理工具¶
列出模块¶
可以使用lsmod
命令列出列出所有内核模块,包括已加载和未加载的模块:
$ lsmod
Module Size Used by
arptable_filter 12702 1
xt_nat 12681 1
macvlan 19239 0
veth 13458 0
xt_CT 12956 2
nf_log_ipv4 12767 0
nf_log_common 13317 1 nf_log_ipv4
xt_LOG 12690 0
xt_limit 12711 0
iptable_raw 12678 1
ip_set_hash_net 36021 2
vxlan 53857 0
ip6_udp_tunnel 12755 1 vxlan
udp_tunnel 14423 1 vxlan
...
或使用find
命令查看所有可用模块:
$ find /lib/modules/$(uname -r)/ -name '*.ko'
/lib/modules/5.15.0-117-generic/kernel/drivers/media/radio/si470x/radio-si470x-usb.ko
/lib/modules/5.15.0-117-generic/kernel/drivers/media/radio/si470x/radio-si470x-i2c.ko
/lib/modules/5.15.0-117-generic/kernel/drivers/media/radio/tea575x.ko
/lib/modules/5.15.0-117-generic/kernel/drivers/media/radio/tef6862.ko
/lib/modules/5.15.0-117-generic/kernel/drivers/media/radio/radio-raremono.ko
/lib/modules/5.15.0-117-generic/kernel/drivers/media/radio/radio-si476x.ko
/lib/modules/5.15.0-117-generic/kernel/drivers/media/radio/radio-tea5764.ko
/lib/modules/5.15.0-117-generic/kernel/drivers/media/radio/radio-wl1273.ko
/lib/modules/5.15.0-117-generic/kernel/drivers/media/radio/radio-mr800.ko
/lib/modules/5.15.0-117-generic/kernel/drivers/media/radio/shark2.ko
...
显示模块信息¶
modinfo
命令提供有关内核模块的信息,包括作者、描述、许可证、参数等:
$ modinfo usbhid
filename: /lib/modules/5.15.0-117-generic/kernel/drivers/hid/usbhid/usbhid.ko
license: GPL
description: USB HID core driver
author: Jiri Kosina
author: Vojtech Pavlik
author: Andreas Gal
srcversion: 541F12A52D5FDDAF4E19777
alias: usb:v*p*d*dc*dsc*dp*ic03isc*ip*in*
depends: hid
retpoline: Y
intree: Y
name: usbhid
vermagic: 5.15.0-117-generic SMP mod_unload modversions
sig_id: PKCS#7
signer: Build time autogenerated kernel key
sig_key: 45:00:3B:43:A0:87:2F:E9:C1:F9:9B:0C:A2:C6:3D:78:F7:15:94:98
sig_hashalgo: sha512
...
检查依赖关系¶
modules.dep 文件记录了模块之间的依赖关系,可以直接用grep
命令搜索指定模块的依赖。也可以使用modprobe
命令:
$ modprobe --show-depends usbhid
insmod /lib/modules/5.15.0-117-generic/kernel/drivers/hid/hid.ko
insmod /lib/modules/5.15.0-117-generic/kernel/drivers/hid/usbhid/usbhid.ko
加载模块¶
modprobe
命令是最常用的加载模块的工具,它会自动处理模块依赖关系,而insmod
命令则不会。
如果需要在 Linux 启动时自动加载模块,可以将模块的名称添加到/etc/modules
文件中。
删除模块¶
可以使用modprobe -r
命令删除模块,它会自动处理模块依赖关系。也可以使用rmmod
命令。
生成依赖关系¶
depmod
命令用于生成 modules.dep 文件,这些文件对于modprobe
命令的自动处理依赖关系很有用。