Skip to content

内核模块

模块(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内核模块主要由以下几个部分组成:

  1. 模块加载函数
  2. 模块卸载函数
  3. 模块许可证声明
  4. 模块参数
  5. 模块导出符号
  6. 模块作者信息
  7. 模块描述信息

编写内核模块时,必须要包含的头文件为linux/module.hlinux/init.h

模块加载函数

模块加载函数以__init宏作为前缀,带有此标识的函数如果在编译时进入内核,在链接时会放在.init.text段中。

在模块加载失败时,应返回正确的值,同时释放在加载的过程中申请的资源。

模块卸载函数

模块卸载函数以__exit宏作为前缀,主要用于资源的释放。

模块参数

内核模块可以在加载、系统启动时或者系统运行时动态设置其参数。

模块参数以module_param(参数名,参数类型,读写权限)作为前缀,参数类型可以是byte、short、ushort、int、uint、long、ulong、charp、bool、invbool。参数数组形式为module_param_array(参数名,参数类型,数组长度,读写权限)

动态修改模块参数

static int num = 5;
module_param(num, int, S_IRUGO);
...

在加载模块时,可以通过insmod hello.ko num=10来修改num的值。

导出符号

模块导出符号以EXPORT_SYMBOL(符号名)作为前缀,导出的符号可以在其他模块中被访问。EXPORT_SYMBOL_GPL(符号名)导出的符号在模块加载时会受到GPL协议的约束。

如果需要引用别的模块导出的符号,则需要在 Makefile 中指定 KBUILD_EXTRA_SYMBOLS 变量,并将其他模块的.ko 文件所在的路径添加到该变量中。

模块的声明与描述

我们可以用MODULE_AUTHORMODULE_DESCRIPTIONMODULE_LICENSEMODULE_ALIASMODULE_DEVICE_TABLEMODULE_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命令的自动处理依赖关系很有用。