一、查看内核驱动代码你会发现很多的函数带有EXPORT_SYMBOL()宏定义。
二、那么EXPORT_SYMBOL的作用是什么?
EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,不用修改内核代码就可以在您的内核模块中直接调用,即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。
三、使用方法
1、在模块函数定义之后使用“EXPORT_SYMBOL(函数名)”来声明。
2、在调用该函数的另外一个模块中使用extern对之声明。
3、先加载定义该函数的模块,然后再加载调用该函数的模块,请注意这个先后顺序。
四、实例测试。
1、测试思路:在模块export_symbol_one中定义一个函数function_one(void);在另外一个模块export_symbol_two中定义一个函数function_two(void),function_two(void)里面会调用function_one(void)。
2、测试代码:
export_symbol_one.c
#include<linux/init.h>
#include<linux/module.h>
#include<linux/kernel.h>
static int function_one(void)
{
printk("EXPORT_SYMBOL In Func: %s...\n",__func__);
return 0;
}
EXPORT_SYMBOL(function_one);
static int __init export_symbol_init(void)
{
printk("EXPORT_SYMBOL Module one,Init!\n");
return 0;
}
static void __exit export_symbol_exit(void)
{
printk("EXPORT_SYMBOL Module one,Exit!\n");
}
module_init(export_symbol_init);
module_exit(export_symbol_exit);
Makefile:
obj-m += export_symbol_one.o
KDIR := /home/weifanghai/Android_4.4_git/xunwei/kernel/iTop4412_Kernel_3.0
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o
export_symbol_two.c
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/module.h>
static int function_two(void)
{
extern int function_one(void);
function_one();
printk("EXPORT_SYMBOL In Func: %s...\n",__func__);
return 0;
}
static int __init export_symbol_init(void)
{
printk("EXPORT_SYMBOL Module two,Init!\n");
function_two();
return 0;
}
static void __exit export_symbol_exit(void)
{
printk("EXPORT_SYMBOL Module two,Exit!\n");
}
module_init(export_symbol_init);
module_exit(export_symbol_exit);
Makefile
obj-m += export_symbol_two.o
KDIR := /home/weifanghai/Android_4.4_git/xunwei/kernel/iTop4412_Kernel_3.0
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.o
3、依次加载两个模块:
[root@iTOP-4412]# insmod export_symbol_one.ko
[root@iTOP-4412]# insmod export_symbol_two.ko
4、运行结果效果图:
5、小结:从上面的打印信息看,模块export_symbol_two.ko里面的函数function_two(void)可以调用模块export_symbol_one.ko里面的函数function_one(void)。
Linux的EXPORT_SYMBOL和EXPORT_SYMBOL_GPL的使用和区别
简要说明使用方法:
一个模块mod1中定义一个函数func1;在另外一个模块mod2中定义一个函数func2,func2调用func1。
在模块mod1中,EXPORT_SYMBOL(func1);
在模块mod2中,extern int func1();
就可以在mod2中调用func1了。
同理EXPORT_SYMBOL_GPL使用相同。
1、EXPORT_SYMBOL的作用是什么?
EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,不用修改内核代码就可以在您的内核模块中直接调用,即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用。
这里要和System.map做一下对比:
System.map 中的是连接时的函数地址。连接完成以后,在2.6内核运行过程中,是不知道哪个符号在哪个地址的。
EXPORT_SYMBOL的符号,是把这些符号和对应的地址保存起来,在内核运行的过程中,可以找到这些符号对应的地址。而模块在加载过程中,其本质就是能动态连接到内核,
如果在模块中引用了内核或其它模块的符号,就要EXPORT_SYMBOL这些符号,这样才能找到对应的地址连接。
2、使用方法
第一、在模块函数定义之后使用EXPORT_SYMBOL(函数名);
第二、在调用该函数的模块中使用extern对之声明;
第三、首先加载定义该函数的模块,再加载调用该函数的模块;
3、区别
EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);
这两个宏均用于将给定的符号导出到模块外, _GPL版本的宏定义只能使符号对GPL许可的模块可用。 符号必须在模块文件的全局部分导出,不能在函数中导出,
这是因为上述这两个宏将被扩展成一个特殊用途的声明,而该变量必须是全局的。这个变量存储于模块的一个特殊的可执行部分(一个"ELF段" ),在装载时,内核通过这个段来寻找模块导出的变量
(感兴趣的读者可以看<linux/module.h>获知更详细的信息)。
EXPORT_SYMBOL举例说明
1、函数方面的使用:
例如我下面的例子,在一个驱动中drivers/video/lt9211/lt9211.c定义了函数lt9211_mipitolvds_init,然后在另外的.c文件中可以直接extern来使用:
943 void lt9211_mipitolvds_init(void)
944 {
945 lt9211_config();
946
947 lt9211_timingset();
948 if( pvideo_format != NULL )
949 {
950 lt9211_desscpll();
951 lt9211_mipipcr();
952
953 //Tx config
954 lt9211_txphy();
955 lt9211_txdigital();
956 lt9211_txpll();
957 #ifdef LT9211_VEDIO_CHECK_DEBUG
958 lt9211_videocheckdebug();
959 #endif
960
961 #ifdef LT9211_BT_SET
962 lt9211_BT_set();
963 #endif
964 }
965
966 mdelay(50);
967 }
968 EXPORT_SYMBOL(lt9211_mipitolvds_init);
直接exterm来使用:
43 extern void lt9211_mipitolvds_init(void); //首先从其他地方extern过来
...
635 static int panel_simple_enable(struct drm_panel *panel)
636 {
637 struct panel_simple *p = to_panel_simple(panel);
638
639 if (p->enabled)
640 return 0;
641
642 /* add for lt9211 resume/suspend init */
643 lt9211_mipitolvds_init(); //这里是对函数的调用
644
645 if (p->desc && p->desc->delay.enable)
646 panel_simple_sleep(p->desc->delay.enable);
647
648 backlight_enable(p->backlight);
649
650 p->enabled = true;
651
652 return 0;
653 }
2、变量方面的使用
首先在一个.c文件中定义:
int gyro_lsm9ds1_x = 0;
EXPORT_SYMBOL(gyro_lsm9ds1_x);
然后在另外的.c中extern过来使用:
extern int gyro_lsm9ds1_x;
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这些符号,这样才能找到对应的地址连接。
使用方法:
第一、在模块函数定义之后使用EXPORT_SYMBOL(函数名)
第二、在掉用该函数的模块中使用extern对之声明
第三、首先加载定义该函数的模块,再加载调用该函数的模块
另外,在编译调用某导出函数的模块时,往往会有WARNING: "****" [**********] undefined!
使用dmesg命令后会看到相同的信息。开始我以为只要有这个错误就不能加载模块,后来上网查了一下,发现这主要是因为在编译连接的时候还没有和内核打交道,当然找不到symbol了,但是由于你生成的是一个内核模块,所以LD不提示error,而是给出一个warning,寄希望于在insmod的时候,内核能够把这个symbol连接上。
一个模块mod1中定义一个函数func1;在另外一个模块mod2中定义一个函数func2,func2调用func1。 在模块mod1中,EXPORT_SYMBOL(func1); 在模块mod2中,extern int func1(); 就可以在mod2中调用func1了
EXPORT_SYMBOL示范
比如有两个驱动模块:Module A和Module B,其中Module B使用了Module A中的export的函数,因此在Module B的Makefile文件中必须添加:
KBUILD_EXTRA_SYMBOLS += /path/to/ModuleA/Module.symvers
export KBUILD_EXTRA_SYMBOLS
这样在编译Module B时,才不会出现Warning,提示说func1这个符号找不到,而导致编译得到的ko加载时也会出错。
// Module A (mod_a.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
EXPORT_SYMBOL(func1);
static int __init hello_init(void)
{
printk("Module 1,say hello world!\n");
return 0;
}
static void __exit hello_exit(void)
{
printk("Module 1,Exit!\n");
}
module_init(hello_init);
module_exit(hello_exit);
// Module B (mod_b.c)
#include<linux/init.h>
#include<linux/kernel.h>
#include<linux/module.h>
extern int functl(void);
static int func2(void)
{
func1();
printk("In Func: %s...\n",__func__);
return 0;
}
static int __init hello_init(void)
{
printk("Module 2,is used Module 1 function!\n");
func2();
return 0;
}
static void __exit hello_exit(void)
{
printk("Module 2,Exit!\n");
}
module_init(hello_init);
module_exit(hello_exit);
在驱动加载的时候,一定要先加载定义function1的Module A模块,然后再加载调用function1的Module B的驱动模块。
如下操作:
insmod Module_A.ko insmod Module_B.ko
EXPORT_SYMBOL机制
Linux内核由主内核ELF格式文件(vmlinux)和许多内核模块组成。在构成vmliunx主内核文件中,每一个被声明EXPORT_SYMBOL的符号,都只有一个目的,就是让vmlinux主内核文件之外的调用者可能使用这个变量或者函数符号,这个就是给到内核模块来使用的内核导出符号。
为什么会这样?因为组成vmlinux主内核的所有文件(静态)都在同一时间编译和生成,即所有的符号引用都在静态链接阶段完成了。而内核模块的出现让事情有了变化(可能在后期动态加入),内核模块不可避免要使用主内核提供的基础设施(比如变量或者函数),作为独立编译链接的内核模块,必须解决符号引用的问题,也称为‘未解决引用’。 处理‘未解决引用’问题的本质是在模块加载期间找到当前‘未解决的引用’符号在内存中的实际目标地址。
内核和内核模块通过符号表的形式向外部世界导出符号的相关信息,这种导出符号的方式在代码实现是通过EXPORT_SYMBOL宏的形式。
使用一个EXPORT_SYMBOL(my_exp_function)作为例子,向外部导出一个名为my_exp_function的函数。

