by 崔向阳
参考: 《深入理解计算机系统》-Randal E.Bryant, David O’Hallaron
百度知道
#include<stdio.h>
int main(){
printf("Hello World/n");
}
相信很多人都是从这个程序开始接触代码的,但是这么一段简单的代码,却需要计算机每个主要组成部分一起来运作。这篇主要总结它都经过了怎么样的编译过程,又在硬件和操作系统上是怎么运行的。
hello的编译
上述程序写好以后存放在hello.c文本文件里,经过预处理阶段,编译阶段,汇编阶段,链接阶段,最后生成一个可执行的目标文件。
1.预编译阶段
首先使用gcc的预处理命令,生成的预处理后的文件名为hello.i,下图截取了程序中的一小部分:
可以看出,预处理器将预处理命令 #include 头文件stdio.h中的内容直接插入到程序文本里面去了。
2.编译阶段
将预处理后的文件hello.i编译,生成汇编程序文本文件:
汇编语言里的每条语句都确切的描述了一条低级机器语言指令,而且为不同高级语言的不同编译器提供了通用的输出语言。至于汇编代码的含义这里就不讨论了。
3.汇编阶段
这个阶段会将上述生成的汇编代码翻译成机器语言指令,从这个阶段起,生成的不再是文本文件,而是二进制文件,存储的将是机器语言指令而不是字符。用文本打开看到的是乱码,如下所示:
4.链接阶段
程序中通常都调用了各种库函数,比如我们这个简单程序里,就用了printf函数。printf函数存在于printf.o的文件里,这里我们必须将它并入我们的hello.o程序中。链接器就是做这个的,完成处理后就会得到可执行的目标文件,这里我们起名为hello。
上图列出了四个阶段的几个文件,然后就可以用 ./hello 执行程序了。下面将介绍程序在硬件中的执行过程。
hello程序在硬件上的执行
典型的硬件系统组织如下图所示:
hello可执行文件最初存储在磁盘上,当我们输入命令 ./hello 后,会逐字将内容读取到寄存器,然后再存放到存储器,从而hello文件中的数据和代码被拷贝到主存中。利用DMA(直接存储器访问),可以不经过处理器直接从磁盘拷贝到主存。
当hello文件里的代码和数据都被拷贝到存储器后,处理器开始执行hello主程序里的机器指令,将"Hello World/n"从存储器拷贝到寄存器堆,再从寄存器拷贝到显示设备,最后显示在屏幕上。
系统的硬件组成
总线
总线携带信息字节并在各部件之间传递,通常总线被设计为传输定长的字节块,称为字。
I/O设备
I/O设备是系统与外部联系的通道,每个I/O都是通过控制器或者适配器与I/O总线连接的。控制器和适配器的区别在于控制器位于主板上,而适配器是插在主板插槽上的卡。网络设备也是I/O设备。
主存
主存由一组DRAM组成,从逻辑上看,主存是由连续的字节数组组成的,每个字节有它自己唯一的地址(数组索引)。
处理器
处理器主要由程序计数器(PC)、寄存器堆和算术逻辑单元(ALU)组成。处理器的核心是PC,PC任一时刻都存储着下一条机器语言指令在主存中的地址。寄存器堆由一些字长大小的寄存器组成,每一个寄存器都有它们唯一的名字。ALU计算新的数据和地址值,通常所说的32位机、64位机就是按ALU的字长说的,这也是通常所说的cpu字长。cpu字长和总线字长不一定一致。
高速缓存(cache)
现代计算机里通常都包含cache,有L1, L2甚至L3级高速缓存。这些高速缓存采用速度较快的SRAM实现,用于临时存储常用的或处理器即将用到的信息,从而大大提升了计算机的速度。如下图所示,L1位于处理器芯片上,而L2通过一条特殊的总线连接到处理器。
hello程序与操作系统
继续看我们的hello程序,当运行hello程序时,程序并没有直接访问显示器或主存,而是通过操作系统提供的服务。
操作系统有两个用途:防止硬件被滥用;为应用程序提供简单一致的方法去控制低级硬件。操作系统通过进程、虚拟存储器和文件来实现这两个用途。
1.进程
进程是操作系统对运行程序的一种抽象。在一个系统上交错运行多个进程,实现这种交错运行的机制叫做上下文切换。操作系统保存进程运行所需的所有信息叫做这个进程的上下文,比如PC、寄存器堆的值,和主存的内容。上下文切换即保存当前进程上下文,恢复新进程的上下文。然后新进程就会从上次停止的地方开始继续运行。下图展示了从shell运行hello程序的情况:
2.虚拟存储器
虚拟存储器是一个抽象概念,好像每个进程都在独占的使用主存一样,每个进程看到的存储器是一直的,称为虚拟存储空间。下图所示为hello在linux中进程的虚拟地址空间:
图中由下到上分别为:
程序代码和数据。进程一旦运行,这部分区域就会被指定大小。
堆。malloc、free这类动态分配存储空间的方法对此区域进行操作,堆可以再运行时动态扩大和缩小。
共享库。存放c标准库等共享库的区域。
栈。函数调用时的信息需要存放到栈里,栈在程序运行时也可以动态扩大和收缩。每调用一个函数的时候,栈就会增长,从函数返回时,栈会收缩。
内核虚拟存储器。操作系统总是驻留在存储器中的部分,地址空间的四分之一是为内核预留的,应用程序不允许读写这个区域或直接调用在内核定义的函数。
3.文件
文件就是字节序列。I/O设备都可以看做文件。
小结
这里对程序运行时涉及的硬件和软件系统大部分部件只是简单概括的进行了介绍,以后会进一步描述细节。