『技术栈』后端专业学习Linux

内功修炼:程序是如何运行起来的

2016-11-03  本文已影响5063人  Torival

对于任何一个学习过C语言的来说,“HelloWorld”程序都不会陌生。因为它应该是你打开新世界的看到的第一束光。至今我还记得第一次敲出这个程序的时候激动了好久。但是你们知道短短的几行代码,是怎么让程序运行起来的么?

// hello.c
#include <stdio.h>
int main(int argc, char *argv[]) {
    printf("Hello World!\n");
    return 0;
}

程序是如何运行起来的?很多人可能会说,不就是五个步骤:预处理(Prepressing)编译(Compilation)汇编(Assembly)链接(Linking)装载(Loading)么?是这样的。但是你知道每一步背后都做过一些什么吗?如果你能回答上以下的问题,我想这个文章就没有必要看下去了。

如果你发现对其中的一些问题,不是很了解的话,甚至没有想过这些问题的时候,而你有向了解一下,那么就可以,跟着我的步伐一步俩步,往下看啦。这个文章是为你准备的。需要声明的是,本文主要针对gcc编译器,也就是针对C和C++,不一定适用于其他语言的编译。下图为总览。

GCC编译过程

预处理

预处理的过程,其实,主要是处理那些源代码中以#开始的预编译指令。比如#include#define等,处理的规则如下:

对于第一步预编译的过程,可以通过以下方式完成:

gcc -E hello.c -o hello.i

或者

cpp hello.c > hello.i

编译

编译过程可分为6步:词法分析、语法分析、语义分析、源代码优化、代码生成、目标代码优化。对应与下图的每一步。下面我们以一个具体的表达式进行分析:

array[index] = (index + 4)*(2 + 6);
Compilation
记号 类型
array 标记符
[ 左方括号
index 标记符
] 右标记符
= 赋值
( 左圆括号
index 标记符
+ 加号
4 数字
) 右圆括号
* 乘号
( 左圆括号
2 数字
+ 加号
6 数字
) 右圆括号

注:lex工具,可实现按照用户描述的词法规则将输入的字符串分割为一个一个记号。

最后的俩个步骤十分依赖与目标机器,因为不同的机器有不同的字长,寄存器,整数数据类型和浮点数据类型等。

汇编

汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,所以根据汇编指令和机器指令的对照表一一翻译即可。汇编过程可以通过以下方式完成。

as hello.s -o hello.o

或者

gcc -c hello.s -o hello.o

链接

静态链接

把一个程序分割为多个模块,然后通过某种方式组合形成一个单一的程序,这就是链接。而模块间如何组合的问题,归根到底,就是模块如何进行通信的俩个问题:(1) 模块间的函数调用,(2) 模块间的变量访问。但无论是那一个问题,其本质是获取一个地址,函数运行的地址、或者变量存放的地址。

如果熟悉汇编的,应该会知道hello.o文件,既目标文件,是以分段的形式组织在一起的。其简单来说,把程序运行的地址划分为了一段一段的片段,有的片段是用来存放代码,叫代码段,这样,可以给这个段加个只读的权限,防止程序被修改;有的片段用来存放数据,叫数据段,数据经常修改,所以可读写;有的片段用来存放标识符的名字,比如某个变量 ,某个函数,叫符号表;等等。由于有这么多段,所以为了方便管理,所以又引入了一个段,叫段表,方便查找每个段的位置。

当文件之间相互需要链接的时候,就把相同的段合并,然后把函数,变量地址修改到正确的地址上 。这就是静态链接,如下图。


静态链接

但是这里有俩个问题:

动态链接

我们的想法很简单,就是当第一个例子在运行时,在内存中只有一个副本;第二个例子在发生时,只需要下载更新后的lib,然后链接,就好了。那么其实,这就是动态链接的基本思想了:把链接这个过程推迟到运行的时候在进行。在运行的时候动态的选择加载各种程序模块,这个优点,就是后来被人们用来制作程序的插件(Plug-in)。

这里,我们不得不介绍一个东西,叫做动态链接器。它会在程序运行的时候,把程序中所有未定义的符号(比如调了动态库的一个函数,或者访问了一个变量)绑定到动态链接库中。简单的来说就是把程序中函数的地址改正到动态库,之后动态链接器会把控制权交给程序,然后程序执行。

这种在装载时修正地址,经常被称为装载时重定位(Load Time Relocation)。而静态链接时修正,则被称为链接时重定位(Link Time Relocation)。

可能有的人,就要问了,多个程序应用一个库不会有问题么?变量冲突?是这样的。动态链接文件,把那些需要修改的部分分离了出来,与数据放在了一起,这样指令部分就可以保持不变,而数据部分可以在每个进程中拥有一个副本,这种方案就是目前被称为地址无关代码(PIC,Position-independent Code)的技术。

链接库

通过上面,我们了解到了动态链接,静态链接。一组相应目标文件的集合,我们称它为库。因而也就有了静态链接库,动态链接库。

装载

介绍装载就不得不介绍三种文件格式了:ELF,PE,COFF。现在PC平台上流行的可执行文件格式(Executable),无论是Windows下的PE(Portable Executable)文件,还是Linux下的ELF(Executable Linkable Format)文件,都是COFF(Common file format)文件格式的变种。可执行文件例如,Windows下的*.exe,Linux下的/bin/bash。其实目标文件,内部结构上来说和可执行文件的结构几乎是一样的,所以一般跟可执行文件格式一起用一种格式进行存储。

下面以ELF文件为例子,介绍。

每一个ELF文件,都会有一个ELF文件头,里面会记录很多关于这个程序相关信息,通过它确定段表,进而确定各个段。总的来说,装载做了以下三件事情:


以上就是最近几天看完《程序员的自我修养》一些感悟吧。
└(o)┘;

上一篇 下一篇

猜你喜欢

热点阅读