1 学习本书的目的
计算机系统由硬件和系统软件组成。本书是推荐给哪些希望深入了解这些组件如何工作,以及这些组件是如何影响程序正确性和性能,以此来提高自身技能的读者。学完本书,你将知道:
- 如何避免由计算机表示数字的方式引起的奇怪的数字错误(第二章:信息的表示和处理)
- 学会一些小窍门来优化自己的C代码,以充分利用现代处理器和存储器系统的设计
- 你将了解编译器是如何实现过程调用的
- 如何利用这些知识来避免缓冲区溢出错误带来的安全漏洞
- 将学会如何识别和避免链接时那些令人讨厌的错误(第七章:链接)
- 学会如何编写自己的Unix shell、自己的动态存储分配包、自己的web服务器
- 并发带来的希望和陷阱
2. 信息就是位+上下文
hello
程序的生命周期是从一个源程序开始的,源程序实际上由值0
和1``组成的位序列,
8`位为一个字节。在现代计算机系统中,大部分都使用ASCII码来表示文本字符:
1 | //hello.c |
像上面的hello.c
程序就是以字节序列方式存储在文件的,每个字节都有一个整数值,对应于某些字符,如#
对应35;像hello.c
这样只由ASCII字符构成的文件为文本文件,其他文件则称为二进制文件
系统中的所有信息:磁盘文件、内存中的程序都由一串比特表示,区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文,比如在不同上下文,一个同样字节序可能表示一整数、浮点数、字符串等。
3. 程序被其他程序翻译成不同格式
hello
程序的生命周期是从一个源程序开始的,是因为这样才能被人读懂;然而计算机世界里,只有01
,因此每条C语句都必须被其他程序转化为低级机器语言指令,然后这些指令按照一种可执行目标程序打包,以二进制磁盘文件形式存放(可执行文件)。
在Unix中,从源程序到可执行文件由编译驱动程序完成: 1
gcc -o hello hello.c
预处理阶段:在这个阶段预处理器(cpp)根据字符
#
开头命令进行头文件展开、宏替换和除去注释等工作,修改原始的C程序。如#include<stdio.h>
命令告诉预处理器读取系统头文件stdio.h
的内容,并把它直接插入程序文本中编译阶段:编译器(ccl)将文本文件
hello.i
翻译成文本文件hello.s
。汇编阶段:汇编器将
hello.s
翻译成机器语言指令,并把这些指令打包成一种叫做可重定位目标程序,并保存在目标文件hello.o
- 链接阶段:
hello
程序调用了printf
函数,它是每个C编译器都提供的标准C库的一个函数,这个函数存在于一个名为printf.o
的单独编译好的目标文件中,而这个文件必须合并到我们的hello.o
程序中。链接就是负责这种合并。- 你可能会问为什么不在预处理的时候就以
#include
导入展开?这是因为性能优化的问题,一个文件从预处理到编译再到汇编会消耗一定的时间资源,如果有现成已经预编译好的目标文件,使用它而不是#include
将会节约这些不必要的时间
- 你可能会问为什么不在预处理的时候就以
理解链接时出现的错误。根据经验,一些令人困扰的程序往往都与链接器有关,如无法解析一个引用。 避免安全漏洞,缓冲区溢出错误常发生在大多数网络和Internet服务器上。
4. 系统的硬件组成
为例理解程序的运行过程,我们必须知道一些典型系统的硬件知识:
总线:贯穿整个系统的一组电子管道,称为总线,它携带信息并负责在各个部件间传递
I/O设备:I/O设备时系统与外部世界的联系通道,主要有磁盘、鼠标、键盘和显示器
主存:一个零时存储设备,在处理执行程序时,用来存放程序和程序处理的数据
- 处理器:CPU,是执行存储在主存指令的引擎。处理器核心是一个大小为一个子的存储设备即寄存器,称为程序计数器
PC
,在任何时候,PC都指向主存中某条机器语言指令地址。处理器从程序计数器指向的内存读取指令,解释指令的位,执行该指令指示的简单操作,然后更新PC,使其指向下一条指令。- CPU的操作围绕主存、寄存器文件和算术/逻辑单元(ALU)进行
- 加载:从主存复制一个字节或者一个字到寄存器,以覆盖寄存器原来的内容
- 存储:从寄存器复制一个字节或者一个字到主存的某个位置,以覆盖原来的内容
- 操作:从两个寄存器的内容复制到ALU,ALU对这两个字做算术处理,并将结果存放到一个寄存器中
- 跳转:从指令本身中抽取一个字,并将这个字复制到程序计数器PC中,以覆盖PC原来的值
- CPU的操作围绕主存、寄存器文件和算术/逻辑单元(ALU)进行
5. 高速缓存
上面的系统硬件组成中我们明白,系统会花费大量的时间把信息从一个地方搬到另一个地方,就比如hello
可执行程序:
- 首先我们要在shell兼容
./hello
,此时通过USB控制器经过总线到达CPU、从CPU再到主存 - 此时主存并没有
hello
文件的映射,就会去磁盘找并映射加载 加载到主存后,处理器开始执行hello程序的机器语言程序,这些指令将
hello, world\n
字符串的字节从主存复制到寄存器文件,再从寄存器文件复制到显示设备 较大的存储设备比较小的存储设备运行得慢,而快速设备得造价远高于同类的低速设备。因此高速缓冲处理器是一个比主存更小更快的存储设备,它存放近期处理器可能需要的信息。- 位于处理器的L1高速缓存的容量达到数万字节,访问速度和访问寄存器一样快
L2高速缓存容量达数十万到百万字节,其通过一条特殊总线连接带处理器,虽然比L1慢5倍,但比主存快5~10倍 因此合理利用高速缓存存储器和存在能够提高程序的性能。(第六章)