运行图
详解
下面针对每个子步骤做进一步的讲解:
1预处理hello.c,主要是处理程序里面的文件包含、处理宏定义、条件编译。
2 把c文件编译成为汇编文件(.s),其中进行了词法分析,语法分析,语义分析、生成中间代码、对代码进行优化等工作。
3 把汇编文件(.s)编译成可重定位文件(.o)。
4 把可重定位文件(.o)链接成为可执行文件,其中链接可分为静态链接和动态链接
静态链接:在编译阶段就会把所有用到的库打包到自己的可执行程序中,其优点是具有较好的兼容性,不依赖外部环境,但是生成的程序比较大。
动态链接:在应用程序运行时,链接器去加载外部的共享库,并完成共享库和动态编译程序之间的链接。不同的程序可以共用代码库,节省内存空间。
5 控制台输入./hello命令后,Shell会创建一个新的进程来执行该程序。fork()函数就是用于创建一个新的进程的。这里的进程可以先简单理解为程序的容器。
6 exeve()函数可以理解为向上一步新建的进程,填充一个可执行程序(hello)。
7 sys_execve()函数为linux系统调用,被exeve()函数调用,这里的系统调用可以理解为是操作系统系统开放给用户的最底层接口。
8 do_exeve()函数是sys_execve()函数的核心。
9 load_elf_binary()函数会去文件系统中读取hello程序到内存,然后判断它是否是动态链接的可执行程序,如果不是,则进一步判断是否是静态链接的文件。
10 ld-linux-xx.so是glibc库中的动态连接器。如果hello程序是动态链接程序,该动态链接器会去加载共享库,并完成共享库和程序的链接工作, 然后准备真正开始执行hell程序。
11 相反,如果hello程序是静态编译的程序,则无需再加载链接共享库,直接开始准备执行hello程序。
第10和11步分别执行之后.都会开始执行hello程序,_start是程序的真正入口,而该符号在glibc中。也就是说程序的真正入口在glibc。
12 __libc_start_main()也是glibc中的函数,用于在执行用户程序前进行一些初始化工作。
13 调用用户程序中的mian()函数,开始执行printf打印函数。
14 程序执行完了之后,调用glibc库中的_exit()函数,来结束当前进程。