Linux 驱动开发之内核模块开发(四)—— 符号表的导出

时间:2021-10-27 06:03:11

        Linux内核头文件提供了一个方便的方法用来管理符号的对模块外部的可见性,因此减少了命名空间的污染(命名空间的名称可能会与内核其他地方定义的名称冲突),并且适当信息隐藏。 如果你的模块需要输出符号给其他模块使用,应当使用下面的宏定义:

EXPORT_SYMBOL(name);

EXPORT_SYMBOL_GPL(name);   //只适用于包含GPL许可权的模块;

     这两个宏均用于将给定的符号导出到模块外. _GPL版本的宏定义只能使符号对GPL许可的模块可用。 符号必须在模块文件的全局部分导出,不能在函数中导出,这是因为上述这两个宏将被扩展成一个特殊用途的声明,而该变量必须是全局的。这个变量存储于模块的一个特殊的可执行部分(一个"ELF段" ),在装载时,内核通过这个段来寻找模块导出的变量(感兴趣的读者可以看<linux/module.h>获知更详细的信息)。


一、宏定义EXPORT_SYMBOL分析

1、源码

<include/linux/moudule.h>

…….

#ifndef MODULE_SYMBOL_PREFIX
#define MODULE_SYMBOL_PREFIX ""
#endif

…….

struct kernel_symbol //内核符号结构
{
unsignedlong value; //该符号在内存地址中的地址
constchar *name; //该符号的名称

};

……

#define __EXPORT_SYMBOL(sym,sec) \
externtypeof(sym) sym; \
__CRC_SYMBOL(sym,sec) \
staticconst char __kstrtab_##sym[] \
__attribute__((section(“__ksymtab_strings”),aligned(1))) \
=MODULE_SYMBOL_PREFIX#sym; \
staticconst struct kernel_symbol __ksymtab_##sym \
__used \
__attribute__((section(“__ksymatab”sec),unused)) \
={(unsignedlong)&sym,_kstrab_#sym}

#define EXPORT_SYMBOL(sym) \
__EXPOTR_SYMBOL(sym,””)

#define EXPORT_SYMBOL_GPL(sym) \
__EXPOTR_SYMBOL(sym,”_gpl”)

#define EXPORT_SYMBOL(sym) \
__EXPOTR_SYMBOL(sym,”_gpl_future”)

在分析前,先了解如下相关知识:

1)#运算符,##运算符

     通常在宏定义中使用#来创建字符串 #abc就表示字符串”abc”等。

    ##运算符称为预处理器的粘合剂,用来替换粘合两个不同的符号,

如:#define xName (n)  x##n

则xName(4)  则变为x4


2)gcc的 __attribute__ 属性:

     __attribute__((section(“section_name”)))的作用是将指定的函数或变量放入到名为”section_name”的段中

    __attribute__属性添加可以在函数或变量定义的时候直接加入在定义语句中。

如:

int myvar__attribute__((section("mydata"))) = 0;

表示定义了整形变量myvar=0;并且将该变量存放到名为”mydata”的section中

关于gcc_attribute详解可以参考:http://blog.sina.com.cn/s/blog_661314940100qujt.html


2、EXPORT_SYMBOL的作用是什么?

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

       这里要和System.map做一下对比:System.map 中的是连接时的函数地址。连接完成以后,在2.6内核运行过程中,是不知道哪个符号在哪个地址的。

       EXPORT_SYMBOL的符号,是把这些符号和对应的地址保存起来,在内核运行的过程中,可以找到这些符号对应的地址。而模块在加载过程中,其本质就是能动态连接到内核,如果在模块中引用了内核或其它模块的符号,就要EXPORT_SYMBOL这些符号,这样才能找到对应的地址连接。


二、 EXPORT_SYMBOL使用方法

第一、在模块函数定义之后使用EXPORT_SYMBOL(函数名)

第二、在调用该函数的模块中使用extern对之声明

第三、首先加载定义该函数的模块,再加载调用该函数的模块

要调用别的模块实现的函数接口和全局变量,就要导出符号 /usr/src/linux-headers-2.6.32-33-generic/Module.symvers

A B
static int num =10;static void show(void){    printk("%d  \n",num);}EXPORT_SYMBOL(show); extern void show(void);

函数A先将show() 函数导出,函数B 使用extern 对其声明,要注意:

a -- 编译a模块后,要将 Module.symvers 拷贝到b模块下

b -- 然后才能编译b模块

c -- 加载:先加载a模块,再加载b模块

d -- 卸载:先卸载b模块,再卸载a模块


三、示例
代码a ,hello.c
#include <linux/module.h>

static int num =10;
static void show(void)
{
printk("show(),num = %d\n",num);
}
static int hello_init(void)
{
printk("hello_init");
return 0;
}
static void hello_exit(void)
{
printk("hello_exit \n");
}
EXPORT_SYMBOL(show);
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

代码b show.c
#include <linux/module.h>

extern void show(void);

static int show_init(void)
{
printk("show_init");
show();
return 0;
}
static void show_exit(void)
{
printk("show_exit \n");
}

MODULE_LICENSE("GPL");

module_init(show_init);
module_exit(show_exit);<strong>
</strong>

编译后加载模块,卸载模块,可以用 dmesg 查看内核打印信息。