【Linux内核模块】导出符号详解:模块间的“资源共享”机制

发布时间:2026/5/20 16:17:23

【Linux内核模块】导出符号详解:模块间的“资源共享”机制 一、什么是内核模块导出符号1.1 模块间的资源共享想象一个工厂里有多个车间机械加工车间有精密车床函数电子车间有示波器变量装配车间需要使用车床加工零件但自己没有此时机械车间可以 导出 车床使用权让装配车间调用在内核中模块 A 可以将自己的函数或变量 导出供模块 B 使用这就是导出符号的核心思想。1.2 符号的本质在内核中符号Symbol就是函数或全局变量的名称。每个符号对应内存中的一个地址函数名 → 代码段中的起始地址变量名 → 数据段中的存储位置导出符号就是把这些名称和地址注册到一个公共表内核符号表中让其他模块可以通过名称找到并使用它们。内核通过符号表Symbol Table记录所有全局符号的地址信息分为两种类型静态符号仅在编译单元内可见如static修饰的函数全局符号可被其他模块访问需显式导出1.3 符号表结构通过readelf -s可查看模块符号表代码语言javascriptAI代码解释readelf -s hello.ko # 输出示例 # Num: Value Size Type Bind Ndx Name # 12: 00000000 4 FUNC GLOBAL DEFAULT 1 public_functionBind符号绑定类型LOCAL/GLOBAL/WEAKNdx段索引UND表示未定义Type符号类型FUNC/OBJECT/NOTYPE1.4 导出符号的两大应用场景功能复用避免多个模块重复实现相同功能如 CRC 校验算法分层设计底层模块提供基础功能上层模块调用如驱动框架与具体驱动二、导出符号的三要素定义、导出、使用要实现模块间符号共享必须掌握三个核心步骤定义符号→导出符号→使用符号。2.1 第一步定义符号函数或变量先在模块中定义要导出的函数或全局变量代码语言javascriptAI代码解释// 定义要导出的函数 int my_crc32(const unsigned char *buf, size_t len) { // CRC32计算实现 // ... return crc; } // 定义要导出的全局变量 int global_counter 0;注意函数不能是staticstatic会限制作用域为当前文件变量同理。2.2 第二步导出符号使用EXPORT_SYMBOL或EXPORT_SYMBOL_GPL宏导出符号代码语言javascriptAI代码解释// 导出函数 EXPORT_SYMBOL(my_crc32); // 导出变量 EXPORT_SYMBOL(global_counter);这两个宏的区别在于EXPORT_SYMBOL允许所有模块使用无论许可证EXPORT_SYMBOL_GPL仅允许 GPL 兼容许可证的模块使用推荐做法除非必要优先使用EXPORT_SYMBOL_GPL保证内核许可证纯洁性。2.3 第三步使用导出的符号在需要使用这些符号的模块中先声明符号类似extern再直接使用代码语言javascriptAI代码解释// 声明要使用的外部符号 extern int my_crc32(const unsigned char *buf, size_t len); extern int global_counter; // 在模块中使用 static int __init use_module_init(void) { int crc my_crc32(hello, 5); printk(CRC32值: %x\n, crc); global_counter; // 使用全局变量 printk(计数器值: %d\n, global_counter); return 0; }三、导出符号的工作原理内核符号表揭秘理解导出符号的工作原理才能更好地使用这个机制。3.1 内核符号表模块间的通讯录内核维护着一个全局的符号表本质是哈希表记录了所有导出符号的名称和地址。当模块 A 导出符号时符号名称和地址被添加到符号表其他模块可以通过符号名称查找对应的地址这个符号表在/proc/kallsyms中可见需要 root 权限代码语言javascriptAI代码解释$ sudo cat /proc/kallsyms | grep my_crc32 ffffffffc00080a0 T my_crc32其中ffffffffc00080a0是符号地址T表示该符号在代码段Text 段my_crc32是符号名称3.2 符号解析过程当模块 B 使用模块 A 导出的符号时内核会在模块加载时检查模块 B 引用的外部符号在内核符号表中查找这些符号的地址将模块 B 代码中对这些符号的引用替换为实际地址这个过程称为符号解析由内核在模块加载时自动完成。3.3 导出符号的生命周期导出时机模块加载时EXPORT_SYMBOL所在的初始化函数执行后生效范围模块加载后直到模块卸载前失效时机模块卸载时其导出的符号自动从符号表移除四、EXPORT_SYMBOL vs EXPORT_SYMBOL_GPL许可证的微妙差别这两个宏的核心区别在于许可证兼容性。1. EXPORT_SYMBOL无限制导出任何模块无论使用何种许可证都可以使用该符号适用于通用工具函数如 CRC 计算、字符串处理2. EXPORT_SYMBOL_GPLGPL 约束导出仅允许 GPL 兼容许可证的模块使用该符号适用于依赖 GPL 特定机制的函数如内核锁、文件系统 API使用该符号的模块必须声明MODULE_LICENSE(GPL)3. 违反许可证约束的后果如果非 GPL 模块使用了EXPORT_SYMBOL_GPL导出的符号编译时不会报错但加载模块时内核会警告Module taints kernel可能导致内核功能异常如无法正确获取锁违反 GPL 许可证条款存在法律风险总结除非必须开放给所有模块否则优先使用EXPORT_SYMBOL_GPL。五、实战示例模块间符号共享的完整流程下面通过一个具体例子演示如何实现模块间的符号共享。5.1 模块 A导出符号的模块math_helper.c代码语言javascriptAI代码解释#include linux/module.h #include linux/init.h // 定义要导出的函数 int add(int a, int b) { return a b; } int subtract(int a, int b) { return a - b; } // 定义要导出的全局变量 int operation_count 0; // 导出符号 EXPORT_SYMBOL(add); EXPORT_SYMBOL(subtract); EXPORT_SYMBOL(operation_count); static int __init math_helper_init(void) { printk(KERN_INFO 数学助手模块加载成功\n); return 0; } static void __exit math_helper_exit(void) { printk(KERN_INFO 数学助手模块卸载成功\n); } module_init(math_helper_init); module_exit(math_helper_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(数学计算辅助模块);5.2 模块 B使用导出符号的模块calculator.c代码语言javascriptAI代码解释#include linux/module.h #include linux/init.h // 声明要使用的外部符号 extern int add(int a, int b); extern int subtract(int a, int b); extern int operation_count; static int __init calculator_init(void) { int result; result add(5, 3); printk(KERN_INFO 5 3 %d\n, result); result subtract(5, 3); printk(KERN_INFO 5 - 3 %d\n, result); // 更新操作计数器 operation_count 2; printk(KERN_INFO 总操作次数: %d\n, operation_count); return 0; } static void __exit calculator_exit(void) { printk(KERN_INFO 计算器模块卸载成功\n); } module_init(calculator_init); module_exit(calculator_exit); MODULE_LICENSE(GPL); // 必须声明GPL兼容许可证 MODULE_DESCRIPTION(使用导出符号的计算器模块);5.3 编译 Makefile代码语言javascriptAI代码解释obj-m math_helper.o calculator.o all: make -C /lib/modules/$(shell uname -r)/build M$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M$(PWD) clean5.4 测试流程代码语言javascriptAI代码解释# 编译模块 make # 加载模块先加载导出符号的模块 sudo insmod math_helper.ko sudo insmod calculator.ko # 查看日志 dmesg | tail -n 5 [ 1234.567890] 数学助手模块加载成功 [ 1234.567900] 5 3 8 [ 1234.567910] 5 - 3 2 [ 1234.567920] 总操作次数: 2 [ 1234.567930] 计算器模块加载成功 # 卸载模块顺序与加载相反 sudo rmmod calculator sudo rmmod math_helper六、导出符号的高级用法符号版本控制当模块升级时可能会修改导出函数的参数或行为可能导致依赖模块出错。内核提供了符号版本控制机制来解决这个问题。

相关新闻