
如果你见过内核模块的代码,肯定注意过那些以MODULE_开头的宏定义 ——MODULE_LICENSE("GPL")、MODULE_AUTHOR("Your Name")…… 这些看似不起眼的声明,其实是模块的身份证,记录着模块的关键信息。它们不仅能让内核识别模块的身份,还影响模块的功能权限。
想象你去参加一个技术会议,门口的签到表会记录你的姓名、公司、职位 —— 这些信息帮助主办方识别你的身份。内核模块加载时也一样,内核需要知道:
这些信息都通过模块声明来传递,缺少关键声明的模块,内核会拒绝接待(限制功能或直接加载失败)。
新手常犯的错误是觉得这些声明无关紧要,甚至省略MODULE_LICENSE。但实际上:

这是唯一必须的声明,它的作用就像软件的版权协议,决定了模块能使用内核的哪些功能。
常见许可证类型:
不声明许可证的后果:
加载模块时内核会报错:
module: module license 'unspecified' taints kernel.更严重的是,模块将无法调用EXPORT_SYMBOL_GPL导出的函数(如大部分内核核心功能),等于被降级使用。
格式:MODULE_AUTHOR("姓名或邮箱")
作用:记录模块开发者,方便问题追溯。比如:
MODULE_AUTHOR("Zhang San <zhangsan@example.com>");当模块出现 bug 时,内核日志会包含开发者信息,方便联系维护者。
格式:MODULE_DESCRIPTION("模块功能说明")
作用:用一句话描述模块的用途,比如:
MODULE_DESCRIPTION("USB to serial converter driver");管理员用modinfo查看时,能快速知道这个模块是干什么的,避免误删关键模块。
格式:MODULE_VERSION("x.y.z")
作用:标记模块版本,方便管理升级。比如:
MODULE_VERSION("1.2.3");当系统中存在多个版本的模块时,这个声明能帮你区分它们。
格式:MODULE_ALIAS("别名")
作用:给模块起个别名,方便modprobe自动加载。比如:
MODULE_ALIAS("usb:v1234p5678d*dc*dsc*dp*ic*isc*ip*in*");这个别名表示该模块支持 USB 厂商 ID 为 1234、产品 ID 为 5678 的设备,当系统插入该设备时,modprobe会自动加载此模块。
除了核心声明,还有一些扩展声明能让模块信息更丰富,管理更方便。
用于驱动模块声明支持的设备,格式因总线类型而异。以 USB 驱动为例:
static const struct usb_device_id my_usb_id_table[] = {
{ USB_DEVICE(0x1234, 0x5678) }, // 支持的设备ID
{ } // 结束标记
};
MODULE_DEVICE_TABLE(usb, my_usb_id_table);这个声明会让内核知道模块支持哪些设备,当对应的设备插入时,自动加载模块。
格式:MODULE_SUPPORTED_DEVICE("设备类型描述")
作用:简要说明模块支持的设备,比如:
MODULE_SUPPORTED_DEVICE("USB to RS232 converters");主要用于文档说明,不影响实际功能。
格式:MODULE_INFO(键, "值")
作用:添加自定义元信息,比如:
MODULE_INFO(version, "1.2.3");
MODULE_INFO(release_date, "2024-05-20");这些信息会被modinfo识别并显示,适合添加版本日期、测试状态等自定义内容。
模块声明的所有信息,都可以用modinfo命令查看,这是管理员管理模块的常用工具。
编译模块后,执行:
modinfo 模块名.ko比如查看一个名为usb_serial.ko的模块:
modinfo usb_serial.kofilename: /path/to/usb_serial.ko
license: GPL v2
author: Zhang San <zhangsan@example.com>
description: USB to serial converter driver
version: 1.2.3
alias: usb:v1234p5678d*dc*dsc*dp*ic*isc*ip*in*
supported: external
depends:
intree: N
vermagic: 5.4.0-100-generic SMP mod_unload 每一行对应一个模块声明:
当模块加载时,内核会:
这个过程确保内核的 GPL 许可证条款不被违反,也是为什么闭源驱动常被内核社区抵制的原因。
模块声明的信息被编译到.modinfo段(模块的一个特殊数据段)中,不影响模块的代码执行,只用于信息展示和内核验证。
可以用objdump命令查看这个段的内容:
objdump -s -j .modinfo 模块名.ko会看到类似这样的输出(二进制数据,包含所有声明信息)。
modinfo显示的depends字段(依赖模块),虽然不是通过MODULE_宏声明的,但内核会根据模块引用的符号自动生成这个列表。比如模块 A 使用了模块 B 导出的符号,depends就会显示模块 B 的名称。
下面通过一个完整的模块示例,展示如何正确使用各种声明:
1. 模块代码(serial_driver.c)
#include <linux/module.h>
#include <linux/init.h>
#include <linux/usb.h>
// 驱动支持的USB设备ID表
static const struct usb_device_id serial_id_table[] = {
{ USB_DEVICE(0x0403, 0x6001) }, // FTDI设备ID
{ USB_DEVICE(0x1a86, 0x7523) }, // 国产CH340设备ID
{ } // 结束标记
};
MODULE_DEVICE_TABLE(usb, serial_id_table);
// 模块初始化函数
static int __init serial_init(void) {
printk(KERN_INFO "Serial driver loaded\n");
return 0;
}
// 模块退出函数
static void __exit serial_exit(void) {
printk(KERN_INFO "Serial driver unloaded\n");
}
// 入口/出口声明
module_init(serial_init);
module_exit(serial_exit);
// 核心声明
MODULE_LICENSE("GPL v2"); // 必须的许可证声明
MODULE_AUTHOR("Li Si <lisi@example.com>"); // 开发者信息
MODULE_DESCRIPTION("USB to TTL serial converter driver"); // 功能描述
MODULE_VERSION("2.1.0"); // 版本号
// 扩展声明
MODULE_ALIAS("usb:v0403p6001d*dc*dsc*dp*ic*isc*ip*in*");
MODULE_ALIAS("usb:v1a86p7523d*dc*dsc*dp*ic*isc*ip*in*");
MODULE_INFO(release_date, "2025-07-01"); // 自定义发布日期
MODULE_SUPPORTED_DEVICE("FTDI/CH340 USB to Serial adapters");2. 编译 Makefile
obj-m += serial_driver.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean3. 编译并查看声明
# 编译模块
make
# 查看声明信息
modinfo serial_driver.ko输出结果会包含所有声明的信息,比如:
license: GPL v2
author: Li Si <lisi@example.com>
description: USB to TTL serial converter driver
version: 2.1.0
alias: usb:v0403p6001d*dc*dsc*dp*ic*isc*ip*in*
alias: usb:v1a86p7523d*dc*dsc*dp*ic*isc*ip*in*
supported: FTDI/CH340 USB to Serial adapters
release_date: 2024-06-011. 忘记声明MODULE_LICENSE
症状:加载模块时内核警告「tainted kernel」,且无法使用某些功能。
解决:立即添加MODULE_LICENSE("GPL")(或其他兼容许可证),重新编译模块。
2. 许可证与符号不兼容
症状:模块使用了EXPORT_SYMBOL_GPL导出的符号,但声明了非 GPL 许可证,加载失败。
解决:将许可证改为 GPL 兼容类型(如GPL、LGPL)。
3. 声明信息错误或过时
症状:modinfo显示的设备 ID 与实际支持的设备不符,导致设备无法识别。
解决:更新MODULE_DEVICE_TABLE和MODULE_ALIAS,确保与硬件 ID 匹配。
4. 过度声明
症状:添加了大量无关的MODULE_INFO,导致modinfo输出混乱。
解决:只保留必要的声明,自定义信息按需添加(如版本、发布日期)。
1. 必选声明清单
2. 推荐声明清单
3. 风格建议
模块声明看似简单,实则是模块开发的基础礼仪—— 清晰的声明能让内核正确识别模块,让管理员轻松管理模块,让其他开发者快速理解模块。