【Linux内核模块】模块声明与描述
如果你见过内核模块的代码,肯定注意过那些以MODULE_开头的宏定义 ——MODULE_LICENSE("GPL")、MODULE_AUTHOR("Your Name")…… 这些看似不起眼的声明,其实是模块的身份证,记录着模块的关键信息。它们不仅能让内核识别模块的身份,还影响模块的功能权限。
目录
一、为什么需要模块声明?
1.1 内核需要认识模块
1.2 声明不是可有可无的装饰
1.3 模块声明的三大作用
二、核心声明:模块的法律文件
2.1 MODULE_LICENSE:许可证声明(必须有!)
2.2 MODULE_AUTHOR:开发者信息(推荐)
2.3 MODULE_DESCRIPTION:功能描述(推荐)
2.4 MODULE_VERSION:版本信息(推荐)
2.5 MODULE_ALIAS:别名(可选)
三、扩展声明:让模块信息更完整
3.1 MODULE_DEVICE_TABLE:设备匹配表(驱动模块必备)
3.2 MODULE_SUPPORTED_DEVICE:支持的设备类型(可选)
3.3 MODULE_INFO:自定义信息(灵活扩展)
四、如何查看模块声明信息?—— modinfo命令实战
4.1 基本用法
4.2 输出结果解析
4.3 实用场景
五、声明的潜规则:内核如何处理这些信息?
5.1 许可证验证流程
5.2 声明的存储位置
5.3 声明与模块依赖
六、实战示例:带完整声明的模块代码
七、常见错误与避坑指南
八、声明的最佳实践:写出规范的模块信息
一、为什么需要模块声明?
1.1 内核需要认识模块
想象你去参加一个技术会议,门口的签到表会记录你的姓名、公司、职位 —— 这些信息帮助主办方识别你的身份。内核模块加载时也一样,内核需要知道:
- 这个模块遵循什么许可证(是否允许使用内核的 GPL 符号)
- 谁开发的(出问题时找谁)
- 模块的功能是什么(是否与其他模块冲突)
这些信息都通过模块声明来传递,缺少关键声明的模块,内核会拒绝接待(限制功能或直接加载失败)。
1.2 声明不是可有可无的装饰
新手常犯的错误是觉得这些声明无关紧要,甚至省略MODULE_LICENSE。但实际上:
- 没声明许可证的模块会被内核标记为污染内核(tainted kernel)
- 某些内核功能(如EXPORT_SYMBOL_GPL导出的符号)会对未声明 GPL 的模块关闭
- 缺少描述信息的模块,管理员用modinfo查看时会一脸茫然
1.3 模块声明的三大作用
二、核心声明:模块的法律文件
2.1 MODULE_LICENSE:许可证声明(必须有!)
这是唯一必须的声明,它的作用就像软件的版权协议,决定了模块能使用内核的哪些功能。
常见许可证类型:
- MODULE_LICENSE("GPL"):最常用,遵循 GPL 协议,可使用内核中所有符号(包括EXPORT_SYMBOL_GPL导出的)。
- MODULE_LICENSE("GPL v2"):明确指定 GPL 版本 2,兼容性更好。
- MODULE_LICENSE("Dual BSD/GPL"):双许可证,既可以按 BSD 也可以按 GPL 协议使用。
- MODULE_LICENSE("Proprietary"):专有许可证(闭源),会被内核标记为污染,且无法使用 GPL-only 符号。
不声明许可证的后果:
加载模块时内核会报错:
module: module license 'unspecified' taints kernel.
更严重的是,模块将无法调用EXPORT_SYMBOL_GPL导出的函数(如大部分内核核心功能),等于被降级使用。
2.2 MODULE_AUTHOR:开发者信息(推荐)
格式:MODULE_AUTHOR("姓名或邮箱")
作用:记录模块开发者,方便问题追溯。比如:
MODULE_AUTHOR("Zhang San <zhangsan@example.com>");
当模块出现 bug 时,内核日志会包含开发者信息,方便联系维护者。
2.3 MODULE_DESCRIPTION:功能描述(推荐)
格式:MODULE_DESCRIPTION("模块功能说明")
作用:用一句话描述模块的用途,比如:
MODULE_DESCRIPTION("USB to serial converter driver");
管理员用modinfo查看时,能快速知道这个模块是干什么的,避免误删关键模块。
2.4 MODULE_VERSION:版本信息(推荐)
格式:MODULE_VERSION("x.y.z")
作用:标记模块版本,方便管理升级。比如:
MODULE_VERSION("1.2.3");
当系统中存在多个版本的模块时,这个声明能帮你区分它们。
2.5 MODULE_ALIAS:别名(可选)
格式:MODULE_ALIAS("别名")
作用:给模块起个别名,方便modprobe自动加载。比如:
MODULE_ALIAS("usb:v1234p5678d*dc*dsc*dp*ic*isc*ip*in*");
这个别名表示该模块支持 USB 厂商 ID 为 1234、产品 ID 为 5678 的设备,当系统插入该设备时,modprobe会自动加载此模块。
三、扩展声明:让模块信息更完整
除了核心声明,还有一些扩展声明能让模块信息更丰富,管理更方便。
3.1 MODULE_DEVICE_TABLE:设备匹配表(驱动模块必备)
用于驱动模块声明支持的设备,格式因总线类型而异。以 USB 驱动为例:
static const struct usb_device_id my_usb_id_table[] = {{ USB_DEVICE(0x1234, 0x5678) }, // 支持的设备ID{ } // 结束标记
};
MODULE_DEVICE_TABLE(usb, my_usb_id_table);
这个声明会让内核知道模块支持哪些设备,当对应的设备插入时,自动加载模块。
3.2 MODULE_SUPPORTED_DEVICE:支持的设备类型(可选)
格式:MODULE_SUPPORTED_DEVICE("设备类型描述")
作用:简要说明模块支持的设备,比如:
MODULE_SUPPORTED_DEVICE("USB to RS232 converters");
主要用于文档说明,不影响实际功能。
3.3 MODULE_INFO:自定义信息(灵活扩展)
格式:MODULE_INFO(键, "值")
作用:添加自定义元信息,比如:
MODULE_INFO(version, "1.2.3");
MODULE_INFO(release_date, "2024-05-20");
这些信息会被modinfo识别并显示,适合添加版本日期、测试状态等自定义内容。
四、如何查看模块声明信息?—— modinfo命令实战
模块声明的所有信息,都可以用modinfo命令查看,这是管理员管理模块的常用工具。
4.1 基本用法
编译模块后,执行:
modinfo 模块名.ko
比如查看一个名为usb_serial.ko的模块:
modinfo usb_serial.ko
4.2 输出结果解析
filename: /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
每一行对应一个模块声明:
- license → MODULE_LICENSE
- author → MODULE_AUTHOR
- description → MODULE_DESCRIPTION
- version → MODULE_VERSION
- alias → MODULE_ALIAS
4.3 实用场景
- 检查模块是否声明了正确的许可证(避免权限问题)
- 确认模块版本(排查版本不兼容问题)
- 查看模块支持的设备(判断是否匹配当前硬件)
- 查找模块作者(报告 bug 或寻求帮助)
五、声明的潜规则:内核如何处理这些信息?
5.1 许可证验证流程
当模块加载时,内核会:
- 检查MODULE_LICENSE是否存在
- 如果是 GPL 兼容许可证,标记模块为合规
- 如果是专有许可证或未声明,标记为污染内核(taint flag)
- 根据合规性决定是否允许访问 GPL-only 符号
这个过程确保内核的 GPL 许可证条款不被违反,也是为什么闭源驱动常被内核社区抵制的原因。
5.2 声明的存储位置
模块声明的信息被编译到.modinfo段(模块的一个特殊数据段)中,不影响模块的代码执行,只用于信息展示和内核验证。
可以用objdump命令查看这个段的内容:
objdump -s -j .modinfo 模块名.ko
会看到类似这样的输出(二进制数据,包含所有声明信息)。
5.3 声明与模块依赖
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) modulesclean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
3. 编译并查看声明
# 编译模块
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-01
七、常见错误与避坑指南
1. 忘记声明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. 必选声明清单
- ✅ MODULE_LICENSE:至少声明一个许可证(推荐 GPL v2)
- ✅ MODULE_DESCRIPTION:一句话说清模块功能
- ✅ 驱动模块必须加MODULE_DEVICE_TABLE:关联支持的硬件 ID
2. 推荐声明清单
- MODULE_AUTHOR:方便他人联系开发者
- MODULE_VERSION:便于版本管理
- MODULE_ALIAS:支持自动加载(尤其是驱动模块)
3. 风格建议
- 许可证声明放在所有声明的最前面(醒目)
- 描述信息简洁明了(不超过 50 个字符)
- 作者信息包含邮箱(如"Name <email>")
- 版本号遵循语义化版本(主版本。次版本。修订号)
模块声明看似简单,实则是模块开发的基础礼仪—— 清晰的声明能让内核正确识别模块,让管理员轻松管理模块,让其他开发者快速理解模块。