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 后缀的文件,里面就是导出符号的信息
标签:__,符号,SYMBOL,sym,EXPORT,内核,机制 From: https://www.cnblogs.com/god-of-death/p/17112692.html