要理解一个程序的执行过程,首先要理解什么是编译器。我们用C语言写的程序,计算机只能识别机器语言(二进制指令),计算机并不能理解。因此,必须
将C语言转换为机器语言。编译器就是用来实现这一功能的。编译器将源代码文件按照一定的对应规则映射程计算机能够识别的二进制指令。

 



关于源文件、目标文件和可执行文件
源文件:保存源代码的文件(*.c, *.cpp)
目标文件:编译器根据源文件创建的机器语言指令成为目标代码,包含目标代码的磁盘文件即目标文件。
 一般为(*.obj)文件,Linux下为(*.o)文件。


编译完源代码后生成的目标文件并不能直接执行。一般我们编写的程序都会引用基本的库函数,因此在运行程序之前还要把库函用定义好的目标代码替换,这个过程称为链接。就是把自己写的源代码的目标文件与库函数的目标文件组合起来,生成最终的可执行文件。链接由链接器完成。






一般程序的执行过程:


程序先由程序员编辑创建并保存在硬盘上,源程序在机器上表现为01组成的位序列,8个位一个字节,每个字节表示一个文本字符,它是以ascii码的形式表示的。只由ascii字符组成的文件叫做文本文件,所有其他的叫做二进制文件。

     
 为了让机器能够识别并运行程序,每条语句必须被转为低级机器语言指令,然后将指令按照可执行目标程序的格式打包,并以二进制磁盘文件的形式存放起来。以c程序为例,转换过程大致分为预处理,编译,汇编,链接四个步骤。下面进行详细解释。

     
 预处理器根据以字符#开头的命令修改原始的c程序,比如#include<stdio.h>告诉预处理器读取系统头文件stdio.h的内容,并把它直接插入程序文本中,将#define的变量替换等等,结果得到了另一个c程序,通常以.i作为文件扩展名。

     
 编译器将文本文件hello.i翻译成hello.s,它包含了一个汇编语言程序。汇编语言程序中的每条语句都以一种标准的文本格式确切的描述一条低级机器语言指令。

       汇编器将汇编程序翻译为机器语言指令,把这些指令打包成可重定位目标程序(relocateble object
program)的格式,并把结果保存在hello.o中。hello.o文件是二进制文件,因为他的字节编码是机器语言指令而不是ascii码。如果用文本编辑器打开hello.o会看到一堆乱码。

     
 链接阶段,比如hello中用到了printf函数,这是标准c库的函数,存在于一个名为printf.o的单独编译好的目标文件中,这个文件必须以某种方式合并到我们编译好的的目标文件中。链接器(ld)程序负责处理这种合并,结果得到hello文件,它是可执行目标文件,可以被加载到内存中,由系统执行。

     
 转换完成之后就是执行了。在unix系统中,shell是一个命令行解释器,输出一个提示符,等待用户输入然后执行命令。如果输入的第一个单词不是一个内置的shell命令,shell将其解释为可执行文件,比如输入./hello,它将加载并运行这个文件。hello在屏幕上输出信息,然后终止。shell输出一个提示符,等待下一个输入的命令行。具体的过程为:初始时,shell执行它的指令,等待输入。用户输入字符创“./hello”后,shell将字符逐一读入寄存器,然后存放到存储器中,敲回车键后,shell知道用户结束命令输入。然后shell执行一系列的指令来加载可执行的hello文件,将hello目标文件的代码和数据从磁盘复制到主存,数据包含输出的字符串"HELLO,WORLD\n"。一旦目标文件hello中的代码和数据被加载到主存,处理器开始执行main的机器语言指令,将字符串从主存拷贝到寄存器,并输出到屏幕上。

       由于涉及大量的主存,磁盘,寄存器通信,故产生了cache等缓冲提高速度的设备,减少通信阻塞。

       
为了减少用户的负担,操作系统对计算机硬件资源进行了抽象,产生了进程,线程,虚拟地址等概念。进程是程序的一次执行,是操作系统分配资源的单位,多个进程是可以并发执行的,并发执行实际上每个时刻执行的还是一个进程,只不过进程间切换的速度比较快,给人的感觉是并发执行。操作系统为每个进程保存执行的状态信息,称为上下文,包括pc和寄存器文件当前值,主存内容等等。切换进程时,发生上下文切换。一个进程中可以有多个线程执行单元,每个线程都运行在进程的上下文中,共享同样的代码和数据,由于网络服务器等应用对并行处理的需求越来越大,多线程模型也越来越重要。虚拟地址为每个进程提供了一个假象,即每个进程都在独占主存,每个进程看到的是一致的存储器,称为虚拟地址空间。虚拟地址空间是由大量的准确定义的区构成,linux从低地址到高地址依次为:程序代码和数据;堆;共享库;栈;内核虚拟存储器。