内核模块的文件格式与EXPORT_SYMBOL的实现

模块的文件格式与EXPORT_SYMBOL的实现



模块的文件格式

以内核模块形式存在的驱动程序,比如demodev.ko,其在文件的数据组织形式上是ELF(Executable and Linkable Format)格式,更具体地,内核模块是一种普通的可重定位目标文件。file一个hello.ko得到:
在这里插入图片描述

ELF是Linux下非常重要的一种文件格式,常见的可执行程序都是以ELF的形式存在。在这里我们结合Linux源代码中定义的ELF相关数据结构(基于32位体系架构),给出ELF格式的一个比较详细的结构图:
在这里插入图片描述
图中忽略了驱动程序模块ELF文件中不会用到的Program header table从图中可以看到,静态的ELF文件视图3总体上可分为三大部分:头部的ELF header,中间的section和尾部的Section header table。

  • ELF header
    大小是52字节,位于文件头部。
typedef struct elf32_hdr {
	unsigned char e_ident[EI_NIDENT];
	Elf32_Half e_type;/*表明文件类型,对于驱动模块,这个值是1,也就是说驱动模块是一个可定位的ELF文件〈relocatable file)。*/
	Elf32_Half e_machine;
	Elf32_Word e_version;
	Elf32_Addr e_entry;	/* Entry point */
	Elf32_Off e_phoff;
	Elf32_Off e_shoff;/*表明Section heade rtable部分在文件中的偏移量。*/
	Elf32_Word e_flags;
	Elf32_Half e_ehsize;
	Elf32_Half e_phentsize;
	Elf32_Half e_phnum;
	Elf32_Half e_shentsize;/*表明Section header table部分中每一个entry的大小(以字节计)。*/
	Elf32_Half e_shnum;/*表明Section header table中有多少个entry。因此,Section header table的大小便为e_shentsize x e_shnum个字节。*/
	Elf32_Half e_shstrndx;/*与section header entry中的sh_name一起用来指明对应的section的name。*/
} Elf32_Ehdr;
  • Section
    ELF文件的主体,位于文件视图中间部分的一个连续区域中。但是当模块被内核加载时,会根据各自属性被重新分配到新的内存区域(有些section也可能只是起辅助作用,因而在运行时并不占用实际的内存空间)。

  • Section header table
    该部分位于文件视图的末尾,由若干个(具体个数由ELF header中的e_shnum变量指定)Section header entry组成,每个entry具有同样的数据结构类型。

typedef struct elf32_shdr {
  Elf32_Word	sh_name;
  Elf32_Word	sh_type;
  Elf32_Word	sh_flags;
  Elf32_Addr	sh_addr;/*这个值用来表示该entry所对应的section在内存中的实际地址。在静态的文件视图中,这个值为0,当模块被内核加载时,加载器会用该在内存中的实际地址来改写sh_addr〈如果section不占用内存空间,该值为0)。*/
  Elf32_Off	sh_offset;/*表明对应的section在文件视图中的偏移量。*/
  Elf32_Word	sh_size;/*表明对应的section在文件视图中的大小〈以字节计)。类型为SHT_NOBITS的section例外,这种n在文件视图中不占有空间。*/
  Elf32_Word	sh_link;
  Elf32_Word	sh_info;
  Elf32_Word	sh_addralign;
  Elf32_Word	sh_entsize;/*主要用于由固定数量entry组成的表所构成的section,如符号表,此种情况下用来表示表中entry的大小。*/
} Elf32_Shdr;

EXPORT_SYMBOL的实现

看过Linux内核源码的读者应该知道,源码中充斥着像EXPORT_SYMBOL这样的宏,在我们自己的设备驱动程序中也经常会发现它的身影。大部分时间里,我们只知道它用来向外界导出一个符号,仅此而己。我们对这些宏是如此习惯,以至于常常忽略其存在的意义,更不用说去仔细探究其背后的实现原理了。然而这些不起眼的宏却有着大用场,如果没有它们,我们的驱动程序甚至连printk这样常见的内核函数都不能使用。

内核和内核模块通过符号表的形式向外部世界导出符号的相关信息,这种导出符号的方式在代码层面以EXPORT_SYMBOL宏定义的形式存在。从全局来看,EXPORT_SYMBOL这类宏功能的完整实现需要经过三个部分来达成:EXPORT_SYMBOL宏定义部分,链接脚本链接器部分和使用导出符号部分。本节讲述前两个部分,第二部分的描述将延后到模块加载的相关段落。

//导出符号
/*
 *__CRC_SYMBOL用来作为版本控制信息使用,
 *每个EXPORT_SYMBOL宏实际上定义了两个变量:
 *第一个变量是个简单的char型指针,用来表示导出的符号名称:
 *第二个变量类型是struct_kernel_symbol数据结构,用来表示一个内核符号的实例,
 */
