CSAPP 的全称是 Computer Systems: A Programmer's Perspective ,中文翻译为 《深入理解计算机系统》 。这本书以 hello world 程序的运行贯穿始终,可以作为应用级别程序员一窥底层概貌的显微镜,也可以作为系统方向研究者的研究的方向概览。下面我们从第一章出发,进入计算机系统的世界。

# 信息 = 位 + 上下文

  1. 计算机系统 = 硬件 + 系统软件
  2. hello 从源程序开始 -> 所有信息都用位表示 -> 8 个位一组 -> 字节 —> ASCII 标准编码字节 —> 文本字符 -> 文本文件
  3. 其他 -> 二进制文件
  4. 数据都是比特位,区别在于上下文如何解释他们

C 语言的起源:作为用于 Unix 操作系统的语言被开发

# 程序与编译系统

编译过程:

  1. 预处理器(cpp) 修改原始 hello.c 程序,生成 hello.i 文件
  2. 编译器(ccl) 将其翻译为机器语言指令,生成 hello.s 文件
  3. 汇编器(as) 将其翻译为二进制目标文件 (可重定位目标程序),生成 hello.o 文件
  4. 链接器(ld) 将其与其他预编译好的二进制目标文件链接起来,生成二进制可执行文件,生成 hello 文件

什么是可重定位目标程序?

GNU 项目 & 自由软件概念 & 开源运动(环境为 GNU 环境,内核为 Linux 内核)

了解编译器工作过程的好处:

  1. 优化程序性能
  2. 理解链接时出现的错误
  3. 避免安全漏洞

# 处理器 & 内存中机器指令

此时 hello 已经被编译成可执行文件并存储在磁盘上。

# 硬件组成

  1. 总线:传输定长字节块,称为字(各系统不一样大)
  2. I/O 设备:键盘、鼠标、显示器、磁盘驱动器等。每个 I/O 设备通过一个控制器或适配器与 I/O 总线相连。其区别主要在于其封装方式。
  3. 主存:临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据,由一组动态随机存储器(DRAM)芯片构成。从逻辑上来说,存储器是一个线性的字节数组,每个字节都有唯一的地址(数组索引)。
  4. 处理器:是解释或执行存储在主存中引擎的指令。下一条要执行的指令的地址存储在 PC 中。CPU 中还包含一组寄存器。CPU 的设计根据指令集架构而不同。微体系结构描述的就是这个处理器是如何实现的。

# hello 程序运行

假设我们在 shell 中输入以下内容:

./hello

其运行的过程如下:

  1. shell 程序将输入字符串 ./hello 逐一读入寄存器(通过 I/O 桥和总线接口)
  2. 将其转储到内存中(通过总线接口和 I/O 桥)
  3. shell 程序从磁盘中加载可执行的 hello 可执行文件,将其从磁盘通过 I/O 桥复制到主存(DMA 技术,如果没有 DMA 技术,还需要通过处理器才能到达主存)
  4. 处理器执行 hello 程序中的指令
  5. hello world 字符串:主存 —> I/O 桥 -> 总线接口 —> 寄存器文件 -> 总线接口 -> I/O 桥 -> 图形适配器 —> 显示器

# 高速缓存 & 存储设备层次结构

高速缓存:较大的存储设备运行慢,造价低。而近年来处理器和主存之间的速度差距还在增大。因此,高速缓冲存储器 (cache) 的目的是尽量减少处理器和主存之间差异带来的速度差距。其采用更小更快的存储设备,作为暂时的存储区域。其使用静态随机访问存储器(SRAM)技术实现。

核心:高速缓存的局部性原理

存储设备层次结构:在处理器和较大较慢的设备之间插入一个更小更快的存储设备,上一层次设备作为第一层存储器的高速缓存。

# 操作系统资源管理 & 抽象

操作系统:应用程序和硬件之间插入的一层软件。

其几个基本功能包括:

  1. 防止硬件被失控的应用程序滥用
  2. 向应用程序提供简单一致的机制来控制复杂而又通常大不相同的低级硬件设备(利用抽象)
    1. 文件: I/O 设备抽象
    2. 虚拟内存:主存 + 磁盘 I/O 设备抽象
    3. 进程:对处理器、主存和 I/O 设备抽象

