谈EXPORT_SYMBOL使用

本文详细介绍了EXPORT_SYMBOL在Linux内核中的作用及其使用方法。EXPORT_SYMBOL用于将内核中的函数或变量导出,使得其他内核模块可以调用这些函数或变量。文章通过实例展示了如何在不同模块间使用EXPORT_SYMBOL进行函数调用。

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

EXPORT_SYMBOL只出现在2.6内核中,在2.4内核默认的非static 函数和变量都会自动导入到kernel 空间的, 都不用EXPORT_SYMBOL() 做标记的。
2.6就必须用EXPORT_SYMBOL() 来导出来(因为2.6默认不到处所有的符号)。

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

2、使用方法
   第一、在模块函数定义之后使用EXPORT_SYMBOL(函数名)
   第二、在掉用该函数的模块中使用extern对之声明
   第三、首先加载定义该函数的模块,再加载调用该函数的模块

另外,在编译调用某导出函数的模块时,往往会有WARNING: "****" [**********] undefined!
使用dmesg命令后会看到相同的信息。开始我以为只要有这个错误就不能加载模块,后来上网查了一下,发现这主要是因为在编译连接的时候还没有和内核打交道,当然找不到symbol了,但是由于你生成的是一个内核模块,所以LD不提示error,而是给出一个warning,寄希望于在insmod的时候,内核能够把这个symbol连接上。

 

 

 

参考资料:

-------------------------------------------------------------

http://blog.chinaunix.net/u/12592/showart_461504.html

一个模块mod1中定义一个函数func1;在另外一个模块mod2中定义一个函数func2,func2调用func1。
在模块mod1中,EXPORT_SYMBOL(func1);
在模块mod2中,extern int func1();
就可以在mod2中调用func1了。

参考:
http://topic.youkuaiyun.com/u/20070910/09/ee2cff13-9179-41e3-9292-4fd73261f709.html
http://www.dev-archive.com/msdn-archive/524/kernel-driver-5244619.shtm

mod1.c
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>

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

EXPORT_SYMBOL(func1);

static int __init hello_init(void)
{
        printk("Module 1,Init!/n");
        return 0;
}

static void __exit hello_exit(void)
{
        printk("Module 1,Exit!/n");
}

module_init(hello_init);
module_exit(hello_exit);


#############################################################
mod2.c
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/module.h>

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

static int __init hello_init(void)
{
        printk("Module 2,Init!/n");
        func2();
        return 0;
}

static void __exit hello_exit(void)
{
        printk("Module 2,Exit!/n");
}

module_init(hello_init);
module_exit(hello_exit);

################################################################
Makefile
ifneq ($(KERNELRELEASE),)
obj-m   := XXXX.o
else
KDIR    := /lib/modules/$(shell uname -r)/build
PWD             := $(shell pwd)

default:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules

clean:
        rm -rf Module.symvers *.ko *.o *.mod.c .*.cmd .tmp_versions

endif

################################################################

#insmod ./mod1.ko
#insmod ./mod2.ko
#rmmod mod2
#rmmod mod1

Jan 11 11:59:17 wangyao-desktop kernel: [ 9886.801010] Module 2,Exit!
Jan 11 11:59:21 wangyao-desktop kernel: [ 9891.450214] Module 1,Exit!
Jan 11 12:05:29 wangyao-desktop kernel: [10258.385014] Module 1,Init!
Jan 11 12:05:38 wangyao-desktop kernel: [10267.465923] Module 2,Init!
Jan 11 12:05:38 wangyao-desktop kernel: [10267.465928] In Func: func1...
Jan 11 12:05:38 wangyao-desktop kernel: [10267.465930] In Func: func2...
Jan 11 12:05:50 wangyao-desktop kernel: [10280.091755] Module 2,Exit!
Jan 11 12:05:57 wangyao-desktop kernel: [10287.332596] Module 1,Exit!

可见,在mod2中的func2函数成功的调用了mod1中的func1函数。

