2024年Linux最全Linux EXPORT_SYMBOL宏详解,2024年最新2024Linux运维面试宝典

最全的Linux教程,Linux从入门到精通

======================

  1. linux从入门到精通(第2版)

  2. Linux系统移植

  3. Linux驱动开发入门与实战

  4. LINUX 系统移植 第2版

  5. Linux开源网络全栈详解 从DPDK到OpenFlow

华为18级工程师呕心沥血撰写3000页Linux学习笔记教程

第一份《Linux从入门到精通》466页

====================

内容简介

====

本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。

华为18级工程师呕心沥血撰写3000页Linux学习笔记教程

本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。

需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论

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

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

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

我们都知道Linux 是单内核,作为一个不可分割的静态执行库,内核通常以单个静态的二进制文件形式存放在磁盘中,在一个单独的内核地址空间上运行,由于内核都处在同一内核地址空间,因此内核可以直接调用函数。如果只是单一的内核映像存在,而没有内核模块存在,EXPORT_SYMBOL通常是没有啥意义的,因为对于静态编译链接而成的单一的内核映像而言,所有的符号引用都将在静态链接阶段完成。

由于Linux支持动态地加载内核模块,运行内核在运行时根据需要动态地加载模块,由于有内核模块的存在,内核模块通常会使用内核映像中的符号,因此使用EXPORT_SYMBOL将内核映像的符号导出,这样在内核模块中就能使用该符号了。

内核模块是独立编译的,然后加载到正在运行的内核中,当编译内核模块时,编译器引用到内核中的符号时,会产生未解决的引用,处理“未解决引用”问题的就要在模块加载期间找到当前“未解决的引用”符号在内存中的实际目标地址。内核和内核模块通过符号表的形式向外部导出符号的相关信息,这种导出符号的方式在代码层面以EXPORT_SYMBOL宏定义的形式存在。

解决模块中的未解决引用是在模块的加载阶段,用nm命名查看模块未解决的引用:

 nm nf_conntrack.ko 

输出中U代表未解决的引用,对于未解决的引用符号,不显示该符号相对于模块起始地址的相对偏移地址,即没有符号值。
在这里插入图片描述

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

 nm nf_conntrack.ko | grep '\<U\>'

在这里插入图片描述

二、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

在这里插入图片描述
对这些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。
在这里插入图片描述
备注: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表示:内核模块导出的符号所在起始地址。

最全的Linux教程,Linux从入门到精通

======================

  1. linux从入门到精通(第2版)

  2. Linux系统移植

  3. Linux驱动开发入门与实战

  4. LINUX 系统移植 第2版

  5. Linux开源网络全栈详解 从DPDK到OpenFlow

华为18级工程师呕心沥血撰写3000页Linux学习笔记教程

第一份《Linux从入门到精通》466页

====================

内容简介

====

本书是获得了很多读者好评的Linux经典畅销书**《Linux从入门到精通》的第2版**。本书第1版出版后曾经多次印刷,并被51CTO读书频道评为“最受读者喜爱的原创IT技术图书奖”。本书第﹖版以最新的Ubuntu 12.04为版本,循序渐进地向读者介绍了Linux 的基础应用、系统管理、网络应用、娱乐和办公、程序开发、服务器配置、系统安全等。本书附带1张光盘,内容为本书配套多媒体教学视频。另外,本书还为读者提供了大量的Linux学习资料和Ubuntu安装镜像文件,供读者免费下载。

华为18级工程师呕心沥血撰写3000页Linux学习笔记教程

本书适合广大Linux初中级用户、开源软件爱好者和大专院校的学生阅读,同时也非常适合准备从事Linux平台开发的各类人员。

需要《Linux入门到精通》、《linux系统移植》、《Linux驱动开发入门实战》、《Linux开源网络全栈》电子书籍及教程的工程师朋友们劳烦您转发+评论

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

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

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

  • 10
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值