CSAPP-1 计算机系统漫游

简单介绍了计算机系统的组成,存储层次结构,操作系统内核及其抽象的知识。

1. 计算机系统漫游

系统的硬件组成

一个典型系统的硬件组成

总线

​ 贯穿整个系统的是一组电子管道,成为总线,它携带信息字节并负责在各个部件间触底。通常总线被设计成传送定长的字节块,也就是字(word)。自重的字节是(即字长)是一个系统参数,各个系统都不尽相同。

I/O 设备

​ I/O(输入/输出)设备是系统与外部世界的联系通道。每个I/O设备都通过一个控制器或适配器与I/O总线连接。控制器和适配器的区别主要在它们的封装方式。控制器是I/O设备本身或者系统主印制电路板(通常称作主板)上的芯片组。而适配器则是一块插在主板插槽上的卡。

主存

​ 主存是一个临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。从物理上来说,主存是由一组动态随机存取存储器(DRAM)芯片组成的。从逻辑上来说,存储器是一个线性的字节数组,每个字节都有其唯一的地址(数组索引),这些地址是从零开始的。

处理器

中央处理单元(CPU),简称处理器,是解释(或执行)存储在主存中指令的引擎。处理器的核心是一个大小为一个字的存储设备(或寄存器),称为程序计数器(PC)。在任何时刻,PC都指向主存中的某条机器指令(即含有该条指令的地址)。

​ 从系统通电开始,直到系统断电,处理器一直在不断地执行程序计数器指向的指令,再更新程序计数器,使其指向下一条指令。处理器看上去是按照一个非常简单的指令执行模型来操作的,这个模型是由指令集架构决定的。在这个模型中,指令按照严格的顺序执行,而执行一条指令包含执行一系列的步骤。处理器从程序计数器指向的内存处读取指令,解释指令中的位,执行该指令指示的简单操作,然后更新PC, 使其指向下一条指令,而这条指令并不一定和在内存中刚刚执行的指令相邻。

​ 这样的简单操作并不多,它们围绕着主存、寄存器文件(register file)算术/逻辑单元(ALU)进行。寄存器文件是一个小的存储设备,由一些单个字长的寄存器组成,每个寄存器都有唯一的名字。ALU 计算新的数据和地址值。下面是一些简单操作的例子,CPU 在指令的要求下可能会执行这些操作。

  • 加载:从主存复制一个字节或者一个字到寄存器,以覆盖寄存器原来的内容。
  • 存储:从寄存器复制一个字节或者一个字到主存的某个位置,以覆盖这个位置上原
    来的内容。
  • 操作:把两个寄存器的内容复制到ALU, ALU 对这两个字做算术运算,并将结果
    存放到一个寄存器中,以覆盖该寄存器中原来的内容。
  • 跳转:从指令本身中抽取一个字,并将这个字复制到程序计数器(PC)中,以覆盖PC 中原来的值。

​ 处理器看上去是它的指令集架构的简单实现,但是实际上现代处理器使用了非常复杂的机制来加速程序的执行。因此,我们将处理器的指令集架构和处理器的微体系结构区分开来:指令集架构描述的是每条机器代码指令的效果;而微体系结构描述的是处理器实际上是如何实现的。

高速缓存

高速缓存存储器

​ 根据机械原理,较大的存储设备要比较小的存储设备运行得慢,而快速设备的造价远高于同类的低速设备。针对这种处理器与主存之间的差异,系统设计者采用了更小更快的存储设备,称为高速缓存存储器(cache memory, 简称为cache高速缓存),作为暂时的集结区域,存放处理器近期可能会需要的信息。位于处理器芯片上的L1 高速缓存的容量可以达到数万字节,访问速度几乎和访问寄存器文件一样快。一个容量为数十万到数百万字节的更大的L2 高速缓存通过一条特殊的总线连接到处理器。进程访问L2 高速缓存的时间要比访问L1 高速缓存的时间长5 倍,但是这仍然比访问主存的时间快5~10 倍。L1和L2 高速缓存是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的。比较新的、处理能力更强大的系统甚至有三级高速缓存:L1、L2 和L3。系统可以获得一个很大的存储器,同时访问速度也很快,原因是利用了高速缓存的局部性原理,即程序具有访问局部区域里的数据和代码的趋势。通过让高速缓存里存放可能经常访问的数据,大部分的内存操作都能在快速的高速缓存中完成。

