1.1 内核模块的文件格式

以内核模块形式存在的驱动程序,比如demodev.ko,其在文件的数据组织形式上是ELF (Executable and Linkable Format)格式,更具体地,内核模块是一种普通的可重定位目标文件。用file命令查看demodev.ko文件,可以得到类似如下的输出:

dennis@AMDLinuxFGL:/$ file demodev.ko
demodev.ko: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

ELF是Linux下非常重要的一种文件格式,常见的可执行程序都是以ELF的形式存在。本书不会详细讨论ELF格式的技术细节,但是为了让读者能更好地理解后续的模块加载、导出符号和模块参数等相关主题,在这里我们结合Linux源代码中定义的ELF相关数据结构(基于32位体系架构),给出ELF格式的一个比较详细的结构图,如图1-1所示(这张图在后续的小节中会被多次引用):

图1-1 ELF文件视图格式

图1-1中忽略了驱动程序模块ELF文件中不会用到的Program header table。从图1-1可以看到,静态的ELF文件视图在说ELF文件视图时,它总是静态的。加上“静态的”是为了强调和“动态的”执行期ELF内存视图的对比。当然, ELF文件被加载时总是会先被放到某段内存区域中,此时称为“静态的”内存视图;而在后续的加载处理过程中,其在内存中搬移的视图则称为“动态的”内存视图,简称内存视图。总体上可分为三大部分:头部的ELF header,中间的Section和尾部的Section header table。

ELFheader部分

大小是52字节,位于文件头部。对于驱动模块文件而言,其中一些比较重要的数据成员如下:

e_type

表明文件类型,对于驱动模块,这个值是1,也就是说驱动模块是一个可定位的ELF文件(relocatable file)。

e_shoff

表明Section header table部分在文件中的偏移量。

e_shentsize

表明Section header table部分中每一个entry在本章中,entry具有特别的含义,特指Section header table中的一个entry。在本章接下来的代码表示中会多次用到这一用法:entry[i]表示Section header table中索引值为i的entry。的大小(以字节计)。

e_shnum

表明Section header table中有多少个entry。因此,Section header table的大小便为e_shentsize×e_shnum个字节。

e_shstrndx

与Section header entry中的sh_name一起用来指明对应的section的name。

Section部分

ELF文件的主体,位于文件视图中间部分的一个连续区域中。但是当模块被内核加载时,会根据各自属性被重新分配到新的内存区域(有些section也可能只是起辅助作用,因而在运行时并不占用实际的内存空间)。

Section header table部分

该部分位于文件视图的末尾,由若干个(具体个数由ELF header中的e_shnum变量指定) Section header entry组成,每个entry具有同样的数据结构类型。对于设备驱动模块而言,一些比较重要的数据成员如下:

sh_addr

这个值用来表示该entry所对应的section在内存中的实际地址。在静态的文件视图中,这个值为0,当模块被内核加载时,加载器会用该section在内存中的实际地址来改写sh_addr (如果section不占用内存空间,该值为0)。

sh_offset

表明对应的section在文件视图中的偏移量。

sh_size

表明对应的section在文件视图中的大小(以字节计)。类型为SHT_NOBITS的section例外,这种section在文件视图中不占有空间。

sh_entsize

主要用于由固定数量entry组成的表所构成的section,如符号表,此种情况下用来表示表中entry的大小。

以上简单介绍了内核模块所属ELF文件的一些主要数据成员,显然设备驱动程序并不会使用到这些数据,它们是给内核模块加载器在加载模块时使用的,这里只是为了给后续的模块加载过程的讨论做一个简单的技术铺垫(如果读者对ELF文件的技术细节感兴趣,这里推荐一个非常实用的在Linux环境下读取ELF文件信息的工具——readelf)。接下来在进行模块加载这个沉重的话题讨论前,先来看一个有趣的东西:模块是如何向外界导出符号信息的。