每个EXPORT_SYMBOL宏实际会定义二个变量:
- const char*指针类型:表示导出符号的名称
- struct kernel_symbol数据结构类型:用来表示一个内核符号的实例。其中value是这个符号在内存中的地址,而name是符号名。这个内核符号的实例对象(kernel_symbol)告诉外界这个导出符号的二点信息:符号名称与地址 (有了地址和地址背后表达的数据类型就可以对符号进行读写操作)

EXPORT_SYMBOL宏导出的符号,与通常变量定义并没有实质性差异,唯一不同点是它们被放入了特定的section中。(普通变量是放在data对应的section中)
之所以需要把向外界导出的符号统一放到一个特殊的section里面, 是为了在加载其他模块的时用来处理那些‘未解决的引用’符号。
linux2.6的“/proc/kallsyms”文件对应着内核符号表,记录了符号以及符号所在的内存地址。用cat的结果如下:
EXPORT_SYMBOL的用法_shell
模块可以使用如下宏导出符号到内核符号表:
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名) 只适用于包含GPL许可权的模块。
导出的符号可以被其他模块使用,不过使用之前一定要extern声明一下。这就是作为模块之间互相通信的一种方法。
举一个代码演示:
一个文件是hello.c文件,定义2个函数,用于导出
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
int add_integar(int a,int b)
{
return a + b;
}
int sub_integar(int a,int b)
{
return a - b;
}
EXPORT_SYMBOL(add_integar);
EXPORT_SYMBOL(sub_integar);
另一个文件是test.c,用于调用hello模块导出的函数:
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
extern int add_integar(int ,int); //声明要调用的函数
extern int sub_integar(int ,int); //声明要调用的函数
int result(void)
{
int a,b;
a = add_integar(1,1);
b = sub_integar(1,1);
printk("%d/n",a);
printk("%d/n",b);
return 0;
}
两个文件编译都要有对应的makefile。模板如下:
ifeq ($(KERNELRELEASE),)
# Assume the source tree is where the running kernel was built
# You should set KERNELDIR in the environment if it's elsewhere
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
# The current directory is passed to sub-makes as argument
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
.PHONY: modules modules_install clean
else
# called from kernel build system: just declare what our modules are
obj-m := hello.o
endif
把hello.o改成对应的文件名.o就可以了。分别make后,先加载hello模块,再加载test模块(验证失败,无法加载test.ko)。然后cat /proc/kallsyms | grep integar,显示:
f8eae000 u add_integar [hello2]
f8eae004 u sub_integar [hello2]
f8eae02c r __ksymtab_sub_integar [hello]
f8eae03c r __kstrtab_sub_integar [hello]
f8eae034 r __ksymtab_add_integar [hello]
f8eae048 r __kstrtab_add_integar [hello]
f8eae000 T add_integar [hello]
f8eae004 T sub_integar [hello]
可以看出符号表的名字和对应的内存地址。EXPORT_SYMBOL标签内定义的函数对全部内核代码公开,不用修改内核代码就可以在您的内核模块中直接调用。
-----------------------------------
EXPORT_SYMBOL宏定义原型:
#define EXPORT_SYMBOL(sym) \ __EXPORT_SYMBOL(sym, "")
其中__EXPORT_SYMBOL原型如下:
/* For every exported symbol, place a struct in the __ksymtab section */#define __EXPORT_SYMBOL(sym, sec) \ extern typeof(sym) sym; \ __CRC_SYMBOL(sym, sec) \ static const char __kstrtab_##sym[] \ __attribute__((section("__ksymtab_strings"), aligned(1))) \ = MODULE_SYMBOL_PREFIX #sym; \ static const struct kernel_symbol __ksymtab_##sym \ __used \ __attribute__((section("__ksymtab" sec), unused)) \ = { (unsigned long)&sym, __kstrtab_##sym }
__EXPORT_SYMBOL宏定义主要有四个方面:
1.获取导出的符号的名称,以字符串表示2.把获取到的以字符串表示的名称以及值放入,__ksymtab_+导出的符号为变量的struct kernel_symbol变量中;3.在编译工程中,把struct kernel_symbol编译进入以"ksymtab"为开头的符号表中;4.内核在加载模块时候先查找已经加载过的模块的符号表,如果不存在则继续加载模块
EXPORT_SYMBOL_GPL宏定义与EXPORT_SYMBOL宏定义差不多,不过是编译过程中和加载过程中把符号表加入了以”ksymtab_gpl”为区域的文件中或者内存中。
其中struct kernel_symbol函数原型定义如下:
struct kernel_symbol{unsigned long value;const char *name;};
从定义可以看出使用EXPORT_SYMBOL函数就是把导出的符号以符号值+符号的字符串表示的形式表示的;
EXPORT_SYMBOL函数的使用:
#include <linux/init.h>#include <linux/module.h>static inline intmodule_a_init_func(void){printk("this is module a init func\n");return 0;}static inline voidmodule_a_exit_func(void){printk("this is module a exit func\n");}static inline int __initmodule_a_init(void){printk("this is module a init\n");return module_a_init_func();}static inline void __exitmodule_a_exit(void){printk("this is module a exit\n");module_a_exit_func();}EXPORT_SYMBOL(module_a_init_func);EXPORT_SYMBOL(module_a_exit_func);module_init(module_a_init);module_exit(module_a_exit);
modulea’s Makefile
KERN_DIR = /lib/modules/`uname -r`/build/obj-m := modulea.oall:make -C $(KERN_DIR) M=$(shell pwd) modulesclean:make -C $(KERN_DIR) M=$(shell pwd) modules clean
#include <linux/init.h>#include <linux/module.h>extern intmodule_a_init_func(void);extern voidmodule_a_exit_func(void);static inline int __initmodule_b_init(void){return module_a_init_func();}static inline int __exitmodule_b_exit(void){module_a_exit_func();}module_init(module_b_init);module_exit(module_b_exit);
moduleb’s Makefile
KERN_DIR = /lib/modules/`uname -r`/build/obj-m := moduleb.oKBUILD_EXTRA_SYMBOLS+=/usr/src/linux-2.6.39/driver/export/modulea/Module.symversexport KBUILD_EXTRA_SYMBOLSall:make -C ../modulea/make -C $(KERN_DIR) M=$(shell pwd) modulesclean:make -C $(KERN_DIR) M=$(shell pwd) modules clean
其中,“KBUILD_EXTRA_SYMBOLS+=/usr/src/linux-2.6.39/driver/export/modulea/Module.symvers“的模块A的Modules.symvers路径是绝对路径,不能是相对路径;相对路径会发生如下问题:
//都能够编译成.ko文件:root@ubuntu:/usr/src/linux-2.6.39/driver/export/moduleb# makemake -C ../modulea/make[1]: Entering directory `/usr/src/linux-2.6.39/driver/export/modulea' make -C /lib/modules/`uname -r`/build/ M=/usr/src/linux-2.6.39/driver/export/modulea modules make[2]: Entering directory `/usr/src/linux-headers-3.13.0-160-generic' Building modules, stage 2. MODPOST 1 modules make[2]: Leaving directory `/usr/src/linux-headers-3.13.0-160-generic' make[1]: Leaving directory `/usr/src/linux-2.6.39/driver/export/modulea'make -C /lib/modules/`uname -r`/build/ M=/usr/src/linux-2.6.39/driver/export/moduleb modulesmake[1]: Entering directory `/usr/src/linux-headers-3.13.0-160-generic' CC [M] /usr/src/linux-2.6.39/driver/export/moduleb/moduleb.o /usr/src/linux-2.6.39/driver/export/moduleb/moduleb.c: In function ‘module_b_exit’: /usr/src/linux-2.6.39/driver/export/moduleb/moduleb.c:20:1: warning: no return statement in function returning non-void [-Wreturn-type] } ^ In file included from /usr/src/linux-2.6.39/driver/export/moduleb/moduleb.c:1:0: /usr/src/linux-2.6.39/driver/export/moduleb/moduleb.c: In function ‘__exittest’: include/linux/init.h:303:4: warning: return from incompatible pointer type [enabled by default] { return exitfn; } \ ^ /usr/src/linux-2.6.39/driver/export/moduleb/moduleb.c:23:1: note: in expansion of macro ‘module_exit’ module_exit(module_b_exit); ^ Building modules, stage 2. MODPOST 1 modules WARNING: "module_a_exit_func" [/usr/src/linux-2.6.39/driver/export/moduleb/moduleb.ko] undefined! WARNING: "module_a_init_func" [/usr/src/linux-2.6.39/driver/export/moduleb/moduleb.ko] undefined! CC /usr/src/linux-2.6.39/driver/export/moduleb/moduleb.mod.o LD [M] /usr/src/linux-2.6.39/driver/export/moduleb/moduleb.ko make[1]: Leaving directory `/usr/src/linux-headers-3.13.0-160-generic'root@ubuntu:/usr/src/linux-2.6.39/driver/export/moduleb# vim moduleb.c#加载模块A成功,加载模块B失败;root@ubuntu:/usr/src/linux-2.6.39/driver/export/moduleb# insmod ../modulea/modulea.koroot@ubuntu:/usr/src/linux-2.6.39/driver/export/moduleb# insmod moduleb.koinsmod: ERROR: could not insert module moduleb.ko: Invalid parameters
本文详细介绍了Linux内核中EXPORT_SYMBOL宏的用途,它用于将函数或符号公开给其他内核模块使用,允许模块之间直接调用。文章通过实例展示了如何在模块中定义和使用EXPORT_SYMBOL,以及加载模块的顺序。此外,还对比了System.map和EXPORT_SYMBOL的区别,并提及了EXPORT_SYMBOL_GPL的使用场景。
613





