程序执行原理揭秘:你的代码是如何“跑”起来的?
一、执行前的准备工作
我们先来看一下程序执行前需要做哪些准备工作。
我们首先需要了解程序的格式。你可以把程序比作一本书,而程序的格式就是这本书的版式,它决定了书的结构和排版。就像我们在阅读书籍时,不同的语言(如英语、中文)会有不同的阅读方式和理解方法一样,不同的操作系统(如Linux、Windows)也会有各自的方式去读取和理解程序。这就是程序格式的作用,它帮助操作系统正确地读取和理解程序。
1.1 程序格式
以Linux为例,我们需要使用ELF(Execuatable and Linkable File Format)格式。在这个格式中,依次主要包括了文件头、代码段、数据段、重定向表和符号表等信息。
- 文件头就是这本书的封面,它包含了文件的属性,对应的CPU、操作系统等信息。
- 代码段就像是书中的内容,包含了程序的代码和指令。
- 数据段就像是书中的图表,包含了程序内设置的初始化数据。
- 重定向表就像是书中的脚注,链接前整理的不确定的跳转地址。
- 符号表就像是书中的索引,包含了函数和全局变量的名称与地址对照表。
对于其它操作系统,在Windows中,我们使用的是PE(Portable Executable)格式;在Mac中,我们使用的是Mach-O格式。这些格式都包含了程序或库需要的各种信息,以便于操作系统能正确地加载和执行,但是由于适用于不同的操作系统,它们在文件结构、功能特性以及处理方式上有所差异。例如,ELF支持多种不同的地址空间布局,PE支持延迟加载和数据目录,而Mach-O则支持多架构和动态链接。
我们的程序想要在某种操作系统上运行,就必须以对应的格式组织代码才行。
1.2 Build过程
Build过程就是将程序代码按照某种格式进行组织的过程, 其中包括编译、汇编和链接。。
编译就像是把手稿生成汇编代码或中间代码,生成的是目标文件,这就像是我们的初稿。
汇编就像是把我们的初稿生成机器码,也就是二进制的0和1,但这还不是CPU可以直接执行的指令,这就像是我们的定稿。
链接就像是把我们的定稿打印成书,包括静态链接和动态链接。
- 静态链接是把程序内部不同子程序的代码段合并,以及其中的共享函数。这个过程是把所有目标文件中的符号表集中起来,根据重定位表,把所有不确定的跳转地址修正为符号表中对应的地址,然后合并目标文件中的各个段,形成可执行文件。就像是把书中的各个章节合并在一起。
- 动态链接就像是一座图书馆,它可以让多本书(多个程序)共享一些常用的资源,如语言和操作系统基础库,这样就可以节省内存空间。在Windows系统中,这种共享资源的方式使用的是dll文件,而在Linux系统中,使用的是so文件。你可以把这些共享资源看作是放在特定位置的一本书,每个功能就像书中的一个章节,都有自己的位置。当你需要用到某个功能时,就像查阅书中的某个章节一样,你会通过一个目录(程序链接表)找到它在全局目录(全局偏移表)中的位置。全局目录在你需要时会被创建,这样你就能根据目录找到你需要的功能,而不必去翻阅整本书。
二、执行中的过程
当我们的书准备好后,我们就可以开始阅读了。这个过程就像是程序的执行过程。
装载器把可执行文件加载到内存,就像是我们打开书本,准备阅读。
CPU从内存读取指令和数据,就像是我们从书中读取文字和图表。
动态链接的过程就像是我们阅读时需要参考其他书籍。
当程序跳转到要执行共享库代码指令时,就去程序链接表查找地址,程序链接表中定义了指令在全局偏移表中的地址。如果共享库未加载到内存则先加载,然后建立当前程序虚拟内存与共享库内存的映射,并登记执行指令虚拟内存地址到全局偏移表。然后跳转到当前程序为共享库映射的虚拟地址,读取指令并执行。
总的来说,程序的执行就像是我们写书和阅读书的过程。我们首先需要准备好我们的手稿,然后通过编译、汇编和链接,把我们的手稿变成一本可以阅读的书。然后,我们就可以开始阅读这本书,从中获取知识和信息。在这个过程中,我们可能还需要参考其他的书籍,这就是动态链接的过程。
希望通过本文,你能更好地理解程序的执行过程。