1. 背景

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

2. EXPORT_SYMBOL的作用

EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开, 不用修改内核代码就可以在您的内核模块中直接调用,即使用EXPORT_SYMBOL可以将一个函数以符号的方式导出给其他模块使用

EXPORT_SYMBOL(符号名);  可以导出static函数到符号表中
EXPORT_SYMBOL_GPL(符号名) //用于GPL协议认证的模块
EXPORT_SYMBOL_GPL的符号必须要用MODULE_LICENSE("GPL")或者用MODULE_LICENSE("Dual BSD/GPL")之后才能在模块中引用。
而且MODULE_LICENSE("char")中的char不可以是任意字符,否则错误和没有MODULE_LICENSE效果一样。

这里要和System.map做一下对比: System.map 中的是连接时的函数地址。连接完成以后,在2.6内核运行过程中,是不知道哪个符号在哪个地址的。 EXPORT_SYMBOL 的符号,是把这些符号和对应的地址保存起来,在内核运行的过程中,可以找到这些符号对应的地址。 而模块在加载过程中,其本质就是能动态连接到内核,如果在模块中引用了内核或其它模块的符号, 就要EXPORT_SYMBOL这些符号,这样才能找到对应的地址连接

3. 使用方法

  • 在模块函数定义之后使用EXPORT_SYMBOL(函数名)
  • 在掉用该函数的模块中使用extern对之声明
  • 首先加载定义该函数的模块,再加载调用该函数的模块

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

1.EXPORT_SYMBOL

EXPORT_SYMBOL( my_pub_func);
在预编译阶段会解析为:
extern void *__crc_my_pub_func __attribute__((weak)); 
static const unsigned long __kcrctab_my_pub_func __attribute__((__used__)) __attribute__((section("__kcrctab" ""), unused)) = (unsigned long) &__crc_my_pub_func; 
static const char __kstrtab_my_pub_func[] __attribute__((section("__ksymtab_strings"))) = "" "my_pub_func"; 
static const struct kernel_symbol __ksymtab_my_pub_func __attribute__((__used__)) __attribute__((section("__ksymtab" ""), unused)) = { (unsigned long)&my_pub_func, __kstrtab_my_pub_func };
很显然__ksymtab_my_pub_func存储了my_pub_func的地址和符号信息,该符号对应的地址
只有insmod后才会确定;
__ksymtab_my_pub_func会链接到__ksymtab section,__ksymtab section中的所有内容就构成了
内核"导出"的符号表,这个表在insmod 时候会用到.
2./proc/kallsyms
cat /proc/kallsyms会打印出内核当前的符号表,例如:
...
d8834a24 t snd_free_sgbuf_pages [snd_page_alloc]
c0180d7a U create_proc_entry [snd_page_alloc]
d88341d8 T snd_dma_free_pages [snd_page_alloc]
c013d858 U __get_free_pages [snd_page_alloc]
d8834ab5 t snd_malloc_sgbuf_pages [snd_page_alloc]
c014f906 U kmem_cache_alloc [snd_page_alloc]
c0106dcd U dma_alloc_coherent [snd_page_alloc]
...
其中第一列是该符号在内核地址空间中的地址;第二列是符号属性,小写表示
局部符号,大写表示全局符号,具体含义参考man nm; 第三列表示符号字符串. 
这里只显示EXPORT_SYMBOL,EXPROT_SYMBOL_GPL处理过的符号。
3.System.map内核符号文件
通过more /boot/System.map 可以查看内核符号列表。
可以显示编译好内核后所有在内核中的符号,模块中的要另行查看。
4.通过nm vmlinux也可以查看内核符号列表
可以显示编译好内核后所有在内核中的符号,模块中的要另行查看。
5.通过nm module_name可以查看模块的符号列表
但是得到是相对地址,只有加载后才会分配绝对地址。比如:e1000模块,如果e1000中的符号经过EXPORT_SYMBOL处理,等加载后,我们可以通过more /boot/System.map和nm vmlinux命令查看到,但是没有EXPORT_SYMBOL的,我目前不知道怎么查看。其时这个跟elf文件格式有关,嘿嘿嘿。
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连接上。

另外一个疑问,为什么不加static的函数的符号也会出现在System.map中,是不是只需要extern一下就可以?

错误,内核2.6以后的版本,默认不导出所有符号(2.4以前默认导出所有符号.),如果不使用EXPPORT_SYMBOL, 就不导出,故不会导出到System.map中。下一篇要理解一下System.map这个文件是干啥用的了。