文章目录
- 1. 可执行程序格式
- 2. 可执行程序加载
- 3. 动态库的加载
1. 可执行程序格式
Linux操作系统上的可执行文件格式是ELF(Executable and Linkable Format)
。ELF是一种灵活的、可扩展的文件格式,用于存储可执行程序、共享库和目标文件等二进制文件。
ELF文件采用分段(Segment)的结构,每个段都有不同的作用和属性。常见的段包括:
-
执行段(.text):包含程序的机器代码,用于执行程序的指令。
-
数据段(.data):包含程序的已初始化全局变量和静态变量等数据。
-
未初始化数据段(.bss):包含程序的未初始化全局变量和静态变量等数据。
-
符号表段(.symtab):包含程序的符号表,记录了程序的全局变量、函数、库的引用等信息。
此外,ELF文件格式还定义了一些特殊的节头表(Section Header Table)和程序头表(Program Header Table)来组织文件中的段和节信息。
如下图所示:
因为目标文件.o
也是ELF格式,那么将目标文件链接为可执行程序实际上就是将相同属性的section合并:
在Linux操作系统中,通过elf格式的可执行文件,操作系统可以读取文件中的节和段信息,并加载到内存中执行。这种文件格式的灵活性和可扩展性,使得Linux系统具有较强的兼容性和可移植性。
2. 可执行程序加载
因为可执行程序在Linux内是以ELF
格式呈现的,而ELF
包含了程序的虚拟地址信息,所以程序在编译形成的时候其虚拟地址就已经确定好了;进程在运行程序时就会读取程序ELF信息来初始化mm_struct
中的信息。
此外可执行程序ELF
中还保存了整个程序的入口地址Entry point address
,这样当系统执行程序时,除了将ELF
中的虚拟地址加载到mm_struct
中,还会将整个程序的入口地址赋给CPU的pc
指针,(pc
指针指向当前执行的命令)这样一个程序就可以执行下去了,所以执行程序使用的地址也是虚拟地址。
程序加载到内存中,是会有物理内存地址的,页表的作用就是将程序物理内存地址与虚拟地址之间进行映射,而CPU中只保存虚拟地址是没办法在物理内存中找到程序的内容的,所以需要借助MMU
硬件来将PC
指针指向的虚拟地址转化为物理地址给CR3
寄存器中,完成类似查表的操作,然后CPU拿到了物理内存地址就可以去物理内存中找到对应的内容,并将该内容保存到CPU的EIP
中执行,执行完了,PC
指针就根据该内容的大小保存下一个内容的虚拟指针(因为程序的虚拟指针都是按顺序递增的,增加的大小就是上一条命令的长度),然后再通过硬件转化为实际物理地址,循环往复,直到执行完所有命令,整个过程如下图所示:
3. 动态库的加载
动态库的加载与可执行程序类似,但是它并不直接将虚拟地址保存到mm_struct
中,而是在程序运行需要用到动态库时加载到内存中,并将自己的虚拟地址保存到一个结构体struct vm_area_struct
中,因为动态库加载可能多个也可能不加载,所有Linux使用了一种灵活的数据结构来保存动态库的虚拟地址,该结构体实际上是一个链表,链接在mm_struct
中的共享区中。
程序在调用动态库中的方法时会先将动态库的起始虚拟地址保存起来,然后将调用的方法转化为该方法在动态库中的虚拟地址偏移量,这样就可以根据起始虚拟地址+偏移量
的方法找到动态库中该方法的虚拟地址,然后CPU通过查表找到物理内存地址执行命令,过程如下图所示: