(3)内核模块间交互-内核模块符号导出
将符号sym(函数名或变量名)导出到内核全局符号表,所有内核模块(无论许可协议)均可访问。:仅将符号sym导出给遵循 GPL(或兼容 GPL)许可协议的模块,非 GPL 模块调用会导致内核拒绝加载(触发taint标记,提示许可不兼容)。两者的本质:编译时将符号的「名称 - 地址映射」写入模块的.symtab(符号表)和.modinfo(模块信息)段;模块加载时,内核将这些映射添加到全局符号表(ka
·
一、符号导出的背景与意义
Linux 内核驱动模块(.ko 文件)默认是独立的二进制单元,模块间无法直接访问彼此的函数或全局变量 —— 这是由内核模块的编译和加载机制决定的(每个模块单独编译为 ELF 文件,加载时动态链接到内核,但默认不暴露内部符号)。
然而,复杂驱动的设计常需要分层解耦(如底层硬件驱动提供基础操作接口,上层应用驱动调用这些接口),此时就需要通过「符号导出」机制,将模块中的函数或变量暴露到「内核全局符号表」中,供其他模块查询和调用。
二、核心机制:EXPORT_SYMBOL 与 EXPORT_SYMBOL_GPL
内核提供两个核心宏用于符号导出,均定义在 <linux/export.h> 中(<linux/module.h> 已包含该头文件,无需单独引用):
宏定义与本质
EXPORT_SYMBOL(sym):将符号sym(函数名或变量名)导出到内核全局符号表,所有内核模块(无论许可协议)均可访问。EXPORT_SYMBOL_GPL(sym):仅将符号sym导出给遵循 GPL(或兼容 GPL)许可协议的模块,非 GPL 模块调用会导致内核拒绝加载(触发taint标记,提示许可不兼容)。
两者的本质:编译时将符号的「名称 - 地址映射」写入模块的.symtab(符号表)和.modinfo(模块信息)段;模块加载时,内核将这些映射添加到全局符号表(kallsyms),供其他模块通过符号名查询地址并调用。
三、符号导出的使用规则
- 使用位置:在模块中定义函数 / 变量后,直接在定义下方添加导出宏(无需额外声明)。
- 符号类型:支持导出函数(如
int add(int a, int b))和全局变量(如int num),局部变量(static 修饰)无法导出(无全局可见性)。 - 许可限制:
- 若模块使用
EXPORT_SYMBOL_GPL导出符号,调用该符号的模块必须声明MODULE_LICENSE("GPL"),否则内核加载时会报错(disagrees about version of symbol symbol_name或tainted kernel)。 EXPORT_SYMBOL无许可限制,但非 GPL 模块调用时仍可能导致内核 taint(提示 “非开源模块使用开源符号”)。
四、实验验证:导出模块与导入模块的协作
以下通过两个示例模块,演示符号导出与跨模块调用的完整流程:一个模拟的 LED 控制器模块和一个使用该控制器的应用模块。
(1)场景设定
led_controller.ko(提供者模块):
- 这个模块模拟一个 LED 控制器。
- 它维护一个内部状态,比如当前亮着的 LED 数量。
- 它导出两个函数:
led_turn_on(int led_id): 模拟打开一个指定 ID 的 LED。led_get_status(void): 获取当前 LED 的总体状态(比如亮着的数量)。
led_application.ko(使用者模块):
- 这个模块是一个简单的应用,它使用
led_controller提供的功能。 - 在其初始化函数中,它会调用
led_turn_on来打开几个 LED。 - 然后调用
led_get_status来检查状态,并将结果打印到内核日志。
(2)代码实现
1. 提供者模块: led_controller.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
int led_turn_on(int led_id);
int led_get_status(void);
// 模拟LED控制器的内部状态
// 我们用一个简单的计数器表示当前亮着的LED数量
static int lit_led_count = 0;
/**
* led_turn_on - 模拟打开一个LED
* @led_id: 要打开的LED的ID (我们这里不实际使用ID,仅作演示)
*
* 返回值: 成功返回0
*/
int led_turn_on(int led_id)
{
// 简单的模拟:每次调用,亮灯数量加1
lit_led_count++;
printk(KERN_INFO "LED Controller: Turned on LED ID %d. Total lit LEDs: %d\n", led_id, lit_led_count);
return 0;
}
EXPORT_SYMBOL(led_turn_on); // 导出此函数
/**
* led_get_status - 获取LED控制器的当前状态
*
* 返回值: 当前亮着的LED数量
*/
int led_get_status(void)
{
printk(KERN_INFO "LED Controller: Reporting status. Total lit LEDs: %d\n", lit_led_count);
return lit_led_count;
}
EXPORT_SYMBOL_GPL(led_get_status); // 使用GPL导出,要求使用者模块也是GPL许可
static int __init led_controller_init(void)
{
printk(KERN_INFO "LED Controller Module loaded.\n");
lit_led_count = 0; // 初始化状态
return 0;
}
static void __exit led_controller_exit(void)
{
printk(KERN_INFO "LED Controller Module unloaded.\n");
}
module_init(led_controller_init);
module_exit(led_controller_exit);
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("A simple LED controller kernel module");
MODULE_AUTHOR("Your Name");
2. 使用者模块: led_application.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
// 声明要从LED控制器模块导入的函数
// 这些函数在链接时由内核动态解析
extern int led_turn_on(int led_id);
extern int led_get_status(void);
static int __init led_application_init(void)
{
int status;
printk(KERN_INFO "LED Application Module loaded. Starting...\n");
// 调用LED控制器模块提供的函数来执行操作
printk(KERN_INFO "LED Application: Trying to turn on LED 1...\n");
led_turn_on(1);
printk(KERN_INFO "LED Application: Trying to turn on LED 3...\n");
led_turn_on(3);
// 获取并打印当前状态
printk(KERN_INFO "LED Application: Querying current status...\n");
status = led_get_status();
printk(KERN_INFO "LED Application: Current status received: %d LEDs are lit.\n", status);
if (status > 1) {
printk(KERN_INFO "LED Application: Success! More than one LED is lit.\n");
}
return 0;
}
static void __exit led_application_exit(void)
{
printk(KERN_INFO "LED Application Module unloaded.\n");
}
module_init(led_application_init);
module_exit(led_application_exit);
// 由于我们调用了 EXPORT_SYMBOL_GPL 导出的函数,
// 这个模块也必须是 GPL 许可的
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("An application module using the LED controller");
MODULE_AUTHOR("Your Name");
(3)编译和运行步骤
1. 编写 Makefile:
obj-m += led_controller.o
obj-m += led_application.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
2. 编译模块:
make


3. 加载模块 (顺序至关重要):
- 先加载提供者
led_controller.ko - 再加载使用者
led_application.ko
sudo insmod led_controller.ko
sudo insmod led_application.ko
4. 查看内核日志:
sudo dmesg
你将会看到类似以下的输出:
[ 1115.398477] LED Controller Module loaded.
[ 1123.927323] LED Application Module loaded. Starting...
[ 1123.927336] LED Application: Trying to turn on LED 1...
[ 1123.927411] LED Controller: Turned on LED ID 1. Total lit LEDs: 1
[ 1123.927445] LED Application: Trying to turn on LED 3...
[ 1123.927447] LED Controller: Turned on LED ID 3. Total lit LEDs: 2
[ 1123.927450] LED Application: Querying current status...
[ 1123.927451] LED Controller: Reporting status. Total lit LEDs: 2
[ 1123.927453] LED Application: Current status received: 2 LEDs are lit.
[ 1123.927455] LED Application: Success! More than one LED is lit.

5. 卸载模块 (顺序也至关重要):
- 先卸载使用者
led_application.ko - 再卸载提供者
led_controller.ko
sudo rmmod led_application
sudo rmmod led_controller
再次查看 sudo dmesg 确认卸载信息。
更多推荐



所有评论(0)