当前位置: 首页 > news >正文

【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 许可证验证流程​

当模块加载时,内核会:​

  1. 检查MODULE_LICENSE是否存在​
  2. 如果是 GPL 兼容许可证,标记模块为合规
  3. 如果是专有许可证或未声明,标记为污染内核(taint flag)​
  4. 根据合规性决定是否允许访问 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>")​
  • 版本号遵循语义化版本(主版本。次版本。修订号)​

模块声明看似简单,实则是模块开发的基础礼仪—— 清晰的声明能让内核正确识别模块,让管理员轻松管理模块,让其他开发者快速理解模块。​

http://www.xdnf.cn/news/1167427.html

相关文章:

  • nginx使用手册
  • 在easyui中如何自定义表格里面的内容
  • MCU中的总线桥是什么?
  • 分布在内侧内嗅皮层(MEC)的边界细胞对NLP中的深层语义分析的积极影响和启示
  • 深入浅出理解 TCP 与 UDP:网络传输协议的核心差异与应用
  • JMeter groovy 编译成.jar 文件
  • oracle里面concat函数用法,oracle wm_concat函数用法-
  • python学习-读取csv大文件
  • Apache Ignite实现无死锁特性
  • PHP与Web页面交互:从基础表单到AJAX实战
  • k8s:利用helm离线部署consul v1.21.2
  • 【菜狗学聚类】时间序列聚类主要方法—20250722
  • web3.0怎么入局
  • PePeOnTron上线 Binance Alpha:中文社区正走出自己的Web3之路
  • 内核协议栈源码阅读(一) ---驱动与内核交互
  • 进程优先级切换调度-进程概念(6)
  • Taro 网络 API 详解与实用案例
  • SecretFlow (3) --- 添加合作方并创建项目
  • JavaScript,发生异常,try...catch...finally处理,继续向上层调用者传递异常信息
  • RabbitMQ03——面试题
  • uniapp各大平台导航组件
  • 在 Ubuntu 22.04 上安装并优化 Nginx nginx入门操作 稍难,需要有一定理论 多理解 多实践
  • 《Uniapp-Vue 3-TS 实战开发》自定义时间选择
  • Kafka基础理论速通
  • IDEA全局Maven配置
  • 比特币技术简史 第六章:网络协议 - P2P网络、节点类型与消息传播
  • 未来趋势:LeafletJS 与 Web3/AI 的融合
  • Visual Studio Code 远端云服务器开发使用指南
  • (3)重定向 | 时间相关指令 | 文件查找 | 打包与压缩
  • FastDFS 6.11.0 单机环境搭建与测试(附 Nginx 集成)+ docker构建+k8s启动文件