第一章 计算机系统漫游
1.程序的编译
对于一个hello.c程序,从源文件到目标文件的转化是由编译器驱动程序(compiler driver)完成的,翻译过程分为四个阶段完成,执行这四个阶段的程序(预处理器、编译器、汇编器和链接器)一起构成了编译系统。
图1.1 编译系统
预处理阶段。预处理器(cpp)根据以字符#开头的命令修给原始的C程序,结果得到另一个C程序,通常以.i作为文件扩展名。
编译阶段。编译器(ccl)将文本文件hello.i翻译成文本文件hello.s,它包含一个汇编语言程序。汇编语言程序中的每条语句都以一种标准的文本格式确切地描述了一条低级机器语言指令。
汇编阶段。汇编器(as)将hello.s翻译成机器语言指令,并把指令打包成为一种叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中。Hello.o文件是一个二进制文件,它的字节编码是机器语言指令而不是字符。
链接阶段。此时hello程序调用了printf函数。Printf函数存在于一个名为printf.o的单独的预编译目标文件中。链接器(ld)就负责处理把这个文件并入到hello.o程序中,结果得到hello文件,一个可执行文件。最后可执行文件加载到储存器后由系统负责执行。
2.系统的硬件组成
图1.2 一个典型系统的硬件组成
总线
贯穿整个系统的一组电子管道,携带信息字节并负责在各个部件见传递。通常总线被设计成传送定长的字节块,各种系统的字节数不相同,例如Intel Pentium系统的字节长为4字节。
I/O设备
I/O(输入/输出)设备是系统与外界的联系通道,主要包括:键盘和鼠标,显示器以及用于长期存储数据和程序的磁盘驱动器。每一个I/O设备都是通过一个控制器与适配器与I/O设备连接起来的。控制器是主板上的芯片组,而适配器则是一块插在主板插槽上的卡。
主存
主存是由一组DRAM(动态随机访问存储器)芯片组成的。在处理器执行程序时,它被用来存放程序和程序处理的数据。
处理器
中央处理单元(CPU)简称处理器,是解释存储在主存中指令的引擎。处理器的核心是程序计数器(PC)的字长大小的存储设备(或寄存器)。PC指向主存中的某条机器语言指令(内含其地址)。
高速缓存
之前但系统在执行hello程序时会有大量的拷贝工作,例如把代码和数据从磁盘磁盘拷贝到主存,从主存拷贝到寄存器堆,再从寄存器堆把文件拷贝到显示设备中。这些拷贝工作会减慢程序的实际工作。因此,为了加快程序运行速度,系统设计者采用了更小更快的存储设备,称为高速缓存存储器,它们被用来作为暂时的集结区域,存放处理器在不久的叫过来可能会需要的信息。
形成层次结构的存储设备
存储器分层结构的主要思想是一个层次上的存储器作为下一层次上的存储器的高速缓存。
图1.3 一个存储器层次结构的示例
第二章 信息的处理和表示
这章前面的知识其实以前也学过了些,但这章书介绍的很多细节都是以前没注意的。
在C#中有三种最重要的数字编码。无符号编码是基于传统的二进制表示法的,表示大于或者等于零的数字。二进制补码编码是表示有符号整数的最常见的方式。浮点数编码是表示实数的科学计数法的以二为基数的版本。计算机的表示法用有限的位数来对一个数字编码,因此,当结果太大以致不能表示时,某些运算就会溢出。例如:当计算机计算表达式200*300*400*500会得出-884901888这个违背整数运算属性的结果。
另外浮点运算与其他两种运算相比有完全不同的数学属性。虽然溢出会产生特殊的值+∞,但是一组正数的乘积总是正的。另一方面由于表示的精度有限,浮点运算是不可结合的。例如,在大多数机器上,C表达式(3.14+1e20)-1e20求得值会是0.0,而3.14+(1e20-1e20)求得的值会是3.14.
1.信息存储
大多数计算机使用8位的块来作为最小的可寻址的存储器单位,而不是访问访问存储器中单独的位。存储器的每个字节都由一个唯一的数字来标识,称为它的地址,所有可能地址的集合就称为虚拟地址空间。编译器和运行是系统的一个任务就是将这个存储器空间划分为更可管理的单元,来存放不同的程序对象,也就是程序数据、指令和控制信息,然后有各种机制可以用来分配和管理程序不同部分的存储,这种管理其实完全是在虚拟地址空间里完成的。
1.1十六进制表示法
由于十进制和二进制表示法对于描述为模式来说都不是非常方便,所以引进了另一种替代的方法,也就是十六进制数来书写位模式。表示方法:以0x或0X开头,数字“0”~“9”和字符“A”~“F”来表示16个可能的值。取值范围:0016~FF16。
编写计算级程序的一个常见任务就是手工地在位模式的十进制、二进制和十六进制表示之间转换。二进制和十六进制之间的转换是简单直接的。例如,假设有一个数字0x173A4C,可以通过展开每个十六进制数字,将它转换位二进制格式,如下所示:
这样就给出了二进制表示000101110011101001001100。
反过来如果给定一个二进制数字000101110011101001001100,则可以通过首先将它分割为每四位一组来把它转换为十六进制,当位总数不是四的倍数,最左边的一组可以少于四位,前面用零补足。如下所示:
十进制和十六进制表示之间的转换需要使用乘法或者除法来处理一般情况。将一个十进制数字转换为十六进制,可以反复地用16除x,得到一个商q和一个余数r,然后用十六进制数字表示的r作为最低位数字,并且通过q反复进行这个过程得到剩下的数字。例如,考虑十进制314156的转换:
从这里可以读出十六进制表示为0x4CB2C。
当值x是2的幂时也就是对于某个n,x=2∧n,可以很容易地将x写成十六进制形式,只要知道x的二进制表示就是1后面跟n个零。十六进制数字0代表四个二进制0.所以对于被写成i+4j形式的n来说,其中0≤i≥3,可以把x写成开头的十六进制数字为1(i=0)、2(i=1)、4(i=2)或者8(i=3),然后跟着j个十六进制的0。比如,x=2048=211,有n=11=3+4*2,从而得到十六进制表示0x800。
反过来将一个十六进制数字表示为十进制数字,可以用相应的16的幂乘以每个十六进制数字。比如,给定数字0x7AF,计算它对应的十进制值为7*162+10*16+15=1967。
第一章 计算机系统漫游例如,gcc编译器驱动程序的翻译过程可以分为4个过程: