1.编译驱动程序的结构
编译驱动程序包括语言预处理器,编译器,汇编器和链接器,静态编译的过程如下:
链接器必须完成的两个任务是符号解析和重定位。符号解析的目的是将每个符号引用刚好和一个符号定义联系起来,因为代码节和数据节在编译时是从0地址开始的,所以链接器通过把每一个符号定义域存储器位置联系起来,并修改对这些符号的引用,实现重定位。
目标文件有3种形式:可重定位的目标文件,可执行的目标文件,共享目标文件。可重定位目标文件由编译产生,其典型格式为的ELF格式。每一个重定位目标模块m都有一个符号表,包含了m所定义和引用的符号信息。链接器解析符号引用的方法是将每个引用与他输入的可重定位目标文件的符号表中的一个确定的符号定义联系起来。
静态链接库是将相关目标模块打包而成的一个单独的文件,它可以当作链接器的输入。当链接器构造一个输出的可执行文件时,它只拷贝静态库里被程序引用的目标模块。因为链接时是从左到右扫描目标文件,因此一般把静态库放在链接序列的最后面,否则在排在静态库后面的目标文件若是包含对静态库的引用,那些引用将得不到解析。
经过符号解析以后链接器就知道他的输入目标模块中代码节和数据节的大小,开始重定位,在这个步骤中,将合并输入模块,并分配运行时地址,重定位由两步组成:重定位节和符号定义以及重定位节中的符号引用。重定位节和符号定义将所有相同类型的节合并成同一类型的新的聚合节。如存放代码的.data节全部合并成输出的可执行目标文件的.data节。然后链接器将运行时存储器地址赋给新的聚合节,赋给输入模块定义的每个节,以及赋给输入模块定义的每个符号。当这一步完成时,输入模块中的每一个全局变量和每个指令都有唯一的运行时存储器地址。重定位节中的符号引用,链接器修改代码节和数据节中对每一个符号的引用,使他们指向正确的运行时地址。
加载器将可执行目标文件的内容映射到存储器,并运行这个程序。链接器还可能生成部分链接的可执行目标文件,这样的文件中有对定义在共享库中的程序和数据的未解析的引用,在加载时,加载器将部分链接的可执行文件映射到存储器,然后调用动态链接器,它通过加载共享库和重定位程序中的引用来完成链接任务。
静态库的缺点是需要定期维护和更新,此外每次引用同一个函数都会将代码复制到程序中去,造成极大的空间浪费。而共享库是一个目标模块,,在运行时可以加载到任意的存储器位置,并和一个在存储器中的程序链接起来,这个过程称为动态链接,由动态链接器完成。在Unix系统中通常用.so来表示共享库,在WINDOW中则为.DLL。
动态链接的基本思路是当创建可执行文件时,静态执行一些链接,然后在程序加载时,动态完成链接过程。静态链接时没有任何共享库的代码和数据节被拷贝到可执行文件中,反之,链接器拷贝了一些重定位和符号表信息,他们使得运行时可以解析对动态库中代码和数据节的引用。加载器加载部分链接的目标文件,会发现该目标文件中包含动态链接器的路径名(动态链接器也是一个共享目标),然后加载器加载和运行这个动态链接器,接着动态链接器通过执行下面的重定位完成链接任务:重定位共享库的文本和数据带某个存储器段,重定位部分链接可执行文件中所有对由共享库中定义的符号的引用,最后,动态链接器将控制传递给应用程序。此刻开始,共享库的位置就固定了。此外,应用程序还可能在它运行时要求动态链接器加载和链接任意共享库,而无须在编译时链接那些库到应用中。
被编译为位置无关代码的共享库可以加载到任何地方,也可以在运行时被多个进程共享。为了加载,链接和访问共享库的函数和数据,应用程序还可以在运行时使用动态链接器。