如何运用Linux内核访问另外一个模块的函数和变量?

一口Linux

    一、问题整理
    内核中两个模块,一个A,一个B,A模块中有操作函数,B模块要调用A模块的函数。
    
    二、分析
    这是一个驱动工程师经常遇到的一个问题,该问题其实是模块符号导出问题,实现该功能比较简单,借助EXPORT_SYMBOL()即可。
    1. 什么是符号?
    这里的符号主要指的是全局变量和函数,静态全局变量其实也可以被另外一个模块访问到。
    2. 为什么要导出符号?
    ↓Linux内核采用的是以模块化形式管理内核代码。内核中的每个模块相互之间是相互独立的,也就是说A模块的全局变量和函数,B模块是无法直接访问的。
    有些时候,我们写一些模块代码的时候,发现部分函数功能别人已经实现了,此时我们就想如果我们可以调用他们已经实现好的函数接口就好了。那如何才能做到这点呢?
    就靠符号导出了,也就是说你可以把你实现的函数接口和全局变量导出,以供其他模块使用。
    在Linux内核的世界里,如果一个模块已经以静态的方式编译进的内核,那么它导出的符号就会出现在全局的内核符号表中。
    在Ubuntu 14.04系统中,Linux内核的全局符号表存放在以下文件:
    /usr/src/linux-headers-3.2.0-29-generic-pae/Module.symvers
    如果打开这个文件,可以发现里面的内容就是:
    Addr------->符号名------>模块名------>导出符号的宏
    
    3. 如何导出符号?
    Linux内核给我们提供了两个宏:
    EXPORT_SYMBOL(name);
    EXPORT_SYMBOL_GPL(name);
    上面宏定义的任一个使得给定的符号在模块外可用;GPL版本的宏定义只能使符号对GPL许可的模块可用;符号必须在模块文件的全局部分输出,在任何函数之外,因为宏定义扩展成一个特殊用途的并被期望是全局存取的变量的声明。
    4. 模块编译时,如何寻找使用的符号?
    a.在本模块中符号表中,寻找符号(函数或变量实现)b.在内核全局符号表中寻找c.在模块目录下的Module.symvers文件中寻找
    5. 案例演示
    模块A导出全局变量global_var和函数show两个符号供模块B使用。
    A模块
    #include <linux/init.h>
    #include <linux/module.h>
    static int global_var = 100;
    static void show(void)
    {
     printk("show():  global_var =%d ",global_var);
    }
    static int hello_init(void)
    {
     printk("module b :global_var=%d",global_var);
     return 0;
    }
    static void hello_exit(void)
    {
     printk("hello_exit ");
     return;
    }EXPORT_SYMBOL(global_var);
    EXPORT_SYMBOL(show);
    MODULE_AUTHOR("yikoulinux");
    MODULE_LICENSE("GPL");
    module_init(hello_init);
    module_exit(hello_exit);
    B模块
    #include <linux/init.h>
    #include <linux/module.h>
    extern int global_var;
    extern  void show(void);
    static int hello_init(void)
    {
     printk("module a: global_var= %d",global_var);
     show();
     return 0;
    }
    static void hello_exit(void)
    {
     printk("hello_exit ");
     return;
    }
    MODULE_AUTHOR("yikoulinux");
    MODULE_LICENSE("GPL");
    module_init(hello_init);
    module_exit(hello_exit);
    调试步骤:
    1.编译模块A,然后加载模块A,在模块A编译好后,在它的当前目录会看到一个Module.symvers文件,这里存放的就是我们模块A导出的符号。2.将模块A编译生成的Module.symvers文件拷贝到模块B目录下,然后编译模块B,加载模块B。3.通过dmesg查看模块打印的信息。打印信息如下:
    
    由结果可知,我们在B模块中访问到了模块A的全局变量global_var以及函数show。