存储设备层次结构

一个存储器层次结构的示例

操作系统管理硬件

计算机系统的分层视图

操作系统提供的抽象表示

​ 操作系统有两个基本功能:

​ 1. 防止硬件被失控的应用程序滥用;

​ 2. 向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备。

​ 操作系统通过几个基本的抽象概念(进程、虚拟内存和文件)来实现这两个功能。文件是对I/O 设备的抽象表示,虚拟内存是对主存和磁盘I/O 设备的抽象表示,进程则是对处理器、主存和I/O 设备的抽象表示。

进程

进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程都好像在独占地使用硬件。而并发运行,则是说一个进程的指令和另一个进程的指令是交错执行的。在大多数系统中,需要运行的进程数是多于可以运行它们的CPU 个数的。传统系统在一个时刻只能执行一个程序,而先进的多核处理器同时能够执行多个程序。无论是在单核还是多核系统中,一个CPU 看上去都像是在并发地执行多个进程,这是通过处理器在进程间切换来实现的。操作系统实现这种交错执行的机制称为上下文切换

​ 操作系统保持跟踪进程运行所需的所有状态信息。这种状态,也就是上下文,包括许多信息,比如PC 和寄存器文件的当前值,以及主存的内容。在任何一个时刻,单处理器系统都只能执行一个进程的代码。当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行上下文切换,即保存当前进程的上下文、恢复新进程的上下文,然后将控制权传递到新进程。新进程就会从它上次停止的地方开始。

进程的上下文切换

线程

​ 尽管通常我们认为一个进程只有单一的控制流,但是在现代系统中,一个进程实际上可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。由于网络服务器中对并行处理的需求,线程成为越来越重要的编程模型,因为多线程之间比多进程之间更容易共享数据,也因为线程一般来说都比进程更高效。当有多处理器可用的时候,多线程也是一种使得程序可以运行得更快的方法。

虚拟内存

虚拟内存是一个抽象概念,它为每个进程提供了一个假象,即每个进程都在独占地使用主存。每个进程看到的内存都是一致的,称为虚拟地址空间

进程的虚拟地址空间

​ 每个进程看到的虚拟地址空间由大量准确定义的区构成,每个区都有专门的功能。

  • 程序代码和数据。对所有的进程来说,代码是从同一固定地址开始,紧接着的是和C全局变量相对应的数据位置。代码和数据区是直接按照可执行目标文件的内容初始化的。

  • 。代码和数据区后紧随着的是运行时。代码和数据区在进程一开始运行时就被指定了大小,与此不同,当调用像malloc和free这样的C 标准库函数时,堆可以在运行时动态地扩展和收缩。

  • 共享库。大约在地址空间的中间部分是一块用来存放像C标准库和数学库这样的共享库的代码和数据的区域。

  • 。位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数调用。和堆一样,用户栈在程序执行期间可以动态地扩展和收缩。特别地,每次我们调用一个函数时,栈就会增长;从一个函数返回时,栈就会收缩。

  • 内核虚拟内存。地址空间顶部的区域是为内核保留的。不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。相反,它们必须调用内核来执行这些操作。

​ 虚拟内存的运作需要硬件和操作系统软件之间精密复杂的交互,包括对处理器生成的每个地址的硬件翻译。基本思想是把一个进程虚拟内存的内容存储在磁盘上,然后用主存作为磁盘的高速缓存。

文件

​ 文件就是字节序列,仅此而已。每个I/O 设备,包括磁盘、键盘、显示器,甚至网络,都可以看成是文件。系统中的所有输人输出都是通过使用一小组称为Unix I/O 的系统函数调用读写文件来实现的。

系统之间利用网络通信

网络也是一种I/O设备

​ 现代操作系统经常通过网络和其他系统连接到一起。从一个单独的系统来看,网络可视为一个I/O 设备,如图所示。当系统从主存复制一串字节到网络适配器时,数据流经过网络到达另一台机器,而不是比如说到达本地磁盘驱动器。相似地,系统可以读取从其他机器发送来的数据,并把数据复制到自己的主存。

