一、符号导出的背景与意义

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),供其他模块通过符号名查询地址并调用。

三、符号导出的使用规则

  1. 使用位置:在模块中定义函数 / 变量后,直接在定义下方添加导出宏(无需额外声明)。
  2. 符号类型:支持导出函数(如int add(int a, int b))和全局变量(如int num),局部变量(static 修饰)无法导出(无全局可见性)。
  3. 许可限制
  • 若模块使用EXPORT_SYMBOL_GPL导出符号,调用该符号的模块必须声明MODULE_LICENSE("GPL"),否则内核加载时会报错(disagrees about version of symbol symbol_nametainted kernel)。
  • EXPORT_SYMBOL无许可限制,但非 GPL 模块调用时仍可能导致内核 taint(提示 “非开源模块使用开源符号”)。

四、实验验证:导出模块与导入模块的协作

以下通过两个示例模块,演示符号导出与跨模块调用的完整流程:一个模拟的 LED 控制器模块和一个使用该控制器的应用模块

(1)场景设定

  1. led_controller.ko (提供者模块)
  • 这个模块模拟一个 LED 控制器。
  • 它维护一个内部状态,比如当前亮着的 LED 数量。
  • 它导出两个函数:
    • led_turn_on(int led_id): 模拟打开一个指定 ID 的 LED。
    • led_get_status(void): 获取当前 LED 的总体状态(比如亮着的数量)。
  1. 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 确认卸载信息。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