LinuxEXPORT_SYMBOL宏的使用

本文介绍了Linux内核中EXPORT_SYMBOL宏的使用方法及其作用。详细解释了如何通过此宏使函数或变量对整个内核代码可见,并提供了具体示例,包括模块间的函数调用流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、前言

EXPORT_SYMBOL宏的使用时出现在Linux-2.6之后,在Linux-2.4内核中,默认的非static 函数和变量都会自动导入到kernel 空间, 都不用EXPORT_SYMBOL() 做标记的。

然后Linux-2.6之后默认不导出所有的符号,所以使用EXPORT_SYMBOL() 做标记。

二、EXPORT_SYMBOL宏的作用

EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,不用修改内核代码就可以在内核模块中直接调用,
即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。
符号的意思就是函数的入口地址,或者说是把这些符号和对应的地址保存起来的,在内核运行的过程中,可以找到这些符号对应的地址的。
这里要和System.map做一下对比:
System.map 中的是连接时的函数地址。连接完成以后,在2.6内核运行过程中,是不知道哪个符号在哪个地址的。
EXPORT_SYMBOL 的符号, 是把这些符号和对应的地址保存起来,在内核运行的过程中,可以找到这些符号对应的地址。
在模块加载中,其本质就是动态链接到内核。
如果在模块中引用了内核或其它模块的符号,就要EXPORT_SYMBOL这些符号,这样才能找到对应的地址连接。

三、EXPORT_SYMBOL使用方法

  1.在模块函数定义之后使用EXPORT_SYMBOL(函数名)
  2.在调用该函数的模块中使用extern对要使用的符号或者函数进行声明

  3.首先加载定义该函数的模块,再加载调用该函数的模块

四、例子

1、编写源代码文件

1)module_a.c

#include <linux/init.h> //所有模块都必须包含的头文件 
#include <linux/module.h> //一些宏定义,例如这里的KERN_INFO
#include <linux/kernel.h>

#define DRIVER_AUTHOR "xz@vi-chip.com.cn"
#define DRIVER_DESC   "A sample driver"  

int func1(void)
{
       printk("In Func: %s...\n",__func__);
       return 0;
}

// Export symbol func1  
EXPORT_SYMBOL(func1);
static int __init hello_init(void)
{
    printk(KERN_INFO "module1 Hello world!\n");//前面的宏表示打印的级别  

    return 0;
}

static void __exit hello_exit(void)
{
    printk(KERN_INFO "module1 hello exit!\n");
}

module_init(hello_init);//用宏来指定入口,加载模块时里面的加载函数会被调用  
module_exit(hello_exit);

//模块的许可证 
MODULE_LICENSE("GPL");
//模块的作者
MODULE_AUTHOR(DRIVER_AUTHOR);
//模块的描述

MODULE_DESCRIPTION(DRIVER_DESC);

2)module_b.c

#include <linux/init.h> //所有模块都必须包含的头文件 
#include <linux/module.h> //一些宏定义,例如这里的KERN_INFO
#include <linux/kernel.h>


#define DRIVER_AUTHOR "xz@vi-chip.com.cn"
#define DRIVER_DESC   "A sample driver"  

extern int func1(void);//导出符号外部函数声明   

static int func2(void)
{
       func1();
       printk("In Func: %s...\n",__func__);
       return 0;
}

static int __init hello_init(void)
{
    printk(KERN_INFO "module2 Hello world!\n");//前面的宏表示打印的级别  


    func2();
    return 0;
}
static void __exit hello_exit(void)
{
    printk(KERN_INFO "module2 hello exit!\n");
}

module_init(hello_init);//用宏来指定入口,加载模块时里面的加载函数会被调用  
module_exit(hello_exit);

//模块的许可证 
MODULE_LICENSE("GPL");
//模块的作者
MODULE_AUTHOR(DRIVER_AUTHOR);
//模块的描述
MODULE_DESCRIPTION(DRIVER_DESC);

2、编写Makefile

ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

all:                               
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules   
clean:                                             
        $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

else
    obj-m := module_a.o module_b.o

endif

五、编译内核模块

执行make all 或make

六、安装内核模块

1、先安装module_a.ko

2、再安装module_b.ko

七、查看输出信息

dmesg

[162797.214107] module1 Hello world!
[162802.170616] module2 Hello world!
[162802.170622] In Func: func1...

