程序的编译过程
为了说明程序的编译过程,我们用经典的hello world程序作为例子
#include <stdio.h>
int main(int argc, char const *argv[])
{
printf("hello world!!!\n");
return 0;
}
在linux系统中,我们用GCC编译器将源程序文件helloworld.c编译成可执行目标文件helloworld。
zengwh@zengwh:~/test_code$ gcc helloworld.c -o hello
zengwh@zengwh:~/test_code$ ./hello
hello world!!!
这个过程经过四个阶段,分别是预处理阶段,编译阶段,汇编阶段和链接阶段。执行这个四个阶段的程序分别是预处理器,编译器,汇编器和链接器,一起构成了编译系统。
- 预处理阶段:预处理器(cpp)根据‘#’包含的头文件,将头文件的内容加进源程序中。得到新的程序文件文本,以“.i”为文件扩展名。比如说helloworld程序包含stdio.h头文件,这个阶段将这个头文件的内容插进源程序中
- 编译阶段:编译器(ccl)将文本文件hello.i翻译成汇编程序hello.S。也就是将高级语言翻译成汇编代码,低级的机器语言指令。
- 汇编阶段:汇编器(as)将汇编程序hello.S翻译成机器指令,将这些指令打包成可重定位目标程序的格式,并把结果保存在hello.o文件中,hello.o是个二进制文件。
- 链接阶段:链接器(ld)将会把一个工程中所有的.o文件链接合并成一个可执行目标文件,可以被加载在内存中,由系统运行。
系统硬件组成
高速缓存(Caches)
高速缓存用来存放处理器近期可能需要的信息,用来加快程序在CPU的运行,下图是一个典型系统中的高速缓存存储器。
位于处理器芯片上的L1高速缓存访问速度跟访问寄存器的速度几乎一样快,L2高速缓存通过一条特殊的总线连接到CPU,访问速度比L1慢5倍左右,但比访问主存要快5-10倍。更新的系统还会有L3高速缓存,他们都是用一种SRAM的硬件技术实现的。这样,系统就可以获得很大的一块存储器,而且访问速度也很快。
程序具有访问局部区域里的数据和代码的趋势。通过将可能经常访问的数据保存在高速缓存中的方法,大部分的存储器操作都将在快速的高速缓存中完成,程序性能大大提升。
操作系统管理硬件
在处理器中,指令集结构是对实际处理器硬件的抽象,在操作系统中,文件是对I/O的抽象,虚拟存储器是对程序存储器的抽象,进程是对一个正在运行的程序的抽象,虚拟机则是对整个计算机(包括操作系统,处理器和程序)的抽象
进程
进程是操作系统中对一个正在运行的程序的一个抽象。一个系统可以同时运行多个进程程序,而每个进程都好像独占使用硬件。并发运行则是说一个进程的指令和另一个进程的指令是交错运行的。
线程
一个进程可以由多个线程组成,每个线程运行在进程的上下文中,并共享同样的代码和全局数据。
虚拟存储器
虚拟存储器是一个抽象概念,为每个进程提供一个假象,即每个进程好像在独占使用主存。每个进程看到的是一致的存储器,即是虚拟地址空间。图中的地址是从下往上增大。
- 程序代码和数据:对所有进程来说,代码都是从同一固定地址开始,接着就是全局变量对应的数据位置。代码和数据区一开始运行就已经规定大小。
- 堆:堆可以运行的时候动态地扩展和收缩,比如调用malloc或free函数的时候。
- 共享库:大约在地址中间,用来存放像C标准库或数学库这样共享的代码和数据区域。
- 栈:位于用户虚拟地址空间顶部的是栈,编译器同它来实现函数调用。和堆一样,在程序运行期间可以动态地扩展和收缩。每调用函数,栈增长,函数返回,栈收缩。
- 内核虚拟存储器:地址空间顶部区域是为内核保留的,不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。