Oh!Coder

Coding Life

读《CS:APP(第二版)》

| Comments

最近花了将近两个礼拜的时间重新读了一遍《CS:APP(第二版)》(现已有第三版上市)。坦白说,这本书是让我真正开始正确理解计算机的一本入门书。如果能够早些年读,或许会让我少走不少的弯路,而且也能够让我更早的摸索到进阶学习的门路。可惜有些事情没有如果,不过现在能够读到她,也实属亡羊补牢,希望为时不晚。

去年年初,更确切的说是2013年年底的时候,第一次读到了这本书。时隔一年多,书中很多基础知识也已模糊,本着温故而知新,最近也是重新读了一遍。一方面为了梳理自己对于书中的知识结构,另一方面也为自己以后再次阅读提供一个参考。在此简单概述一下这本书。当然,如果能顺便为路过此地的朋友提供一些阅读借鉴,也不失为一件开心的事情。

本书原本是卡内基梅隆大学计算机系的教材。书的标题也已经表明,本书写作的出发点是从程序员的视角剖析计算机系统,换句话说,是从软件角度出发,来对计算机的各个组成部分进行讲解。

首先从书的目录可以清晰的看出,本书分为三大部分。第一部分,程序结构和执行。第二部分,在系统上运行程序。第三部分,程序间的交互和通信。本书的第一章单独出来,作为整本书的一个高度概述,不属于上述三个部分的任何一个。下面按照阅读顺序,分章节简单做一下概述。

第一章 计算机系统漫游

这一章作为全书的高度概述,为整本书的写作提供了一个提纲。本章以信息表示为起点,通过介绍一个C语言的经典程序hello, world!为例,从程序字符的编写开始,到最终把输出结果打印显示出来,带领读者漫游了一个普通的程序在计算机系统中,是如何经历预编译,编译,汇编,链接等各个阶段,最终显示出计算结果的。

在程序执行的各个阶段,本章按照小节进行了分别的介绍。1.1小节介绍信息的表示,很明显对应的就是本书的第二章信息的表示和处理。接下来1.2和1.3小节程序的不同格式和编译系统,也对应了接下来的第三章程序的机器级表示。1.4和1.5小节讲到了处理器和高速缓存,也对应了第四章的处理器体系结构和第五章的优化程序性能。再接下来的1.6小节存储设备的层次结构,对应了第六章的存储器层次结构。1.7小节开始讲到操作系统相关,此部分对应了本书的第二大部分在系统上运行程序。接下来的1.8小节系统之间利用网络通信对应了本书的第三大部分程序间的交互和通信

从上面的结构划分来看,第一章明显就是整本书的一个全局概述。接下来的各个章节基本上就是对第一章所讲解的各知识点进行了展开。搞清楚了整本书的写作大纲,在接下来阅读的过程中,遇到了新的知识点,有助于把握作者的讲解思路,理解起来也就不会容易偏离主题太多。

第一部分 程序结构和执行

第二章 信息的表示和处理

这一章是学习计算机信息表示的基础。知识点可以分为三大部分。

第一部分讲信息存储,也就是信息在计算机系统内部的表示方式。讲解了常见的进制转换,包括二进制、八进制、十进制以及十六进制。这一小节的小知识点还是蛮多的,比如数据的大小,字符串大小以及寻址和字节序,字节序主要就是大端法和小端法,最后还介绍了布尔代数和C语言中的一些针对数据位的运算法则。

第二部分讲整数的表示和运算。这部分书中又分为两个小节来讲解,第一个小节讲整数的表示,第二个小节讲整数的运算。在整数的表示部分,讲解了如何使用补码来对有/无符号的整数进行表示。在接下来的整数运算部分,以补码为基础,讲解了在补码层级,有/无符号整数之间的运算方式。通过这部分学习,除了了解书中讲到的知识点以外,还可以知道编程语言强制类型转换的背后,从补码层级,计算机到底做了什么。

第三部分讲了浮点数。这部分讲解的篇幅明显要比整数少很多。主要讲了二进制小数,IEEE浮点表示,浮点数的舍入以及之间的运算。这一个小节的每一个子小节,基本上各自讲解了一个独立的知识点。

第三章 程序的机器级表示