[162802.170624] In Func: func2...

八、卸载内核模块

1、先卸载sudo rmmod module_b

2、再卸载sudo rmmod module_a

完毕。

<think>首先,用户的问题是:“我想了解EXPORT_SYMBOL使用方法 请问EXPORT_SYMBOL用法及示例”。根据系统级指令,我需要使用中文回答,并确保LaTeX语法正确。所有数学表达式必须用$...$或$$...$$格式。 用户引用了多个来源: - 引用[1]:解释了EXPORT_SYMBOL的基本概念和简单用法。 - 引用[2]:讨论了EXPORT_SYMBOLEXPORT_SYMBOL_GPL的区别,以及使用方法。 - 引用[3]:描述了EXPORT_SYMBOL定义。 - 引用[4]:提供了查看导出符号的示例。 我的回答需要结构清晰,帮助用户逐步解决问题。回答应该真实可靠,基于这些引用。 关键点: - EXPORT_SYMBOL用于在Linux内核模块中导出符号(函数或变量),使其可以被其他模块使用。 - 基本用法:在函数定义后使用EXPORT_SYMBOL(函数名)。 - 有EXPORT_SYMBOL_GPL用于GPL许可的模块。 - 使用示例:包括代码片段。 - 查看导出符号的方法。 回答结构: 1. **介绍EXPORT_SYMBOL**:简要解释其作用和重要性。 2. **基本用法**:描述如何使用。 3. **代码示例**:提供示例代码。 4. **EXPORT_SYMBOL vs EXPORT_SYMBOL_GPL**:解释区别。 5. **实际使用步骤**:包括如何声明和加载模块。 6. **查看导出符号**:使用nm或/proc/kallsyms。 7. **生成相关问题**:在末尾添加相关问题。 确保LaTeX格式:没有数学表达式,所以可能不需要。但如果有,比如解释变量或方程,要用$...$。 系统指令说:“所有行内数学表达式必须使用$...$格式”,但在这个上下文中,可能没有数学表达式。用户的问题是关于编程的,所以焦点在代码和解释。 在回答中,末尾自然地添加引用标识。例如,如果引用来源[1],就在相关句子末尾加[^1]。 生成相关问题:在回答结束后,添加“§§相关问题§§”部分,列出几个相关问题。 最终回答格式: - 使用中文。 - 结构清晰:分步骤或部分。 - 包括代码块:使用```code```格式。 - 引用来源:在适当位置加[^1], [^2]等。 - 相关问题。 相关问题想法: 1. EXPORT_SYMBOL的内部实现机制是什么? 2. 如何在Linux内核模块中使用导出的函数? 3. EXPORT_SYMBOLEXPORT_SYMBOL_GPL在许可方面有何具体区别? 4. 导出符号时有哪些常见错误和如何调试? 现在,草拟回答大纲: 1. **引言**:解释EXPORT_SYMBOL的作用。参考[1]。 2. **基本用法**:描述语法和步骤。参考[1],[2]。 3. **代码示例**:提供一个简单示例。参考[1],[4]。 4. **区别**:EXPORT_SYMBOL vs EXPORT_SYMBOL_GPL。参考[2]。 5. **定义**:简要提到内部实现。参考[3]。 6. **查看符号**:如何验证导出。参考[4]。 7. **总结**。 8. **相关问题**。</think>### EXPORT_SYMBOL使用方法及示例 EXPORT_SYMBOLLinux 内核开发中一个关键,用于将模块内部的函数或变量符号导出到内核符号表,从而允许其他内核模块访问这些符号。这在构建可加载模块(如设备驱动)时非常重要,因为它支持模块间的函数调用和数据共享[^1]。下面我将逐步解释其用法,并提供实际示例,确保内容基于可靠引用。 #### 1. **基本用法** EXPORT_SYMBOL 的核心作用是将符号(函数或全局变量)标记为“可导出”。使用时需遵循以下步骤: - **在定义模块中**:在函数或变量定义后直接添加 `EXPORT_SYMBOL(符号名)`。 - **在调用模块中**:使用 `extern` 声明要导入的符号。 - **加载顺序**:先加载定义符号的模块,再加载调用符号的模块[^2]。 语法示例: ```c // 定义模块(例如 mymodule.c) void my_function(void) { // 函数实现 } EXPORT_SYMBOL(my_function); // 导出函数 int my_variable = 100; EXPORT_SYMBOL(my_variable); // 导出变量 ``` 在调用模块中: ```c // 调用模块(例如 caller.c) extern void my_function(void); // 声明外部函数 extern int my_variable; // 声明外部变量 void call_function(void) { my_function(); // 调用导出的函数 printk("Value: %d\n", my_variable); // 访问导出的变量 } ``` 关键点: - `EXPORT_SYMBOL` 必须在函数/变量定义后使用,否则编译会失败。 - 符号必须是全局作用域的(非静态),否则无法导出[^1]。 #### 2. **EXPORT_SYMBOLEXPORT_SYMBOL_GPL 的区别** Linux 内核提供了两种导出: - **EXPORT_SYMBOL**:导出符号到所有可加载模块,无论其许可证类型。 - **EXPORT_SYMBOL_GPL**:仅导出到 GPL 许可证的模块,用于强化开源合规性[^2]。 示例: ```c // 仅导出到 GPL 模块 EXPORT_SYMBOL_GPL(my_function); ``` 如果非 GPL 模块尝试调用 `my_function`,内核会阻止加载并报错。这在开发专有驱动时需特别注意。 #### 3. **实际使用示例** 以下是一个完整的内核模块示例,展示如何定义、导出和调用符号: ```c // 定义模块:mymodule.c #include <linux/module.h> #include <linux/kernel.h> void shared_func(void) { printk(KERN_INFO "Shared function called\n"); } EXPORT_SYMBOL(shared_func); // 导出函数 static int __init mymodule_init(void) { printk(KERN_INFO "Module loaded\n"); return 0; } static void __exit mymodule_exit(void) { printk(KERN_INFO "Module unloaded\n"); } module_init(mymodule_init); module_exit(mymodule_exit); MODULE_LICENSE("GPL"); ``` ```c // 调用模块:caller.c #include <linux/module.h> #include <linux/kernel.h> extern void shared_func(void); // 声明导出的函数 static int __init caller_init(void) { printk(KERN_INFO "Caller module loaded\n"); shared_func(); // 调用导出的函数 return 0; } static void __exit caller_exit(void) { printk(KERN_INFO "Caller module unloaded\n"); } module_init(caller_init); module_exit(caller_exit); MODULE_LICENSE("GPL"); ``` **操作步骤**: 1. 编译并加载定义模块: ```bash make -C /lib/modules/$(uname -r)/build M=$(pwd) modules sudo insmod mymodule.ko ``` 2. 编译并加载调用模块: ```bash sudo insmod caller.ko ``` 3. 检查日志验证调用: ```bash dmesg | tail -n 5 # 应显示 "Shared function called" ``` #### 4. **查看导出的符号** 使用工具验证符号是否成功导出: - **nm 命令**:检查目标文件(.ko)中的符号表。 ```bash nm mymodule.ko | grep shared_func # 输出示例:0000000000000000 T shared_func ``` - **/proc/kallsyms**:查看内核运行时符号表。 ```bash grep shared_func /proc/kallsyms # 输出示例:ffffffffc0000000 T shared_func [mymodule] ``` 这有助于调试导出问题[^4]。 #### 5. **内部实现机制** `EXPORT_SYMBOL` 本质是一个,它在编译时展开为内核符号表条目。具体实现涉及: - 创建一个 `kernel_symbol` 结构,存储符号地址和名称。 - 将符号信息放入特定段(如 `__ksymtab`),供内核链接器使用[^3]。 例如,定义简化后为: ```c #define EXPORT_SYMBOL(sym) \ extern typeof(sym) sym; \ static const char __kstrtab_##sym[] = #sym; \ static const struct kernel_symbol __ksymtab_##sym = { (unsigned long)&sym, __kstrtab_##sym }; ``` 这确保了符号在模块加载时被注册到全局表。 #### 总结 EXPORT_SYMBOLLinux 内核模块间通信的基础工具,通过简单调用实现符号导出。使用时需注意: - 正确放置 `EXPORT_SYMBOL` 在定义后。 - 区分 `EXPORT_SYMBOL` 和 `EXPORT_SYMBOL_GPL` 的许可要求。 - 验证符号导出以避免运行时错误[^1][^2][^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值