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 header,中间的Section和尾部的Section header table。
ELFheader部分
大小是52字节,位于文件头部。对于驱动模块文件而言,其中一些比较重要的数据成员如下:
e_type
表明文件类型,对于驱动模块,这个值是1,也就是说驱动模块是一个可定位的ELF文件(relocatable file)。
e_shoff
表明Section header table部分在文件中的偏移量。
e_shentsize
表明Section header table部分中每一个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)。接下来在进行模块加载这个沉重的话题讨论前,先来看一个有趣的东西:模块是如何向外界导出符号信息的。