本书概述

本书概述

本书由12章组成,旨在阐述计算机系统的核心概念。内容概述如下:

  • 第1章:计算机系统漫游。这一章通过研究“hello, world”这个简单程序的生命周期,介绍计算机系统的主要概念和主题。

  • 第2章:信息的表示和处理。我们讲述了计算机的算术运算,重点描述了会对程序员有影响的无符号数和数的补码表示的特性。我们考虑数字是如何表示的,以及由此确定对于一个给定的字长,其可能编码值的范围。我们探讨有符号和无符号数字之间类型转换的效果,还阐述算术运算的数学特性。菜鸟级程序员经常很惊奇地了解到(用补码表示的)两个正数的和或者积可能为负。另一方面,补码的算术运算满足很多整数运算的代数特性,因此,编译器可以很安全地把一个常量乘法转化为一系列的移位和加法。我们用 C 语言的位级操作来说明布尔代数的原理和应用。我们从两个方面讲述了 IEEE 标准的浮点格式:一是如何用它来表示数值,一是浮点运算的数学属性。

    对计算机的算术运算有深刻的理解是写出可靠程序的关键。比如,程序员和编译器不能用表达式 (x-y<0) 来替代 (x<y),因为前者可能会产生溢出。甚至也不能用表达式 (-y<-x) 来替代,因为在补码表示中负数和正数的范围是不对称的。算术溢出是造成程序错误和安全漏洞的一个常见根源,然而很少有书从程序员的角度来讲述计算机算术运算的特性。

  • 第3章:程序的机器级表示。我们教读者如何阅读由 C 编译器生成的 x86-64 机器代码。我们说明为不同控制结构(比如条件、循环和开关语句)生成的基本指令模式。我们还讲述过程的实现,包括栈分配、寄存器使用惯例和参数传递。我们讨论不同数据结构(如结构、联合和数组)的分配和访问方式。我们还说明实现整数和浮点数算术运算的指令。我们还以分析程序在机器级的样子作为途径,来理解常见的代码安全漏洞(例如缓冲区溢出),以及理解程序员、编译器和操作系统可以采取的减轻这些威胁的措施。学习本章的概念能够帮助读者成为更好的程序员,因为你们懂得程序在机器上是如何表示的。另外一个好处就在于读者会对指针有非常全面而具体的理解。

  • 第4章:处理器体系结构。这一章讲述基本的组合和时序逻辑元素,并展示这些元素如何在数据通路中组合到一起,来执行 x86-64 指令集的一个称为“Y86-64”的简化子集。我们从设计单时钟周期数据通路开始。这个设计概念上非常简单,但是运行速度不会太快。然后我们引入流水线的思想,将处理一条指令所需要的不同步骤实现为独立的阶段。这个设计中,在任何时刻,每个阶段都可以处理不同的指令。我们的五阶段处理器流水线更加实用。本章中处理器设计的控制逻辑是用一种称为 HCL 的简单硬件描述语言来描述的。用 HCL 写的硬件设计能够编译和链接到本书提供的模拟器中,还可以根据这些设计生成 Verilog 描述,它适合合成到实际可以运行的硬件上去。

  • 第5章:优化程序性能。在这一章里,我们介绍了许多提高代码性能的技术,主要思想就是让程序员通过使编译器能够生成更有效的机器代码来学习编写 C 代码。我们一开始介绍的是减少程序需要做的工作的变换,这些是在任何机器上写任何程序时都应该遵循的。然后讲的是增加生成的机器代码中指令级并行度的变换,因而提高了程序在现代“超标量”处理器上的性能。为了解释这些变换行之有效的原理,我们介绍了一个简单的操作模型,它描述了现代乱序处理器是如何工作的,然后给出了如何根据一个程序的图形化表示中的关键路径来测量一个程序可能的性能。你会惊讶于对 C 代码做一些简单的变换能给程序带来多大的速度提升。

  • 第6章:存储器层次结构。对应用程序员来说,存储器系统是计算机系统中最直接可见的部分之一。到目前为止,读者一直认同这样一个存储器系统概念模型,认为它是一个有一致访问时间的线性数组。实际上,存储器系统是一个由不同容量、造价和访问时间的存储设备组成的层次结构。我们讲述不同类型的随机存取存储器(RAM)和只读存储器(ROM),以及磁盘和固态硬盘1的几何形状和组织构造。我们描述这些存储设备是如何放置在层次结构中的,讲述访问局部性是如何使这种层次结构成为可能的。我们通过一个独特的观点使这些理论具体化,那就是将存储器系统视为一个“存储器山”,山脊是时间局部性,而斜坡是空间局部性。最后,我们向读者阐述如何通过改善程序的时间局部性和空间局部性来提高应用程序的性能。

  • 第7章:链接。本章讲述静态和动态链接,包括的概念有可重定位的和可执行的目标文件、符号解析、重定位、静态库、共享目标库、位置无关代码,以及库打桩。大多数讲述系统的书中都不讲链接,我们要讲述它是出于以下原因。第一,程序员遇到的最令人迷惑的问题中,有一些和链接时的小故障有关,尤其是对那些大型软件包来说。第二,链接器生成的目标文件是与一些像加载、虚拟内存和内存映射这样的概念相关的。

  • 第8章:异常控制流。在本书的这个部分,我们通过介绍异常控制流(即除正常分支和过程调用以外的控制流的变化)的一般概念,打破单一程序的模型。我们给出存在于系统所有层次的异常控制流的例子,从底层的硬件异常和中断,到并发进程的上下文切换,到由于接收 Linux 信号引起的控制流突变,到 C 语言中破坏栈原则的非本地跳转。

    在这一章,我们介绍进程的基本概念,进程是对一个正在执行的程序的一种抽象。读者会学习进程是如何工作的,以及如何在应用程序中创建和操纵进程。我们会展示应用程序员如何通过 Linux 系统调用来使用多个进程。学完本章之后,读者就能够编写带作业控制的 Linux shell 了。同时,这里也会向读者初步展示程序的并发执行会引起不确定的行为。

  • 第9章:虚拟内存。我们讲述虚拟内存系统是希望读者对它是如何工作的以及它的特性有所了解。我们想让读者了解为什么不同的并发进程各自都有一个完全相同的地址范围,能共享某些页,而又独占另外一些页。我们还讲了一些管理和操纵虚拟内存的问题。特别地,我们讨论了存储分配操作,就像标准库的 mallocfree 操作。阐述这些内容是出于下面几个目的。它加强了这样一个概念,那就是虚拟内存空间只是一个字节数组,程序可以把它划分成不同的存储单元。它可以帮助读者理解当程序包含存储泄漏和非法指针引用等内存引用错误时的后果。最后,许多应用程序员编写自己的优化了的存储分配操作来满足应用程序的需要和特性。这一章比其他任何一章都更能展现将计算机系统中的硬件和软件结合起来阐述的优点。而传统的计算机体系结构和操作系统书籍都只讲述虚拟内存的某一方面。

  • 第10章:系统级 I/O。我们讲述 Unix I/O 的基本概念,例如文件和描述符。我们描述如何共享文件,I/O 重定向是如何工作的,还有如何访问文件的元数据。我们还开发了一个健壮的带缓冲区的 I/O 包,可以正确处理一种称为 short counts 的奇特行为,也就是库函数只读取一部分的输入数据。我们阐述 C 的标准 I/O 库,以及它与 Linux I/O 的关系,重点谈到标准 I/O 的局限性,这些局限性使之不适合网络编程。总的来说,本章的主题是后面两章——网络和并发编程的基础。

  • 第11章:网络编程。对编程而言,网络是非常有趣的 I/O 设备,它将许多我们前面文中学习的概念(比如进程、信号、字节顺序、内存映射和动态内存分配)联系在一起。网络程序还为下一章的主题——并发,提供了一个很令人信服的上下文。本章只是网络编程的一个很小的部分,使读者能够编写一个简单的 Web 服务器。我们还讲述位于所有网络程序底层的客户端-服务器模型。我们展现了一个程序员对 Internet 的观点,并且教读者如何用套接字接口来编写 Internet 客户端和服务器。最后,我们介绍超文本传输协议(HTTP),并开发了一个简单的迭代式 Web 服务器。

  • 第12章:并发编程。这一章以 Internet 服务器设计为例介绍了并发编程。我们比较对照了三种编写并发程序的基本机制(进程、I/O 多路复用和线程),并且展示如何用它们来建造并发 Internet 服务器。我们探讨了用 P、V 信号量操作来实现同步、线程安全和可重入、竞争条件以及死锁等的基本原则。对大多数服务器应用来说,写并发代码都是很关键的。我们还讲述了线程级编程的使用方法,用这种方法来表达应用程序中的并行性,使得程序在多核处理器上能执行得更快。使用所有的核解决同一个计算问题需要很小心谨慎地协调并发线程,既要保证正确性,又要争取获得高性能。

脚注

  1. 直译应为固态驱动器,但固态硬盘一词已经被大家接受,所以沿用。——译者注↩︎