注意:
在编译mod2的时候,出现一个WARNING:
root@wangyao-desktop:~/modules/export_symbol/mod2# make
make -C /lib/modules/2.6.22-14-generic/build SUBDIRS=/root/modules/export_symbol/mod2 modules
make[1]: Entering directory `/usr/src/linux-headers-2.6.22-14-generic'
  Building modules, stage 2.
  MODPOST 1 modules
WARNING: "func1" [/root/modules/export_symbol/mod2/mod2.ko] undefined!
make[1]: Leaving directory `/usr/src/linux-headers-2.6.22-14-generic'


这主要是因为在编译连接的时候还没有和内核打交道,当然找不到symbol了,但是由于你生成的是一个内核模块,所以LD不提示error,而是给出一个warning,寄希望于在insmod的时候,内核能够把这个symbol连接上。

 

-----------------------------------------------------------

http://www.lslnet.com/linux/f/docs1/i46/big5316526.htm

 請教關於EXPORT_SYMBOL

在一個文件裡要用到別的文件中的函數 用extern不就可以了麼

為什麼還需要EXPORT_SYMBOL

謝謝

   
Re: 請教關於EXPORT_SYMBOL

EXPORT_SYMBOL是給模塊用的。


   
Re: 請教關於EXPORT_SYMBOL

我看好像只要函數不是聲明為static的
在System.map中也有並且和EXPORT_SYMBOL的函數一樣。

模塊也可以使用的吧

   
Re: 請教關於EXPORT_SYMBOL

System.map 中的是鏈接時的函數地址。 連接完成以後,在內核運行過程中,是不知道哪個符號在那個地址的。而這個文件是給調試用的。其中的內容,kernel並不知道 。


EXPORT_SYMBOL 的符號, 是把這些符號和對應的地址,保存起來,在內核運行的過程中,可以找到這些符號對應的地址的。

而module在加載過程中,其本質就是動態連接到內核,如果在模塊中引用了內核或其它模塊的符號,就要 EXPORT_SYMBOL 這些符號,這樣才能找到對應的地址連接呀。 要不沒法連接的。

   
Re: 請教關於EXPORT_SYMBOL

那個是2.4的, 2.4中只要全局的, 符號就算導出了; 2.6則必須顯式調用EXPORT_SYMBOL或其變體。

   
Re: 請教關於EXPORT_SYMBOL

內核模塊加載的鏈接過程,不是一個普通的鏈接過程,是內核自己做的一個特殊的過程,
因此,單純extern是不可以的,內核強行要求用EXPORT_SYMBOL

 

-------------------------------------------------------------

http://hi.baidu.com/leal/blog/item/d3e1cafcb97c2dfdfd037fc2.html

System.map[1]是Linux内核符号文件,维护有内核函数名称和非堆栈变量名称与各自地址的对应关系。

若内核函数或变量要被内核模块调用,则必须使用EXPORT_SYMBOL宏进行处理,作用之一是将该符号连接到二进制文件的各个 __ksymtab_xx_xx section(参看include/linux/module.h,使用GCC编译器的__attribute__关键字实现[2])。内核加载模块 时,会先确认该模块调用的各内核函数是否已export(参看__find_symbol() kernel/module.c)。

比如FC5缺省会给vanilla内核打补丁,使其不再export sys_open符号,这一点可搜索该内核对应的System.map文件进行确认,看是否存在__ksymtab_sys_open符号。

[1] The system.map File
http://www.dirac.org/linux/system.map/

[2] Using GNU C __attribute__
http://www.unixwiz.net/techtips/gnu-c-attributes.html

 

------------------------------------------------------------

http://www.linuxsir.org/bbs/thread347677.html

 

在编写module是,如果函数的声明没有加static,那么我理解就应该是全局的阿,没什么要用
EXPORT_SYMBOL()

 

好象是因为系统需要生成类似于C00021_PRINTK 一类的链接用的,因为系统的函数本身其实是有其前缀的

 

模块是动态加载的,需要一个运行时存在的符号表,找到符号。而我们一般所说的符号表是供静态连接时定位符号地址用的。EXPORT_SYMBOL宏的作用就是把静态符号表中的符号和地址放到运行时的符号表中(在一个section中)供运行时寻找符号用。
看一下EXPORT_SYMBOL的定义就知道了。

 

-------------------------------------------------------------

http://www.unixresources.net/linux/clf/linuxK/archive/00/00/71/60/716080.html

