C源程序编译运行处理过程如下图1.1所示:
- 第一步是进行预处理,预处理器将头文件进行展开,并进行相关的宏替换和处理最后生成.i文件 ( Intermediate Representation Code )。
2. 第二步由编译器将.i文件翻译成汇编代码输出为.s文件 ( Assembly Code ) ,由汇编代码和一些伪代码组成。
3. 第三步由汇编器将汇编文件转化成可重定位目标文件.o文件 ( Object File ),这个过程还会根据.s文件中由编译器生成的符号构造一张符号表,内部包含一个个符号表条目,用以链接使用。除此之外还会生成各个节。整个文件由二进制代码和数据组成,直接打开是无法阅读的,需要借助特殊工具,如objdump, readelf等。.o文件内部组成结构如下图1.2所示:
4. 第四步由链接器进行处理,其输入为可重定位目标文件,包括从.c文件生成的.o文件、特殊的可重定位目标文件——共享目标文件(.a静态库文件,.so动态库文件)。
链接器的两个任务是:完成符号解析和进行重定位。
所谓符号解析,即是关联符号定义和符号引用,以作者的理解是扫描输入文件看类似函数或变量的引用是否有对应的定义,如果没有则抛出错误终止链接,如果是只定义而未引用则可以抛出警告(如果开启了对应警告才会抛出);
而重定位即是关联符号定义和内存位置,以作者理解是:在链接之前生成的.s文件中,每个符号定义都没有明确的运行时地址(虚拟地址),然而操作变量或者调用函数之类的符号引用,都是需要一个明确的地址的(寄存器变量除外),所以就需要为这些符号分配一个运行时的虚拟地址,这就由链接中的重定位来完成
最后生成的文件也是由二进制代码和数据组成,相比.o文件基本组成单元——节,可执行目标文件的组成单元是各个叫段的数据。ELF可执行目标文件的典型组成结构如下图1.3所示:
从图1.3观之,可执行目标文件与可重定位目标文件的组成结构甚为相似,在可重定位目标文件中(.o文件),包含的.rel.text, .rel.data是代码节和数据节的重定位条目,然而经由链接器完成了重定位工作,所以最后生成的可执行目标文件中不再需要这个部分。
5. 第五步是加载,由加载器将可执行文件中的各个段加载到操作系统为进程分配的独立的虚拟地址空间内,对于所有Linux程序,其代码段的起始地址都是0x400000。进程虚拟地址空间布局如下图1.4所示:
但因为这是一个虚拟地址,最后会经过内存管理单元MMU(Memory Management Unit)的处理,将虚拟地址映射到不同的物理地址中,即使虚拟地址相同,其实际的物理地址也不尽相同,故而进程间使用相同的虚拟地址空间不仅不会产生冲突,反而有利于链接器的处理,因为每个进程虚拟地址空间布局规律都是一致的,分配地址便可按照固定模式处理,剩下需要具体映射到哪片物理地址,那是MMU的任务。
复制完相关段后,加载器跳转到程序入口点执行_start函数,再由_start函数执行__libc_start_main函数进行环境初始化,最后再调用我们的主程序main函数。
这也就是为什么我们的main函数需要一个返回值,因为我们的函数实际上不像普通的单片机程序一样直接运行在硬件之上,而是由操作系统调用,我们的返回值实际上是返回给调用我们主程序的操作系统。
评论0