这一章主要在讲汇编语言。由于过往的历史中,Intel的芯片太过流行,所以开篇大致介绍了历代过往的Intel芯片产品。而本章讲解的汇编语言,也以IA32(Intel Architecture 32-bit)为标准进行讲解。有意思的是,本书通篇的汇编代码格式却不是Intel的汇编代码格式,而是ATT格式,对于这两种格式,书中也以旁注的形式做了简单说明。

本章开篇的3.2和3.3两个小节,主要是为正式讲解汇编语言做基础知识上的铺垫。这两个小节分别对程序不同级别的代码做了层级介绍,接下来对IA32的数据格式的表示做了说明。

从3.4小节开始,正式对汇编语言进行讲解。对于本章的学习,可以借鉴C语言的学习经验进行学习。从介绍32位CPU的8个常用寄存器开始,依次讲解了寄存器的寻址方式,和数据传送,这部分的学习,基本上可以类比于C语言的数据类型声明和赋值操作。其实我猜作者的教学思路大致也是如此展开的。

以此类推,对于接下来的算术和逻辑操作控制过程数组分配和访问,以及异质的数据结构,可以分别类比于C语言的基本运算,控制语句,函数和数组,以及结构体和联合。而所有上述的汇编运算基础,都是基于32位CPU的8个寄存器,以及对于每个程序而言,有一个独立的栈帧结构。对于这个栈帧结构的讲解,只有到本书的第九章虚拟存储器才会大致完整的讲解完全,然而此时需要了解的部分,其实也只有栈帧结构的%ebp%esp两个寄存器的使用而已。所以此时不必太纠结这个栈帧结构,除此之外,基本上不会影响对汇编语言的理解。对于栈帧结构的理解,可以认为是存储器对于CPU提供的一个统一抽象存储结构。对于栈帧结构的详细讲解,在后面第六章存储器层次结构,第七章链接以及第九章虚拟存储器都会对它有更深入的介绍。

除了上面提到的那个栈帧结构以外,在阅读过程中还有一点需要注意的地方,那就是IA32各个寄存器的默认使用规则,这些小知识点的介绍,穿插在各个小节的讲解中,阅读的时候需要留心。如果对此不了解,有可能在阅读个别汇编代码的时候摸不到头脑。

本章的倒数第三小节3.13,对64位的CPU做了简要的讲解。主要讲解的立足点,也是与前面讲解的32位CPU做了对比。因为64位的CPU有了更多的寄存器,所以相对于32位的CPU来说,还是有不少差别的,比如64位架构的CPU,就不再需要32位CPU中的%ebp寄存器了。当然,这部分的讲解很简短,只是介绍性的,不知道在最新的第三版中,是不是会有更多的调整,毕竟现在64位的CPU已经非常的普及。

第四章 处理器体系结构

这一章通过从零构造一个Y86处理器,带领读者全面了解CPU的基本构造。本章Y86处理器的名称来源,是受到了X86名字的启发。总体感觉呢,这一章的讲解思路还是蛮清晰的吧。

首先,4.1小节确定了Y86处理器要实现的指令集,参考IA32,挑选出其中功能最基本的指令,构成Y86的基础指令集。确定完Y86指令集之后,紧接着介绍的Y86指令集的编码,也就是Y86指令集对应的二进制表示是如何的。

接下来4.2小节,从CPU的逻辑层面的设计入手,顺便介绍了硬件控制语言HCL,目的是让读者了解,逻辑层面的设计是如何转换成最终的硬件电路的。这部分主要就是介绍CPU的硬件逻辑构造,比如CPU上的各种存储器和各种时钟。

紧接着,到了4.3小节。综合上面两个小节的基础姿势,实现一个最简单的Y86,指令的执行逻辑为顺序实现。每条指令的执行都包括了五个阶段,分别为取指译码执行访存写回。通过顺序执行这五个阶段,大一统的实现所有Y86指令的功能。这部分的讲解,除了讲解SEQ的硬件结构以外,还讲解了SEQ的时序问题,比如,在单个指令执行的不同阶段,单位时钟的上升沿和下降沿期间,Y86是如何更新寄存器和存储器的。

当然,本章设计的Y86处理器,目的是要最终实现指令的流水线执行。为了达到最终的效果,在4.4小节讲解了流水线的通用原理。这一小节包括两个主要的知识点,一个是如何计算流水线的效率,另一个就是流水线的具体实现。