driver/char/console.c 里面提供了 这个函数:

/* console_sem is held (except via vc_init()) */

void reset_terminal(int currcons, int do_clear)

{

top = 0;

bottom = video_num_lines;

。。。。。。



在console.c 的最后面也有:

/*
* Visible symbols for modules
*/

EXPORT_SYMBOL(fg_console);
EXPORT_SYMBOL(console_blank_hook);
EXPORT_SYMBOL(hide_cursor);
EXPORT_SYMBOL(reset_terminal); //这个


drivers/char/Makefile 里面也有:


# All of the (potential) objects that export symbols.

# This list comes from 'grep -l EXPORT_SYMBOL *.[hc]'.



export-objs := busmouse.o console.o keyboard.o sysrq.o

misc.o pty.o random.o selection.o serial.o

sonypi.o tty_io.o tty_ioctl.o generic_serial.o

au1000_gpio.o hp_psaux.o nvram.o scx200.o



我要在kernel/power/ui.c 里面调用:


void pm_restore_console(void)

{



if (TEST_ACTION_STATE(SUSPEND_NO_OUTPUT))

return;



// reset_terminal(suspend_console, 1);

reset_terminal(TTY_MAJOR,1);






可是编译内核的时候, 最后, 就说 undefined reset_terminal () , 奇怪了。


/usr/bin/mips-linux-ld -G 0 -static -T arch/mips/ld.script arch/mips/kernel/head.o
arch/mips/kernel/init_task.o init/main.o init/version.o init/do_mounts.o --start-group
arch/mips/kernel/kernel.o arch/mips/mm/mm.o kernel/kernel.o mm/mm.o fs/fs.o ipc/ipc.o
arch/mips/math-emu/fpu_emulator.o arch/mips/emma2_se/emma2.o drivers/char/char.o
drivers/block/block.o drivers/misc/misc.o drivers/net/net.o drivers/ide/idedriver.o
drivers/pci/driver.o drivers/mtd/mtdlink.o drivers/net/wireless/wireless_net.o
drivers/usb/usbdrv.o drivers/media/media.o drivers/md/mddev.o net/network.o
arch/mips/lib/lib.a /home/work/data3/standby/dv_kernel_suspend2/linux/lib/lib.a
--end-group -o vmlinux
kernel/kernel.o: In function `pm_restore_console':
kernel/kernel.o(.text+0x16030): undefined reference to `reset_terminal'
kernel/kernel.o(.text+0x16030): relocation truncated to fit: R_MIPS_26 reset_terminal
make[1]: *** [kallsyms] Error 1
make[1]: Leaving directory `/home/work/data3/standby/dv_kernel_suspend2/linux'
make: *** [vmlinux] Error 2



大家帮诊断一下。 -DEXPORT_SYMTAB 我也加了。 还是不行。


 

EXPORT_SYMBOL()
被export的符号,是用来给加载模块时链接时用的, 编译内核自身时, 和export 应该是没有关系的. 看是否包含了对应的头文件, console.c是被编译为模块, 还是编译到内核? 如果是别编译到内核, 只要在ui.c
里包含了定义reset_terminal的头文件,应该是可以编译出来的.

 

 

没错,一阵见血。
找到原因了,console.o 的编译被我们自己的宏给包住了(我们自己改了一些东西), 结果 console.o 没有被编译出来,当然就找不到reset_terminal() 了。
所以光有:
export-objs := busmouse.o console.o keyboard.o sysrq.o
misc.o pty.o random.o selection.o serial.o
sonypi.o tty_io.o tty_ioctl.o generic_serial.o
au1000_gpio.o hp_psaux.o nvram.o scx200.o
也是不行的,
还有了一个 obj-y =+ console.o

我补充一下:
以前做2.4的时候确实如此,
我记得是 对于2.4 来说, 默认的非static 函数和变量都会自动导入到kernel 空间的, 都不用EXPORT_SYMBOL() 做标记的。

2.6就必须用EXPORT_SYMBOL() 来导出来(因为2.6默认不到处所有的符号)。

 

 

-----------------------------------------------------------

http://forum.kernelnewbies.org/read.php?12,153

<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]。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值