#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);				\
	extern const struct kernel_symbol __ksymtab_##sym;	\
	__visible const struct kernel_symbol __ksymtab_##sym	\
	__used							\
	__attribute__((section("___ksymtab" sec "+" #sym), unused))	\
	= { (unsigned long)&sym, __kstrtab_##sym }

kernel_symbol 定义为:

struct kernel_symbol
{
	unsigned long value;//该符号在内存中的地址
	const char *name;//符号名。
};

通过struct kernel_symbol的一个对象告诉外部世界关于这个符号的两点信息:符号名称和地址。可见,由EXPORT_SYMBOL等宏导出的符号,与一般的变量宁并没有实质性的差异,唯一的不同点在于它们被放在了特定的section中。

以EXPORT_SYMBOL(my_exp_function)为具体例子,即向外部导出一个名为my_exp_function的函数。

__CRC_SYMBOL(sym, sec)					\
	static const char __kstrtab_my_exp_function[]			\
	__attribute__((section("__ksymtab_strings"), aligned(1))) \
	= VMLINUX_SYMBOL_STR(my_exp_function);				\
	extern const struct kernel_symbol __ksymtab_my_exp_function;	\

上面的__kstrtabmy_exp_function会被放置在一个名为“__ksymtab_strings”的section中,__ksymtab_my_exp_function会放置在一个名为“__ksymtab”的section中(对于EXPORT_SYMBOLGPL和EXPORT_SYMBOL_GPLFUTURE而言,其struct kernel_symbol实例所在的section名称则分别为“ksymtab_gpl”和“ksymtab_gpl_future”)。

对这些section的使用需要经过一个中间环节,即链接脚本与链接器部分。链接脚本告诉链接器把所有目标文件中的名为“__ksymtab”的section放置在最终内核〈或者是内核模块)映像文件的名为“__ksymtab”的section中(对于目标文件中的名为"__ksymtab_gpl”、“__ksymtab_gpl_future”、”__kcrctab"、“__kcrctab_gpl_future”、“__kcrctab”、“kcrctab_gpl”和“kcrctab_gpl_future”的section都同样处理),看看下面的这个具体的链接脚本的例子就很清楚了。

/* Kernel symbol table: Normal symbols */			\
	__ksymtab         : AT(ADDR(__ksymtab) - LOAD_OFFSET) {		\
		VMLINUX_SYMBOL(__start___ksymtab) = .;			\
		*(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) = .;		\
		*(SORT(___ksymtab_gpl+*))				\
		VMLINUX_SYMBOL(__stop___ksymtab_gpl) = .;		\
	}								\
									\
	/* Kernel symbol table: Normal unused symbols */		\
	__ksymtab_unused  : AT(ADDR(__ksymtab_unused) - LOAD_OFFSET) {	\
		VMLINUX_SYMBOL(__start___ksymtab_unused) = .;		\
		*(SORT(___ksymtab_unused+*))				\
		VMLINUX_SYMBOL(__stop___ksymtab_unused) = .;		\
	}								\
									\
	/* Kernel symbol table: GPL-only unused symbols */		\
	__ksymtab_unused_gpl : AT(ADDR(__ksymtab_unused_gpl) - LOAD_OFFSET) { \
		VMLINUX_SYMBOL(__start___ksymtab_unused_gpl) = .;	\
		*(SORT(___ksymtab_unused_gpl+*))			\
		VMLINUX_SYMBOL(__stop___ksymtab_unused_gpl) = .;	\
	}								\
									\
	/* Kernel symbol table: GPL-future-only symbols */		\
	__ksymtab_gpl_future : AT(ADDR(__ksymtab_gpl_future) - LOAD_OFFSET) { \
		VMLINUX_SYMBOL(__start___ksymtab_gpl_future) = .;	\
		*(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) = .;			\
		*(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) = .;		\
		*(SORT(___kcrctab_gpl+*))				\
		VMLINUX_SYMBOL(__stop___kcrctab_gpl) = .;		\
	}								\
									\
	/* Kernel symbol table: Normal unused symbols */		\
	__kcrctab_unused  : AT(ADDR(__kcrctab_unused) - LOAD_OFFSET) {	\
		VMLINUX_SYMBOL(__start___kcrctab_unused) = .;		\
		*(SORT(___kcrctab_unused+*))				\
		VMLINUX_SYMBOL(__stop___kcrctab_unused) = .;		\
	}								\
									\
	/* Kernel symbol table: GPL-only unused symbols */		\
	__kcrctab_unused_gpl : AT(ADDR(__kcrctab_unused_gpl) - LOAD_OFFSET) { \
		VMLINUX_SYMBOL(__start___kcrctab_unused_gpl) = .;	\
		*(SORT(___kcrctab_unused_gpl+*))			\
		VMLINUX_SYMBOL(__stop___kcrctab_unused_gpl) = .;	\
	}								\
									\
	/* Kernel symbol table: GPL-future-only symbols */		\
	__kcrctab_gpl_future : AT(ADDR(__kcrctab_gpl_future) - LOAD_OFFSET) { \
		VMLINUX_SYMBOL(__start___kcrctab_gpl_future) = .;	\
		*(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,它们会在对内核或者是某一内核模块的导出符号表进行查找时用到。

/* 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 unsigned long __start___kcrctab[];
extern const unsigned long __start___kcrctab_gpl[];
extern const unsigned long __start___kcrctab_gpl_future[];
#ifdef CONFIG_UNUSED_SYMBOLS
extern const struct kernel_symbol __start___ksymtab_unused[];
extern const struct kernel_symbol __stop___ksymtab_unused[];
extern const struct kernel_symbol __start___ksymtab_unused_gpl[];
extern const struct kernel_symbol __stop___ksymtab_unused_gpl[];
extern const unsigned long __start___kcrctab_unused[];
extern const unsigned long __start___kcrctab_unused_gpl[];

如此,内核代码便可以直接使用这些变量而不会引起编译错误。

内核模块的加载器在处理模块中“未解决的引用”的符号时,会使用到这里定义的这些变量。

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

苦梨甜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值