最后,本章的4.5小节,全面对之前实现的顺序Y86处理器进行改造,依照流水线的通用原理,将Y86彻底改造成一个按照流水线执行指令的处理器。在对Y86进行改造的过程中,通过对寄存器的增减斟酌,可以感受到对时间和空间进行权衡,并最后找到最优解的设计精妙。具体的知识点这里就不展开介绍了。

第五章 优化程序性能

这一章介绍了程序的优化。总体感觉这章的内容还是比较零散的,毕竟程序的优化涉及到了方方面面。

首先,5.1小节主要介绍了GCC编译器的优化能力和它的局限性。5.2小节对程序性能的表示做了介绍。

接着,5.3小节举出了一个具有优化空间的例子。接下来的5.4~5.6小节分别通过消除循环,减少过程调用以及消除不必要的存储器引用,分别对5.3小节列举的那个例子进行了性能优化。5.7小节对现代处理器在性能优化方面做了介绍,我自己认为是在为接下来的5.8和5.9小节做姿势铺垫。

在接下来的5.8和5.9小节,针对程序的性能优化,分别介绍了循环展开和提高并行性。当然,优化的对象依然是5.3小节提出的例子。5.10小节针对前面的优化做了一次小结。

5.11小节针对优化的限制,提出了两个方面的因素,一个是寄存器溢出,另一个是分支预测和预测错误处罚。但与此同时,也介绍了在实际的代码书写过程中,如何面对这两个方面的因素。

上面小节的程序优化大部分都是针对CPU的。5.12小节介绍了存储器在性能优化方面的一些姿势。5.13小节总结了在实际应用中,性能提高有哪些技术。

最后,5.14小节针对大规模的程序,介绍了如何借助优化工具,对程序进行剖析,找到可优化的热点。

第六章 存储器层次结构

这一章主要讲了计算机中的存储器系统。总的来说,这一章的知识结构划分还是蛮清晰的。

首先,6.1小节全面介绍了存储技术。主要包括了三大部分,第一部分是随机访问存储器,第二部分是磁盘存储器,第三部分是固态硬盘。对于这些存储器的技术细节就不展开了。

紧接着,6.2小节介绍了局部性原理,这部分对程序性能的优化和书写比较有帮助,因为利用局部性原理可以提高程序的执行效率。

6.3小节介绍了存储器层次结构。这个层次结构包括了从CPU内部的L0寄存器开始,一直到分布式计算机的文件系统,进行了整体介绍。

在接下来的6.4小节,对高速缓存存储器进行了详细介绍。这里的高速缓存存储器主要就是6.1小节中,属于随机访问存储器的SDRAM,位于CPU和主存储器之间。在这一小节中,介绍了四种高速缓存存储器,分别是通用高速缓存存储器结构直接映射高速缓存组相联高速缓存全相联高速缓存,在本小节最后还介绍了一个真实的高速缓存存储器结构。

在6.5小节展示了一些对于高速缓存友好的代码。

最后在6.6小节,综合前面介绍的姿势,利用一个称为存储器山的模型,分析了高速缓存对一段程序的性能影响。

第二部分 在系统上运行程序

第七章 链接

这一章主要讲了从程序源码到机器代码的链接环节。

7.1小节介绍了编译器驱动程序,主要还是以GCC驱动程序为例。编译驱动程序代表用户在需要时调用语言预处理器、编译器、汇编器和链接器。7.2小节讲了静态链接,其中提到,为了构造可执行文件,链接器必须完成两个主要任务,一个是符号解析,另一个是重定位。7.3小节介绍了目标文件的三种形式,可重定位目标文件、可执行目标文件以及共享目标文件。

紧接着,7.4小节介绍了典型的ELF可重定位目标文件,其中较详细的说明了不同节所代表的意义,其中包括接下来在7.5和7.6小节所涉及的符号表。7.5小节介绍了符号和符号表。7.6小节讲解了符号解析。7.5和7.6两个小节,介绍了链接器两个必完成任务的其中一个,符号解析。7.7小节讲解了链接器的另一个任务,重定位

7.8小节介绍了典型的ELF可执行目标文件。接着7.9小节介绍了如何加载可执行目标文件。

