2024年最全Linux EXPORT_SYMBOL宏详解(2),2024年最新已开源

最后的话

最近很多小伙伴找我要Linux学习资料,于是我翻箱倒柜,整理了一些优质资源,涵盖视频、电子书、PPT等共享给大家!

资料预览

给大家整理的视频资料:

给大家整理的电子书资料:

如果本文对你有帮助,欢迎点赞、收藏、转发给朋友,让我有持续创作的动力!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!


输出中U代表未解决的引用,对于未解决的引用符号,不显示该符号相对于模块起始地址的相对偏移地址,即没有符号值。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/1ab1c807a09047e6834d084d168e2dd8.png)


查看模块所有的未定义引用:



nm nf_conntrack.ko | grep ‘<U>’


![在这里插入图片描述](https://img-blog.csdnimg.cn/f725f375acf449f78bc73ee2671a56ea.png)


## 二、EXPORT\_SYMBOL源码详解



// linux-4.10.1/include/linux/export.h

struct kernel_symbol
{
unsigned long value;
const char *name;
};

/* For every exported symbol, place a struct in the __ksymtab section */
#define ___EXPORT_SYMBOL(sym, sec)
extern typeof(sym) sym;
__CRC_SYMBOL(sym, sec)
static const char __kstrtab_##sym[]
__attribute__((section(“__ksymtab_strings”), aligned(1)))
= VMLINUX_SYMBOL_STR(sym);
static const struct kernel_symbol __ksymtab_##sym
__used
__attribute__((section(“___ksymtab” sec “+” #sym), used))
= { (unsigned long)&sym, __kstrtab_##sym }

#define EXPORT_SYMBOL(sym)
__EXPORT_SYMBOL(sym, “”)

#define EXPORT_SYMBOL_GPL(sym)
__EXPORT_SYMBOL(sym, “_gpl”)

#define EXPORT_SYMBOL_GPL_FUTURE(sym)
__EXPORT_SYMBOL(sym, “_gpl_future”)


C语言的宏定义中 # 和 ## 运算符:  
 #运算符是将其后面的宏参数转化为字符串,用来创建字符串,例如#sym,表示"sym"。  
 ##运算符用来替换粘合两个不同的符号,例如\_\_ksymtab\_##sym,就表示"\_\_ksymtab\_sym"。


**attribute**:\_\_attribute\_\_实际上是GCC的一种编译器命令,用来指示编译器执行实现某些高级操作。\_\_attribute\_\_可以设置函数属性(Function Attribute)、变量属性(Variable Attribute)和类型属性(Type Attribute),函数属性可以帮助开发人员向函数声明中添加一些特性,这可以使编译器在错误检查方面增强。


使用EXPORT\_SYMBOL(export\_function)导出export\_function函数,上述宏可以扩展为:



static const char __kstrtab_export_function[] = “export_function”;
static const struct kernel_symbol __ksymtab_export_function = {(unsigned long)&export_function, __kstrtab_export_function };


第一个变量是一个字符串静态变量,用来表示导出的符号名称"export\_function"。  
 第二个变量类型是struct kernel\_symbol数据结构,用来表示一个内核符号的实例,struct kernel\_symbol的定义为:



// linux-4.10.1/include/linux/export.h

struct kernel_symbol
{
unsigned long value;
const char *name;
};


其中,value是该符号在内存中的地址,name是符号名。所以,由该数据结构可以知道,用EXPORT\_SYMBOL(export\_function)来导出符号"export\_function",实际上是要通过struct kernel\_symbol的一个对象告诉外部关于这个符号的两点信息:符号名称和地址。这样使得内核根据函数的字符串名称,即可找到匹配的代码地址,在解决未定义的引用时需要这要做。


因此,由EXPORT\_SYMBOL等宏导出的符号,与一般的变量定义并没有实质性的差异,唯一的不同点在于它们被放在了特定的section中。


上面的符号"export\_function"会放在"\_\_ksymtab\_strings"的section中,struct kernel\_symbol \_\_ksymtab\_export\_function会放在  
 “\_\_ksymtab"的section中。对于EXPORT\_SYMBOL\_GPL和EXPORT\_SYMBOL\_GPL\_FUTURE而言,其struct kernel\_symbol实例所在的section名称则分别为”\_\_ksymtab\_gpl"和"\_\_ksymtab\_gpl\_future")。


我已 nf\_conntrack.ko 模块为例子:



readelf -S nf_conntrack.ko


![在这里插入图片描述](https://img-blog.csdnimg.cn/3af7c1f376f24e31a566dc9011c7144c.png)  
 对这些section的使用需要经过一个中间环节,即链接脚本与链接器部分。链接脚本告诉链接器把所有目标文件中的名为“\_\_ksymtab”的section放置在最终内核(或者是内核模块)映像文件的名为“\_\_ksymtab”的section中(对于目标文件中的名为“\_\_ksymtab\_gpl”、“\_\_ksymtab\_gpl\_future”、“\_\_kcrctab”、“\_\_kcrctab\_gpl”和“\_\_kcrctab\_gpl\_future”的section都同样处理)。


如下所示:



// linux-4.10.1/include/asm-generic/vmlinux.lds.h

/* Kernel symbol table: Normal symbols */
__ksymtab : AT(ADDR(__ksymtab) - LOAD_OFFSET) {
VMLINUX_SYMBOL(__start___ksymtab) = .;
KEEP(*(SORT(___ksymtab+*)))
VMLINUX_SYMBOL(__stop___ksymtab) = .;
}

/* Kernel symbol table: GPL-only symbols */
__ksymtab_gpl : AT(ADDR(__ksymtab_gpl) - LOAD_OFFSET) {
VMLINUX_SYMBOL(__start___ksymtab_gpl) = .;
KEEP(*(SORT(___ksymtab_gpl+*)))
VMLINUX_SYMBOL(__stop___ksymtab_gpl) = .;
} \

......
								\
/\* Kernel symbol table: GPL-future-only symbols \*/		\
__ksymtab_gpl_future : AT(ADDR(__ksymtab_gpl_future) - LOAD_OFFSET) { \
	VMLINUX\_SYMBOL(__start___ksymtab_gpl_future) = .;	\
	KEEP(\*(SORT(___ksymtab_gpl_future+\*)))			\
	VMLINUX\_SYMBOL(__stop___ksymtab_gpl_future) = .;	\
}								\
								\
/\* Kernel symbol table: Normal symbols \*/			\
__kcrctab         : AT(ADDR(__kcrctab) - LOAD_OFFSET) {		\
	VMLINUX\_SYMBOL(__start___kcrctab) = .;			\
	KEEP(\*(SORT(___kcrctab+\*)))				\
	VMLINUX\_SYMBOL(__stop___kcrctab) = .;			\
}								\
								\
/\* Kernel symbol table: GPL-only symbols \*/			\
__kcrctab_gpl     : AT(ADDR(__kcrctab_gpl) - LOAD_OFFSET) {	\
	VMLINUX\_SYMBOL(__start___kcrctab_gpl) = .;		\
	KEEP(\*(SORT(___kcrctab_gpl+\*)))				\
	VMLINUX\_SYMBOL(__stop___kcrctab_gpl) = .;		\
}								\
								\
......
								\
/\* Kernel symbol table: GPL-future-only symbols \*/		\
__kcrctab_gpl_future : AT(ADDR(__kcrctab_gpl_future) - LOAD_OFFSET) { \
	VMLINUX\_SYMBOL(__start___kcrctab_gpl_future) = .;	\
	KEEP(\*(SORT(___kcrctab_gpl_future+\*)))			\
	VMLINUX\_SYMBOL(__stop___kcrctab_gpl_future) = .;	\
}								\
								\
/\* Kernel symbol table: strings \*/				\
    __ksymtab_strings : AT(ADDR(__ksymtab_strings) - LOAD_OFFSET) {	\
	\*(__ksymtab_strings)					\
}								\

这里之所以要把所有向外界导出的符号统一放到一个特殊的section里面,是为了在加载其他模块时用来处理那些“未解决的引用”符号。上述由链接脚本定义的几个变量\_\_start\_\_\_ksymtab、\_\_stop\_\_\_ksymtab、\_\_start\_\_\_ksymtab\_gpl、\_\_stop\_\_\_ksymtab\_gpl、\_\_start\_\_\_ksymtab\_gpl\_future、\_\_stop\_\_\_ksymtab\_gpl\_future等,它们会在对内核或者是某一内核模块的导出符号表进行查找时用到。


内核源码中为使用这些链接器产生的变量作了如下的声明:



// linux-4.10.1/kernel/module.c

/* Provided by the linker */
extern const struct kernel_symbol __start___ksymtab[];
extern const struct kernel_symbol __stop___ksymtab[];
extern const struct kernel_symbol __start___ksymtab_gpl[];
extern const struct kernel_symbol __stop___ksymtab_gpl[];
extern const struct kernel_symbol __start___ksymtab_gpl_future[];
extern const struct kernel_symbol __stop___ksymtab_gpl_future[];
extern const s32 __start___kcrctab[];
extern const s32 __start___kcrctab_gpl[];
extern const s32 __start___kcrctab_gpl_future[];


内核代码便可以直接使用这些变量而不会引起编译错误。内核模块的加载器在处理模块中“未解决的引用”的符号时,会使用到这里定义的这些变量。


## 三、模块导出符号


由前面我们可以知道模块不仅可以使用内核或者其他模块导出的符号,而且可以向外部导出自己的符号,模块导出符号使用的宏和内核导出符号所使用的完全一样:EXPORT\_SYMBOL、EXPORT\_SYMBOL\_GPL和EXPORT\_SYMBOL\_FUTURE。


内核模块会把导出的符号分别放到“\_\_ksymtab”、“\_\_ksymtab\_gpl”和“\_\_ksymtab\_gpl\_future”section中。如果一个内核模块向外界导出了自己的符号,那么将由模块的编译工具链负责生成这些导出符号section,而且这些section都带有A标志,所以在模块加载过程中会被搬移到CORE section区域中。如果模块没有向外界导出任何符号,那么在模块的ELF文件中,将不会产生这些section。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/87fb846b5fc749318ed93cc331a8136b.png)  
 备注:A (alloc),表示 Section 的属性,alloc(SHF\_ALLOC) 表示 Section 在模块运行期间需要占据内存。  
 没有SHF\_ALLOC标志的section,这样的section最终不占有实际内存地址。


显然,内核需要对模块导出的符号进行管理,以便在处理其他模块中那些“未解决的引用”符号时能够找到这些符号。内核对模块导出的符号的管理使用到了struct module结构中如下的成员变量。


内核中为每一个内核模块都分配了一个struct module实例:



// linux-4.10.1/include/linux/module.h

struct module {

/* Exported symbols */
const struct kernel_symbol *syms; //内核模块导出的符号所在起始地址
const s32 *crcs; //内核模块导出符号的校验码所在起始地址
unsigned int num_syms; //核模块导出的符号的数目

/* GPL-only exported symbols. */
unsigned int num_gpl_syms;
const struct kernel_symbol *gpl_syms;
const s32 *gpl_crcs;

/* symbols that will be GPL-only in the near future. */
const struct kernel_symbol *gpl_future_syms;
const s32 *gpl_future_crcs;
unsigned int num_gpl_future_syms;

}


以struct module结构体的 Exported symbols 为例:  
 const struct kernel\_symbol \*syms表示:内核模块导出的符号所在起始地址。  
 const s32 \*crcs表示:内核模块导出符号的校验码所在起始地址。  
 unsigned int num\_syms:表示内核模块导出的符号的数目。


syms是一个数组,有num\_syms个数组项,数组的类型是struct kernel\_symbol,负责将表示符name分配到内存地址value。  
 crcs也是一个有num\_syms个数组项的数组,存储了导出符号的校验和,用于实现版本控制。



// linux-4.10.1/include/linux/export.h

struct kernel_symbol
{
unsigned long value;

为了做好运维面试路上的助攻手,特整理了上百道 【运维技术栈面试题集锦】 ,让你面试不慌心不跳,高薪offer怀里抱!

这次整理的面试题,小到shell、MySQL,大到K8s等云原生技术栈,不仅适合运维新人入行面试需要,还适用于想提升进阶跳槽加薪的运维朋友。

本份面试集锦涵盖了

  • 174 道运维工程师面试题
  • 128道k8s面试题
  • 108道shell脚本面试题
  • 200道Linux面试题
  • 51道docker面试题
  • 35道Jenkis面试题
  • 78道MongoDB面试题
  • 17道ansible面试题
  • 60道dubbo面试题
  • 53道kafka面试
  • 18道mysql面试题
  • 40道nginx面试题
  • 77道redis面试题
  • 28道zookeeper

总计 1000+ 道面试题, 内容 又全含金量又高

  • 174道运维工程师面试题

1、什么是运维?

2、在工作中,运维人员经常需要跟运营人员打交道,请问运营人员是做什么工作的?

3、现在给你三百台服务器,你怎么对他们进行管理?

4、简述raid0 raid1raid5二种工作模式的工作原理及特点

5、LVS、Nginx、HAproxy有什么区别?工作中你怎么选择?

6、Squid、Varinsh和Nginx有什么区别,工作中你怎么选择?

7、Tomcat和Resin有什么区别,工作中你怎么选择?

8、什么是中间件?什么是jdk?

9、讲述一下Tomcat8005、8009、8080三个端口的含义?

10、什么叫CDN?

11、什么叫网站灰度发布?

12、简述DNS进行域名解析的过程?

13、RabbitMQ是什么东西?

14、讲一下Keepalived的工作原理?

15、讲述一下LVS三种模式的工作过程?

16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?

17、如何重置mysql root密码?

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

5、讲述一下LVS三种模式的工作过程?

16、mysql的innodb如何定位锁问题,mysql如何减少主从复制延迟?

17、如何重置mysql root密码?

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以点击这里获取!

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值