Linux内核跨模块函数调用:EXPORT_SYMBOL()宏定义

本文详细介绍了Linux内核中EXPORT_SYMBOL宏的用途,它用于将函数或符号公开给其他内核模块使用,允许模块之间直接调用。文章通过实例展示了如何在模块中定义和使用EXPORT_SYMBOL,以及加载模块的顺序。此外,还对比了System.map和EXPORT_SYMBOL的区别,并提及了EXPORT_SYMBOL_GPL的使用场景。

一、查看内核驱动代码你会发现很多的函数带有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宏定义原型:


  1. #define EXPORT_SYMBOL(sym) \ __EXPORT_SYMBOL(sym, "")

其中__EXPORT_SYMBOL原型如下:


  1. /* For every exported symbol, place a struct in the __ksymtab section */
  2. #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. 1.获取导出的符号的名称,以字符串表示
  2. 2.把获取到的以字符串表示的名称以及值放入,__ksymtab_+导出的符号为变量的struct kernel_symbol变量中;
  3. 3.在编译工程中,把struct kernel_symbol编译进入以"ksymtab"为开头的符号表中;
  4. 4.内核在加载模块时候先查找已经加载过的模块的符号表,如果不存在则继续加载模块

EXPORT_SYMBOL_GPL宏定义与EXPORT_SYMBOL宏定义差不多,不过是编译过程中和加载过程中把符号表加入了以”ksymtab_gpl”为区域的文件中或者内存中。
其中struct kernel_symbol函数原型定义如下:


  1. struct kernel_symbol
  2. {
  3. unsigned long value;
  4. const char *name;
  5. };

从定义可以看出使用EXPORT_SYMBOL函数就是把导出的符号以符号值+符号的字符串表示的形式表示的;

EXPORT_SYMBOL函数的使用:


  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. static inline int
  4. module_a_init_func(void)
  5. {
  6. printk("this is module a init func\n");
  7. return 0;
  8. }
  9. static inline void
  10. module_a_exit_func(void)
  11. {
  12. printk("this is module a exit func\n");
  13. }
  14. static inline int __init
  15. module_a_init(void)
  16. {
  17. printk("this is module a init\n");
  18. return module_a_init_func();
  19. }
  20. static inline void __exit
  21. module_a_exit(void)
  22. {
  23. printk("this is module a exit\n");
  24. module_a_exit_func();
  25. }
  26. EXPORT_SYMBOL(module_a_init_func);
  27. EXPORT_SYMBOL(module_a_exit_func);
  28. module_init(module_a_init);
  29. module_exit(module_a_exit);

modulea’s Makefile


  1. KERN_DIR = /lib/modules/`uname -r`/build/
  2. obj-m := modulea.o
  3. all:
  4. make -C $(KERN_DIR) M=$(shell pwd) modules
  5. clean:
  6. make -C $(KERN_DIR) M=$(shell pwd) modules clean

  1. #include <linux/init.h>
  2. #include <linux/module.h>
  3. extern int
  4. module_a_init_func(void);
  5. extern void
  6. module_a_exit_func(void);
  7. static inline int __init
  8. module_b_init(void)
  9. {
  10. return module_a_init_func();
  11. }
  12. static inline int __exit
  13. module_b_exit(void)
  14. {
  15. module_a_exit_func();
  16. }
  17. module_init(module_b_init);
  18. module_exit(module_b_exit);

moduleb’s Makefile


  1. KERN_DIR = /lib/modules/`uname -r`/build/
  2. obj-m := moduleb.o
  3. KBUILD_EXTRA_SYMBOLS+=/usr/src/linux-2.6.39/driver/export/modulea/Module.symvers
  4. export KBUILD_EXTRA_SYMBOLS
  5. all:
  6. make -C ../modulea/
  7. make -C $(KERN_DIR) M=$(shell pwd) modules
  8. clean:
  9. 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路径是绝对路径,不能是相对路径;相对路径会发生如下问题:


  1. //都能够编译成.ko文件:
  2. root@ubuntu:/usr/src/linux-2.6.39/driver/export/moduleb# make
  3. make -C ../modulea/
  4. 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'
  5. make -C /lib/modules/`uname -r`/build/ M=/usr/src/linux-2.6.39/driver/export/moduleb modules
  6. make[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'
  7. root@ubuntu:/usr/src/linux-2.6.39/driver/export/moduleb# vim moduleb.c
  8. #加载模块A成功,加载模块B失败;
  9. root@ubuntu:/usr/src/linux-2.6.39/driver/export/moduleb# insmod ../modulea/modulea.ko
  10. root@ubuntu:/usr/src/linux-2.6.39/driver/export/moduleb# insmod moduleb.ko
  11. insmod: ERROR: could not insert module moduleb.ko: Invalid parameters
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值