7.10小节讲了动态链接共享库。7.11小节讲了如何从应用程序中加载和链接共享库。

7.12小节介绍了与位置无关的代码(PIC)。

最后,7.13小节简单列举了一些处理目标文件的工具。

第八章 异常控制流

这一章讲解异常,通过这章的讲解,将对Unix操作系统中的进程和信号量有所了解。同时也会了解到,系统调用其实只是异常的一种类型而已。

8.1小节整体介绍异常的概念,其中包括异常的类别,特别对Linux/IA32系统中的异常做了介绍。

8.2小节开始介绍进程。8.3小节插入讲解了系统调用错误处理。紧接着8.4小节讲解了进程的控制。

8.5小节开始介绍信号,包括什么是信号,信号的发送和接收等相关操作。

8.6小节介绍非本地跳转,它是C语言提供的一种用户级异常控制流形式。

最后,8.7小节介绍了几个常用的操作系统进程的工具。

第九章 虚拟存储器

按照本章前言里介绍,这章主要分为两部分,前一部分描述虚拟存储器是如何工作的。后一部分描述应用程序如何使用和管理虚拟存储器。

9.1小节分别介绍了物理和虚拟寻址。9.2小节讲解了地址空间。

9.3小节讲解了虚拟存储器作为缓存的工具。9.4小节讲解了虚拟存储器作为存储器管理的工具。9.5小节讲解了虚拟存储器作为存储器保护的工具。这三个小节主要是讲解虚拟存储器作为不同工具的使用。

9.6小节讲了地址翻译的基础知识。顾名思义,这一小节主要就是介绍虚拟地址和物理地址的翻译。

9.7小节列举了Intel Core i7/Linux存储器系统的案例。

9.8小节讲了存储器映射。这小节讲了通过虚拟存储器区域与磁盘上的对象进行关联。

9.9小节主要是讲动态存储器分配。分配器因为释放已分配块方式的不同分为两种,一种是显式分配器,另一种是隐式分配器。其中隐式分配器又称为垃圾收集器。这一小节对动态分配做了详细的讲解,本节还专门有一小节,实现了一个简单的分配器。如预料之中,紧接着9.10小节讲解了垃圾收集的话题。

9.11小节对C语言中常见的与存储器有关的错误做了介绍。

第三部分 程序间的交互和通信

第十章 系统级 I/O

本章主要就是通过对Unix系统I/O的讲解,来介绍系统I/O的原理。

10.1小节简单介绍Unix I/O。

10.2和10.3小节分别通过调用Unix系统的I/O相关的API,对打开和关闭文件以及对文件的读和写分别做了介绍。

10.4小节通过封装一个RIO包,介绍了如何增强系统I/O的健壮性。

10.5小节介绍了读取文件的元数据。

10.6小节介绍共享文件。

10.7小节介绍I/O重定向。

10.8小节介绍了标准I/O。

10.9小节综合说明了我们应该使用哪些I/O函数。

第十一章 网络编程

这一章简单介绍了Unix的网络编程。

11.1小节介绍了客户端-服务器编程模型。

11.2小节对网络的基本概念做了介绍。基于网络的基本概念,11.3小节对因特网的基本概念做了介绍。

11.4小节对Unix的socket套接字接口做了介绍。并用基本的socket函数,编写了一个client和server,使它们之间可以读和回送文本行。

11.5小节对Web服务器的基本概念进行了讲解。紧接着,在11.6小节,实现了一个名为TINY的Web服务器。

第十二章 并发编程

这一章介绍了并发编程。现代操作系统,主要提供了三种基本的构造并发程序的方法,进程,I/O多路复用以及线程。本章主要围绕这三种构造方法进行讲解。

12.1小节讲解了基于进程的并发编程。

12.2小节讲解了基于I/O多路复用的并发编程。

12.3小节讲解了基于线程的并发编程。

12.4~12.6小节基本上都是在围绕多线程的并发进行讲解。12.4小节讲解了多线程程序中的共享变量。12.5小节讲解了用信号量进行线程同步。12.6小节讲解了使用线程提高并行性。

最后一12.7小节,讨论了其他并发问题。其中包括线程安全,竞争以及死锁等等。

至此!

打完收工!~

Comments