EXPORT_SYMBOL机制

Linux内核由主内核ELF格式文件(vmlinux)和许多内核模块组成。在构成vmliunx主内核文件中,每一个被声明EXPORT_SYMBOL的符号,都只有一个目的,就是让vmlinux主内核文件之外的内核模块使用这个变量或者函数符号,这个就是给到内核模块来使用的内核导出符号。

组成vmlinux主内核的所有文件(静态)都在同一时间编译和生成,即所有的符号引用都在静态链接阶段完成了。而内核模块的出现让事情有了变化(可能在后期动态加入),内核模块不可避免要使用主内核提供的基础设施(比如变量或者函数),作为独立编译链接的内核模块,必须解决符号引用的问题,也称为‘未解决引用’。 处理 “未解决引用” 问题的本质是在模块加载期间找到当前 “未解决的引用” 符号在内存中的实际目标地址。

所以当内核配置不支持动态加载ko时,EXPORT_SYMBOL宏为空

内核和内核模块通过符号表的形式向外部世界导出符号的相关信息,这种导出符号的方式在代码实现是通过EXPORT_SYMBOL宏的形式。

符号:全局变量或函数

符号表:每个导出符号对应一个结构体,如下。只存此类结构体地址的section称为符号表。

struct kernel_symbol {
    unsigned long value; // 符号的内存地址
    const char *name;    // 符号名对应的字符串地址,如"g_XXX"
    const char *namespace; // 指示是内核符号还是内核模块符号
};

 

 

EXPORT_SYMBOL:

#define EXPORT_SYMBOL(sym)        _EXPORT_SYMBOL(sym, "")

#define _EXPORT_SYMBOL(sym, sec)    __EXPORT_SYMBOL(sym, sec, "")

#define __EXPORT_SYMBOL(sym, sec, ns)    ___EXPORT_SYMBOL(sym, sec, ns)

#define ___EXPORT_SYMBOL(sym, sec, ns)                        \
    extern typeof(sym) sym;                            \
    extern const char __kstrtab_##sym[];                    \
    extern const char __kstrtabns_##sym[];                    \
    __CRC_SYMBOL(sym, sec);                            \
    asm("    .section \"__ksymtab_strings\",\"aMS\",%progbits,1    \n"    \
        "__kstrtab_" #sym ":                    \n"    \
        "    .asciz     \"" #sym "\"                    \n"    \
        "__kstrtabns_" #sym ":                    \n"    \
        "    .asciz     \"" ns "\"                    \n"    \
        "    .previous                        \n");    \
    __KSYMTAB_ENTRY(sym, sec)

#define __KSYMTAB_ENTRY(sym, sec)                    \
    static const struct kernel_symbol __ksymtab_##sym        \
    __attribute__((section("___ksymtab" sec "+" #sym), used))    \
    __aligned(sizeof(void *))                    \
    = { (unsigned long)&sym, __kstrtab_##sym, __kstrtabns_##sym }

 

EXPORT_SYMBOL(sym)展开后为:定义一个 struct kernel_symbol 类型的变量,存在名为 "___ksymtab+sym" 的 section中。

 

insmod时,模块寻找依赖的符号,就是在___ksymtab+sym中遍历。从name指针获取函数名字符串,比对一致后,获取value值(内核函数指针)。即模块需要的符号。

 

编译内核后,会生成 vmlinux.symvers、modules-only.symvers、Module.symvers 这三个以 .symvers 后缀的文件,里面就是导出符号的信息

 

 

 

 

 

 

 

 

 

posted @ 2023-02-11 22:09  流水灯  阅读(509)  评论(0编辑  收藏  举报