程序在计算机中是如何执行的
以C语言为例,介绍整体执行过程。
编译期
以简单的输入helloword为例,这是一个hello.c文件
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
我们调用gcc编译器读取hello.c将其翻译为可执行程序,最终得到了可执行程序 hello
gcc -o hello hello.c
编译过程如下:
- 预处理阶段:将 include <stdio.h> 代表引用的标准输入输出系统头文件的代码内容直接插入程序文本中,得到hello.i文件
- 编译阶段:将预处理后的hello.i翻译成汇编的文本文件hello.s
- 汇编阶段:将汇编文件hello.s翻译成机器语言指令hello.o
- 链接阶段:hello.o还不能正常工作,因为其引用了printf ,其存在于预先编译好的printf.o文件中,我们要将其使用连接器合并到我们的hello.o文件中,最终得到了hello可执行程序文件
执行期
系统硬件组成
要了解执行过程首先要对计算机硬件的组成有大致的了解。
典型的计算机系统有以下一些硬件组成:
- 总线:一组电子管道,负责在各个部件中传递信息,其最小信息单元是字(word),现在大多数字长度为4字节(32位)或8字节(64位)
- I/O设备:与外部系统链接的通道,键鼠磁盘显示器都是I/O设备
- 主存:系统运行时使用的临时储存设备,即系统内存,逻辑上为一个线性字节数组,可通过每个字节的唯一地址(数组索引)获取到存储的值
- 处理器:解释执行主存中的指令的引擎,即我们常说的CPU
- 寄存器:存储一个指令执行需要用到的中间数据
- PC:程序计数器,一直指向下一个需要处理的指令
- ALU:逻辑单元,对两个字的内容执行逻辑运算
运行过程
我们输入执行指令,运行程序且获得了hello word的控制台输出
./hello
- 首先shell程序执行它的指令,等待输入命令,在我们输入指令时shell将其读入寄存器,再由寄存器将其放到内存中。
- 在我们敲下回车后,shell会执行一系列指令将hello文件的代码复制到主存中
- 然后处理器开始处理hello程序,将需要输出的内容hello word放入寄存器,再从寄存器将其复制到显示设备,最终显示在屏幕上。
操作系统
实际上真的和上述执行过程一样,程序直接调用硬件资源吗?并不是,无论是shell,还是hello程序,都没有能力直接访问键盘、显示器、磁盘或主存,其所有的操作均依赖于操作系统去实现。
操作系统核心目标有两个:防止硬件被程序滥用,提供统一简单的机制来操控通常大不相同的硬件设备
操作系统通过以下一些概念来实现这两个目标:
- 文件:对I/O设备的抽象
- 虚拟内存:对I/O设备+主存的抽象
- 进程:对对I/O设备+主存+处理器的抽象
进程
我们可以看到进程包含了I/O主存和处理器,其资源已经能够处理上述我们操作的流程,操作系统给我们提供一种假象:你的程序在独占的使用系统资源。进程是操作系统对一个正在运行的程序的一种抽象,系统上可以运行多个进程,每个进程看似在独占,实际是通过进程的交错执行指令来实现的。
系统上下文: 由于有交错执行的过程,所以这里必须引入一个上下文的概念,操作系统必须保持一个进程运行时所需的状态的所有信息,以便在下次交错执行到这个进程时能恢复之前的状态,这里所需要的信息就是上下文。而交错执行的过程被称为系统上下文切换
那么我们再执行hello程序时实际上有三个程序参与了流程:shell hello 操作系统,操作系统执行的代码我们称为内核代码,程序的为用户代码。那两个用户代码A与B执行切换的流程为:
- A执行用户代码
- 操作系统执行上下文切换从A到B
- B执行用户代码
- 操作系统执行上下文切换B到A
- A执行
- Loop…
在现代系统中,一个进程一般由更小的多个线程作为执行单元执行,每个线程都运行在进程的上下文中,由于多个线程之间共享一个虚拟内存,所以切换时一般比进程更加高效,多线程是一种能让程序运行的更快的方法。
虚拟内存
虚拟内存一般由以下几个部分构成:
- 程序代码和数据:从磁盘文件中加载的程序代码和只读数据文件,紧接着是全局变量的读写数据
- 堆:在程序代码和数据之后,特点是可以收缩
- 共享库:放入标准库和数学库的地方
- 栈:用户虚拟地址的空间顶部是用户栈,用以实现函数调用,在调用一个方法时 此方法会压栈,当从一个方法中返回时是出栈,同样可以动态扩展
- 内核虚拟内存:为内核保留的空间,用户不可见,必须由内核程序(操作系统)来调用
重要概念
前面介绍了系统使如何运行程序的,之后我们预先看一下会贯穿计算机系统的几个重要概念
Amdahl定律
我们对系统一个主要部分进行了大幅度优化,但对整个系统整体运行速度提升却是有限的。
加速公式为:S=1/((1-a)+a/k)
S:系统加速比
a:这个主要部分执行所占整体时间执行的比例
k:该部分性能提升的倍率
并发与并行
- 并发:指一个同时又多个活动的系统
- 并行:用并发使一个系统运行的更快
线程级并发
首先并发系统要基于多核技术,单核心运行的程序,即使使用多线程并发的方式书写的,对程序性能提升也是极为有限的,对于计算密集型的程序反而会降低执行速度。(由于上下文切换需要消耗时间,且计算密集型无法让核心执行得到释放,对于io密集型,若处理io的时间大于线程切换两次的时间理论上可以获得一定的优化)
多核技术让多个核心同时集成在一个芯片上,程序的多个线程可以再不同核心上同同时执行,提高了执行效率(若程序是以多线程的方式书写的话)。
且超线程技术能够进一步降低核心空闲时间:超线程技术让一个核心同时持有多个线程,当一个线程需要等待资源加载到寄存器才能执行时 核心就可以去执行其他的线程,提高了核心利用率。
指令级并行
一般来说 老式的核心需要20个或更多的时钟周期1才能执行一条指令,但当代处理器使用了流水线将一条指令所需要的活动的每个阶段划分成不同步骤,将这些阶段并行的操作,使得处理速度能够接近于一个时钟周期一条指令的执行速度。
当处理器可以达到比一个时钟周期执行一条指令更快的执行速率时,我们称之为超标量处理器
单指令多数据并行
现代处理器拥有一些特殊的硬件允许一条指令产生多个可并行执行的操作,这种方式称之为单指令多数据,即SIMD并行。(例如提供单精度浮点加法运算的指令等)
计算机中最基本的、最小的时间单位。一般来说计算机在一个时钟周期内仅能执行一个最基本的动作 ↩︎