Unix、POSIX 和标准 Unix 规范:

时期 1:OS/360 + Multics
时期 2:Unix(包含层次文件系统、作为用户级进程的 shell 概念)
时期 3:Unix 4.xBSD (增加了虚拟内存和 Internet 协议) + System V Unix + Solaris
时期 4:IEEE 标准化 Unix 开发,将该标准命名为 POSIX 标准

# 进程 & 线程

# 进程

进程是操作系统对一个正在运行的程序的一种抽象,在这个系统中上可同时运行多个进程,而每个进程都好像在独自的使用硬件。并发运行时,是一个进程的指令与另一个进程的指令时间交错执行的。这是通过处理器在进程间切换实现的,该机制由操作系统实现,称之为上下文切换。上下文是指进程运行所需的所有状态信息,包括 PC、寄存器文件当前值、主存的内容。一般单处理器一次只能执行一个进程的代码,当它需要交错执行多个指令时,需要执行上下文切换,其步骤包括:

  1. 保存当前进程的上下文
  2. 恢复新进程的上下文
  3. 将控制权转移到新进程(系统调用)
  4. 新进程终止后,操作系统恢复旧进程上下文
  5. 控制权转移回旧进程(系统调用)

控制权的转移通过系统调用实现,它使得 CPU 的状态从用户态变为内核态。操作系统内核是系统管理全部进程所用代码和数据结构的集合。

在单核处理器中,一个 CPU 可以并发的执行多个进程。而在多核 CPU 中,多个处理器可以同时执行多个进程。

# 线程

一个进程可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和数据区域,但是不一样的是他们拥有各自独立的栈空间。

# 虚拟内存

虚拟内存为每个进程提供了一种假象,就是她们都单独的占有主存,每个进程看到的内存都是一致的,我们将其称之为虚拟地址空间。虚拟地址空间从低字节到高字节可划分为以下几个段:

  1. 只读的程序代码和数据
  2. 可读写的程序数据
  3. 堆(malloc)分配
  4. 共享库内存映射区域
  5. 用户栈
  6. 内核虚拟内存(用户代码不可见)

# 文件

文件可以作为 I/O 设备的抽象,I/O 设备具有各种不同的特性,但是通过将其抽象为文件,我们可以通过一组统一的接口来访问他们。

# 网络通信

网络也可以视为一个 I/O 设备,我们从文件中读写数据的方法很多都可以应用在网络设备的读写上。

# 重要主题

系统是软硬件的有机结合体!

# Amdahl 定律

当我们对系统的某个部分进行加速时,其对系统整体的性能的影响取决于该部分的重要性和加速程度。假设系统执行某应用程序所需时间为ToldT_{old},某部分执行时间与该时间的比例为α\alpha,而该部分性能提升比例为kk。即该部分初始所需时间为αTold\alpha*T_{old}, 现在为αToldk\frac{\alpha*T_{old}}{k},那么总的执行时间应该为:

Tnew=(1α)Told+αToldk T_{new} = (1-\alpha)T_{old}+\frac{\alpha*T_{old}}{k}

加速比 S 为

S=ToldTnew=1(1α)+αk S = \frac{T_{old}}{T_{new}} = \frac{1}{(1-\alpha)+\frac{\alpha}{k}}

其结论为:要想显著加速整个系统,必须提升全系统中相当大的部分的速度。此外,当 k 趋近于\infin 时,最后的加速比等于11α\frac{1}{1-\alpha}

# 并发和并行

我们的优化体现在,希望:

  1. 计算机做的更多
  2. 计算机运行更快

# 线程级并发

  1. 使用进程:有多个程序执行
  2. 使用线程:一个进程中执行多个控制流

# 指令级并行

处理器同时执行多条指令。

  1. 流水线:可以用来处理一条指令的不同部分,以此达到同时执行多条指令的目的
  2. 达到比一个周期一条指令更快的执行速率,称之为超标量

# 单指令、多数据并行

许多处理器拥有特殊硬件,允许一条指令产生多个可以并行执行的操作,这种方式称为单指令、多数据,即 SIMD并行 。例如浮点数加法指令。编译器有时会试图抓取 SIMD 并行性特征,也可以使用特殊向量数据类型编写程序。

# 重点习题

  • Amdahl 定律计算