​ 随着Internet 这样的全球网络的出现,从一台主机复制信息到另外一台主机已经成为
计算机系统最重要的用途之一。比如,像电子邮件、即时通信、万维网、FTP和telnet 这样的应用都是基于网络复制信息的功能。

利用telnet通过网络远程运行hello

Amdahl 定律

​ 该定律的主要思想是,当我们对系统的某个部分加速时,其对系统整体性能的影响取决于该部分的重要性和加速程度。若系统执行某应用程序需要时间为$T_{old}$。假设系统某部分所需执行时间与该时间的比例为 $\alpha$,而该部分性能提升比例为 $k$。即该部分初始所需时间为 $\alpha T_{old}$ ,现在所需时间为$\alpha T_{old}/k$。因此,总的执行时间应为
$$
T_{new} = (1-\alpha)T_{old} + (\alpha T_{old})/k = T_{old} [(1-\alpha)+\alpha/k]
$$
由此,可以计算加速比$S =T_{new}/T_{old} $为
$$
S = \frac{1}{(1-\alpha)+\alpha/k}
$$
考虑 $k$ 趋向于$\infty$时

$$
S_\infty = \frac{1}{1-\alpha}
$$

并发和并行

​ 我们用的术语并发(concurrency)是一个通用的概念,指一个同时具有多个活动的系统;而术语并行(parallelism)指的是用并发来使一个系统运行得更快。并行可以在计算机系统的多个抽象层次上运用。

线程级并发

​ 构建在进程这个抽象之上,我们能够设计出同时有多个程序执行的系统,这就导致了并发。使用线程,我们甚至能够在一个进程中执行多个控制流。传统意义上,这种并发执行只是模拟出来的,是通过使一台计算机在它正在执行的进程间快速切换来实现的,就好像一个杂耍艺人保持多个球在空中飞舞一样。在以前,即使处理器必须在多个任务间切换,大多数实际的计算也都是由一个处理器来完成的。这种配置称为单处理器系统

​ 当构建一个由单操作系统内核控制的多处理器组成的系统时,我们就得到了一个多处理器系统

不同处理器配置分类

多核处理器是将多个CPU(称为“核”)集成到一个集成电路芯片上

多核处理器的组织结构

超线程,有时称为同时多线程(simultaneous multi-threading), 是一项允许一个CPU执行多个控制流的技术。它涉及CPU 某些硬件有多个备份,比如程序计数器和寄存器文件,而其他的硬件部分只有一份,比如执行浮点算术运算的单元。常规的处理器需要大约20 000 个时钟周期做不同线程间的转换,而超线程的处理器可以在单个周期的基础上决定要执行哪一个线程。这使得CPU 能够更好地利用它的处理资源。

​ 多处理器的使用可以从两方面提高系统性能。首先,它减少了在执行多个任务时模拟并发的需要。正如前面提到的,即使是只有一个用户使用的个人计算机也需要并发地执行多个活动。其次,它可以使应用程序运行得更快,当然,这必须要求程序是以多线程方式来书写的,这些线程可以并行地高效执行。因此,虽然并发原理的形成和研究已经超过50年的时间了,但是多核和超线程系统的出现才极大地激发了一种愿望,即找到书写应用程序的方法利用硬件开发线程级并行性。

指令级并发

​ 在较低的抽象层次上,现代处理器可以同时执行多条指令的属性称为指令级并行。在流水线(pipelining)中,将执行一条指令所需要的活动划分成不同的步骤,将处理器的硬件组织成一系列的阶段,每个阶段执行一个步骤。这些阶段可以并行地操作,用来处理不同指令的不同部分。

单指令、多数据并行

​ 在最低层次上,许多现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操作,这种方式称为单指令、多数据,即SIMD 并行。例如,较新几代的Intel 和AMD处理器都具有并行地对8 对单精度浮点数(C 数据类型float)做加法的指令。

​ 提供这些SIMD 指令多是为了提高处理影像、声音和视频数据应用的执行速度。虽然有些编译器会试图从C 程序中自动抽取SIMD 并行性,但是更可靠的方法是用编译器支持的特殊的向量数据类型来写程序,比如GCC 就支持向量数据类型。

虚拟机

虚拟机

虚拟机,它提供对整个计算机的抽象,包括操作系统、处理器和程序。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!