编译系统的工作流程
这个过程虽然是通过一条命令完成的,然而实际上编译系统的处理过程却是非常复 杂的,大致可以分为四个阶段,分别为预处理、编译、汇编以及链接。
-
预处理
预处理器器会根据以 # 开头的代码,来修改原始程序。例如 hello 程序中引入了头文 件 stdio.h,预处理器会读取该头文件中的内容,将其中的内容直接插入到源程序中, 结果就得到了另外一个 C 程序。即:hello.c 经过预处理器后得到为文本文件 hello.i。
-
编译
编译器将 hello.i 文件翻译成 hello.s 文件,这一阶段包括词法分析、语法分析、语义 分析、中间代码生成以及优化等等一系列的中间操作。
-
汇编
汇编器根据指令集将汇编程序 hello.s 翻译成机器指令,并且把这一系列的机器指令 按照固定的规则进行打包,得到可重定位目标文件 hello.o 。此时 hello.o 虽然是一 个二进制的文件,但是还不能执行,还要经历最后一个阶段:链接。
-
链接
在 hello 这个程序中,我们调用了 printf 函数,这个函数是标准 C 库中的一个函数, 存储在名为 printf.o 的文件中。链接器 (ld)负责把 hello.o 和 printf.o 按照一定规则 进行合并。正是因为链接器要对 hello.o 和 printf.o 的进行调整,所以 hello.o 才会被 称之为可重定位目标文件。最终经过链接阶段可以得到可执行目标文件 hello。
硬件架构图
CPU架构
中央处理单元(Central Processing Unit , CPU),也称处理器,包含PC ( 程序计数器:Program Count )、寄存器堆(Register file)、ALU(算数/逻辑计算单元:Arithmatic/logic Unit)三个部分.
内存
主存(Main Memory),也称为内存、运行内存,处理器在执行程序时,内存主要存放程序指令以及数据。从物理上讲,内存是由随机动态存储器芯片组成;从逻辑上讲,内存可以看成一个从零开始的大数组,每个字节都有相应地址.
总线
内存和处理器之间通过总线来进行数据传递。实际上,总线贯穿了整个计算机系统,它负责将信息从一个部件传递到另外一个部件。通常总线被设计成传送固定长度的字节块,也就是字(word),至于这个字到底是多少个字节,各个系统中是不一样的,32 位的机器,一个字长是4 个字节;而64 位的机器,一个字长是8 个字节.
输入输出设备
-
输入输出设备
除了处理器,内存以及总线,计算机系统还包含了各种输入输出设备,例如键盘、鼠标、显示器以及磁盘等等。每一个输入输出设备都通过一个控制器或者适配器与IO 总线相连.
程序执行过程
hello.c 经过编译系统得到可执行目标文件hello,此时可执行目标文件hello 已经存放在系统的磁盘上,那么,如何运行这个可执行文件呢?
-
在linux 系统上运行可执行程序:打开一个shell 程序,然后在shell 中输入相应可执行程序的文件名:linux>./hello
(shell 是一个命令解释程序,如果命令行的第一个单词不是内置的shell 命令,shell就会对这个文件进行加载并运行. 此处,shell 加载并且运行hello 程序,屏幕上显示hello,world 内容,hello 程序运行结束并退出,shell 继续等待下一个命令的输入.)
程序执行流程
- 首先我们通过键盘输入”./hello” 的字符串,shell 程序会将输入的字符逐一读入寄存器,处理器会把hello这个字符串放入内存中。
- 当我们完成输入,按下回车键时,shell 程序就知道我们已经完成了命令的输入,然后执行一系列的指令来来加载可执行文件hello。
- 这些指令将hello 中的数据和代码从磁盘复制到内存。数据就是我们要显示输出的”hello , world\n” ,这个复制的过程将利用DMA(Direct Memory Access)技术,数据可以不经过处理器,从磁盘直接到达内存。
- 当可执行文件hello中的代码和数据被加载到内存中,处理器就开始执行main函数中的代码,main 函数非常简单,只有一个打印功能。
Hello 程序执行过程
Cache至关重要
随着半导体技术的发展,处理器与内存之间的差距还在持续增大,针对处理器和内 存之间的差异,系统设计人员在寄存器文件和内存之间引入了高速缓存(cache), 比较新的,处理能力比较强的处理器,一般有三级高速缓存,分别为 L1 cache ,L2 cache 以及 L3 cache。
操作系统
操作系统作用
- 防止硬件被失控的应用程序滥用。
-
操作系统提供统一的机制来控制这些复杂的底层硬件。
image-20211214201201428.png
操作系统抽象概念
- 文件是对IO 设备的抽象
- 虚拟内存是对内存和磁盘IO的抽象
-
进程是对处理器、内存以及IO设备的抽象
image-20211214201058226.png
进程
现代操作系统中,一个进程实际上由多个线程组成,每个线程都运行在进程的上下文中,共享代码和数据。由于网络服务器对并行处理的需求,线程成为越来越重要的编程模型。
虚拟内存
操作系统为每个进程提供了一个假象,就是每个进程都在独自占用整个内存空间, 每个进程看到的内存都是一样的,我们称之为虚拟地址空间1.
下图为 Linux 的虚拟地址空间,从下往上看,地址是增大的。最下面是 0 地址。
第一个区域是用来存放程序的代码和数据的,这个区域的内容是从可执行目标文件中加载而来的,例如我们多次提到的 hello 程序。对所有的进程来讲,代码都是从固定的地址开始。至于这个读写数据区域放的是什么数据呢?例如在 C 语言中,全局变量就是存放在这个区域.
顺着地址增大的方向,继续往上看就是堆(heap),学过 C 语言的同学应该用过malloc 函数,程序中 malloc 所申请的内存空间就在这个堆中。程序的代码和数 据区在程序一开始的时候就被指定了大小,但是堆可以在运行时动态的扩展和 收缩.
接下来,就是共享库的存放区域。这个区域主要存放像 C 语言的标准库和数学库这种共享库的代码和数据,例如 hello 程序中的 printf 函数就是存放在这里.
继续往上看,这个区域称为用户栈(user stack),我们在写程序的时候都使用过函数调用,实际上函数调用的本质就是压栈。这句话的意思是:每一次当程序进行函数调用的时候,栈就会增长,函数执行完毕返回时,栈就会收缩。需要注意的是栈的增长方向是从高地址到低地址.
最后,我们看一下地址空间的最顶部的区域,这个区域是为内核保留的区域,应用程序代码不能读写这个区域的数据,也不能直接调用内核中定义的函数,也就是说,这个区域对应用程序是不可见的.
文件
所有的 IO 设备,包括键盘,磁盘,显示器,甚至网络,这些都可以看成文件, 系统中所有的输入和输出都可以通过读写文件来完成。
系统之间利用网络通信
使用本地计算机上的telnet客户端连接远程主机上的 telnet服务器
当我们在 ssh 的 客户端中输入 hello 字符串并且敲下回车之后,客户端的软件就会通过网络将 字符串发送到 ssh 服务端,ssh 服务端从网络端接收到这个字符串以后,会将这 个字符串传递给远程主机上的 shell 程序,然后 shell 负责 hello 程序的加载,运 行结果返回给 ssh 的服务端,最后 ssh 的服务端通过网络将程序的运行结果发 送给 ssh 的客户端,ssh 客户端在屏幕上显示运行结果
阿姆达尔定律 (Amdahl’s Law, 1967)
记 α ∈ [0, 1] 是某任务无法并行处理部分所占的比例. 假设该任务的工作量固定,则对任意 n 个处理器,相比于 1 个处理器,能够取得的加速比满足:S(n) < 1 .
古斯塔法森定律 (Gustafson’s Law, 1988)
记 α ∈ [0, 1] 是某任务无法并行处理部分所占的比例. 假设该任务的工作量可以随着 处理器个数缩放,从而保持处理时间固定. 则对任意 n 个处理器,相比于 1 个处理 器,能够取得的加速比 S (n) 不存在上界.
孙-倪定律 (Sun-Ni’s Law, 1990)
并发并行
如何获得更高的计算能力呢?可以通过以下三种途径:
线程级并发;
指令级并行;
单指令多数据并行
线程级并发
首先我们看一个多核处理器的组织结构,下图的处理器芯片具有四个 CPU 核 心,由于篇幅限制,另外两个用省略号代替了。每个 CPU 核心都有自己的 Ll cache 和 L2 cache ,四个CPU核心共享 L3 cache,这 4 个 CPU 核心集成在一 颗芯片上。
-
对于许多高性能的服务器芯片,单颗芯片集成的 CPU 数量高达几十个,甚至上百个。通过增加 CPU 的核心数,可以提高系统的性能。
image-20211214205443764.png
还有一个技术就是超线程(hyperthreading),也称同时多线程。如果每 个 CPU 核心可以执行两个线程,那么四个核心就可以并行的执行 8 个线程。在 CPU 内部,像程序计数器和寄存器文件这样的硬件部件有多个备份,而像浮点 运算部件这个样的硬件还是只有一份,常规单线程处理器在做线程切换时,大概需 要 20000 个时钟周期,而超线程处理器可以在单周期的基础上决定执行哪一个线程, 这样一来,CPU 可以更好地利用它的处理资源。当一个线程因为读取数据而进入等 待状态时,CPU 可以去执行另外一个线程,其中线程之间的切换只需要极少的时间代价。
指令级并行
现代处理器可以同时执行多条指令的属性称为指佘级并行,每条指令从开始到结束大概需要 20 个时钟周期或者更多,但是处理器采用了非常多的技巧可以同时处理多达 100 条指命,因此,近几年的处理器可以保持每个周期24条指令的执行速率。
单指令多数据并行
现代处理器拥有特殊的硬件部件,允许一条指令产生多个并行的操作,这种方式称为单指令多数据(Single Instruction Multiple Data)。SIMD 的指令多是为了提高处 理视频、以及声音这类数据的执行速度,比较新的 Intel 以及 AMD 的处理器都是支持 SIMD 指令加速。