最近看了矢泽久雄[日]的另一本书《程序是怎么跑起来的》,同样把大学学到的知识又复习了一遍,主要包括计算机组成原理、操作系统、数字逻辑、数据结构、编程语言等知识。下面是我记录的一些书中的重点:
CPU 是英文 Central Processing Unit(中央处理器)的缩写,相当于计算机的大脑,它的内部由数百万至数亿个晶体管构成。
CPU 和内存是由许多晶体管组成的电子部件,通常称为 IC(Integrated Circuit,集成电路)。从功能方面来看,如图 1-2 所示,CPU 的内部由寄存器、控制器、运算器和时钟四个部分构成,各部分之间由电流信号相互连通。寄存器 可用来暂存指令、数据等处理对象,可以将其看作是内存的一种。
一个 CPU 内部会有 20~100 个寄存器。控制器 负责把内存上的指令、数据等读入寄存器,并根据指令的执行结果来控制整个计算机。运算器 负责运算从内存读入寄存器的数据。时钟 负责发出 CPU 开始计时的时钟信号。不过,也有些计算机的时钟位于 CPU 的外部。
通常所说的内存指的是计算机的主存储器(main memory),简称主存。主存通过控制芯片等与 CPU 相连,主要负责存储指令和数据。主存由可读写的元素构成,每个字节(1 字节 = 8 位 )都带有一个地址编号。CPU 可以通过该地址读取主存中的指令和数据,当然也可以写入数据。但有一点需要注意,主存中存储的指令和数据会随着计算机的关机而自动清除。
通常我们将汇编语言编写的程序转化成机器语言的过程称为汇编 ;反之,机器语言程序转化成汇编语言程序的过程则称为反汇编 。
机器语言是指 CPU 能直接解释和执行的语言。
编译是指将使用高级编程语言编写的程序转换为机器语言的过程,其中,用于转换的程序被称为编译器(compiler)。
寄存器 | 功能 |
---|---|
累加寄存器(accumulator register) | 存储执行运算的数据和运算后的数据 |
标志寄存器(flag register) | 存储运算处理后的CPU的状态 |
程序计数器(program counter) | 存储下一条指令所在内存的地址 |
基址寄存器(base register) | 存储数据内存的起始地址 |
变址寄存器(index register) | 存储基址寄存器的相对地址 |
通用寄存器(general purpose register) | 存储任意数据 |
指令寄存器(instruction register) | 存储指令。CPU内部使用,程序员无法通过程序对该寄存器进行读写操作 |
栈寄存器(stack register) | 存储栈区域的起始地址 |
储执行运算的数据和运算后的数据
CPU 是具有各种功能的寄存器的集合体。其中,程序计数器、累加寄存器、标志寄存器、指令寄存器和栈寄存器都只有一个,其他的寄存器一般有多个。程序计数器和标志寄存器比较特殊。另外,存储指令的指令寄存器等寄存器,由于不需要程序员做多关注。
操作系统(operating system)是指管理和控制计算机硬件与软件资源的计算机程序。
程序的流程分为顺序执行、条件分支和循环三种。顺序执行 是指按照地址内容的顺序执行指令。条件分支 是指根据条件执行任意地址的指令。循环 是指重复执行同一地址的指令。
变址寄存器的值就相当于高级编程语言程序中数组的索引功能。
CPU 则会把基址寄存器+变址寄存器的值解释为实际查看的内存地址。变址寄存器的值就相当于高级编程语言程序中数组的索引功能。
8 位 = 1 字节
计算机处理信息的最小单位——位 ,就相当于二进制中的一位。位的英文 bit 是二进制数位(binary digit)的缩写。
字节是最基本的信息计量单位。位是最小单位,字节是基本单位。内存和磁盘都使用字节单位来存储和读写数据,使用位单位则无法读写数据。因此,字节是信息的基本单位。
用字节单位处理数据时,如果数字小于存储数据的字节数(= 二进制数的位数),那么高位上就用 0 填补。例如,100111 这个 6 位二进制数,用 8 位(= 1 字节)表示时为 00100111,用 16 位(= 2 字节)表示时为 0000000000100111。
移位运算 指的是将二进制数值的各数位进行左右移位(shift = 移位)的运算。移位有左移(向高位方向)和右移(向低位方向)两种。
二进制数左移后就会变成原来的 2 倍、4 倍、8 倍……反之,二进制数右移后则会变成原来的 1/2、1/4、1/8……
进制数中表示负数值时,一般会把最高位作为符号来使用,因此我们把这个最高位称为符号位。符号位是 0 时表示正数 ,符号位 是 1 时表示负数。
补码,我们需要将二进制数的各数位的数值全部取反6 ,然后再将结果加 1。
负数用补码表示
移位后需要在最高位补 0。类似于霓虹灯往右滚动的效果。这就称为逻辑右移。
将二进制数作为带符号的数值进行运算时,移位后要在最高位填充移位前符号位的值(0 或 1)。这就称为算术右移。
符号扩充 就是指在保持值不变的前提下将其转换成 16 位和 32 位的二进制数。将 01111111 这个正的 8 位二进制数转换成 16 位二进制数时,很容易就能得出 0000000001111111 这个正确结果,但是像 11111111 这样用补数来表示的数值,该如何处理比较好呢?实际上处理方法非常简单,将其表示成 1111111111111111 就可以了。也就是说,不管是正数还是用补数表示的负数,都只需用符号位的值(0 或者 1)填充高位即可。这就是符号扩充的方法。
算术运算 是指加减乘除四则运算。逻辑运算 是指对二进制数各数字位的 0 和 1 分别进行处理的运算,包括逻辑非(NOT 运算)、逻辑与(AND 运算)、逻辑或(OR 运算)和逻辑异或(XOR 运算9 )四种。
逻辑非 指的是 0 变成 1、1 变成 0 的取反操作。逻辑与 指的是“两个都是 1”时,运算结果为 1,其他情况下运算结果都为 0 的运算。逻辑或 指的是“至少有一方是 1”时,运算结果为 1,其他情况下运算结果都是 0 的运算。逻辑异或 指的是排斥相同数值的运算。“两个数值不同”,也就是说,当“其中一方是 1,另一方是 0”时运算结果是 1,其他情况下结果都是 0。
有一些十进制数的小数无法转换成二进制数”。例如,十进制数 0.1,就无法用二进制数正确表示,小数点后面即使有几百位也无法表示。
很多编程语言中都提供了两种表示小数的数据类型,分别是双精度浮点数和单精度浮点数。双精度浮点数类型 用 64 位、单精度浮点数类型 用 32 位来表示全体小数。在 C 语言中,双精度浮点数类型和单精度浮点数类型分别用 double 和 float 来表示。
浮点数 是指用符号、尾数、基数和指数这四部分来表示的小数。
符号部分 是指使用一个数据位来表示数值的符号。该数据位是 1 时表示负,为 0 时则表示“正或者 0”
在二进制数中,我们使用的是“将小数点前面的值固定为 1 的正则表达式 ”。
指针 也是一种变量,它所表示的不是数据的值,而是存储着数据的内存的地址。通过使用指针,就可以对任意指定地址的数据进行读写。
数组 是指多个同样数据类型的数据在内存中连续排列的形式。作为数组元素的各个数据会通过连续的编号被区分开来,这个编号称为索引 (index)。
栈用的是 LIFO(Last Input First Out,后入先出)方式,而队列用的则是 FIFO(First Input First Out,先入先出)方式。
在数组的各个元素中,除了数据的值之外,通过为其附带上下一个元素的索引,即可实现链表 。
二叉查找树是指在链表的基础上往数组中追加元素时,考虑到数据的大小关系,将其分成左右两个方向的表现形式。
内存和磁盘也都被归类为存储部件。不过,利用电流来实现存储的内存,同利用磁效应来实现存储的磁盘,还是有差异的。而从存储容量来看,内存是高速高价,而磁盘则是低速廉价。
一般把输入装置、输出装置、存储器、运算器和控制器这 5 种部件设备称 为计算机的 5 大部件。
内存主要是指主内存(负责存储 CPU 中运行的程序指令和数据的内存),磁盘主要是指硬盘。
存储在磁盘中的程序需要读入到内存后才能运行。
磁盘缓存 指的是把从磁盘中读出的数据存储到内存空间中的方式。这样一来,当接下来需要读取同一数据时,就不用通过实际的磁盘,而是从磁盘缓存中把内容读出。使用磁盘缓存可以大大改善磁盘数据的访问速度
虚拟内存 是指把磁盘的一部分作为假想的内存来使用。这与磁盘缓存是假想的磁盘(实际上是内存)相对,虚拟内存是假想的内存(实际上是磁盘)。
为了实现虚拟内存,就必须把实际内存 (也可称为物理内存 )的内容,和磁盘上的虚拟内存的内容进行部分置换(swap),并同时运行程序。
虚拟内存的方法有分页式 和分段式 两种。Windows 采用的是分页式。该方式是指,在不考虑程序构造的情况下,把运行的程序按照一定大小的页(page)进行分割,并以页为单位在内存和磁盘间进行置换。在分页式中,我们把磁盘的内容读出到内存称为 Page In,把内存的内容写入磁盘称为 Page Out。一般情况下,Windows 计算机的页的大小是 4KB。
为了实现虚拟内存功能,Windows 在磁盘上提供了虚拟内存用的文件(page file,页文件 )。该文件由 Windows 自动做成和管理。文件的大小也就是虚拟内存的大小,通常是实际内存的相同程度至两倍程度。
虚拟内存无法彻底解决内存不足的问题。
DLL (Dynamic Link Library)文件 ,顾名思义,是在程序运行时可以动态加载 Library(函数和数据的集合)的文件。此外,还有一个需要大家注意的地方,那就是多个应用可以共有同一个 DLL 文件。而通过共有同一个 DLL 文件则可以达到节约内存的效果。
栈清理处理 是指,把不需要的数据从接收和传递函数的参数时使用的内存上的栈区域中清理出去。
磁盘是通过把其物理表面划分成多个空间来使用的。划分的方式有扇区方式 和可变长方式 两种,前者是指将磁盘划分为固定长度的空间,后者则是指把磁盘划分为长度可变的空间。一般的 Windows 计算机所使用的硬盘和软盘,采用的都是扇区方式。扇区方式中,把磁盘表面分成若干个同心圆的空间就是磁道 ,把磁道按照固定大小(能存储的数据长度相同)划分而成的空间就是扇区。
扇区是对磁盘进行物理读写的最小单位。Windows 中使用的磁盘,一般 1 个扇区是 512 字节。
Windows 在逻辑方面(软件方面)对磁盘进行读写的单位是扇区整数倍簇 。根据磁盘容量的不同,1 簇可以是 512 字节(1 簇 = 1 扇区)、1KB(1 簇 = 2 扇区)。
不管是硬盘还是软盘,不同的文件是不能存储在同一个簇中的,否则就会导致只有一方的文件不能被删除。因此,不管是多么小的文件,都会占用 1 簇的空间。这样一来,所有的文件都会占用 1 簇的整数倍的磁盘空间。
文件是将数据存储在磁盘等存储媒介中的一种形式。程序文件中存储数据的单位是字节。
把文件内容用“数据 × 重复次数”的形式来表示的压缩方法称为 RLE(Run Length Encoding,行程长度编码)算法。
虽然针对相同数据经常连续出现的图像、文件等,RLE 算法可以发挥不错的效果,但它并不适合文本文件的压缩。
我们把能还原到压缩前状态的压缩称为可逆压缩 ,无法还原到压缩前状态的压缩称为非可逆压缩。
运行环境 = 操作系统 + 硬件
CPU 只能解释其自身固有的机器语言。不同的 CPU 能解释的机器语言的种类也是不同的。
机器语言的程序称为本地代码 (native code)。程序员用 C 语言等编写的程序,在编写阶段仅仅是文本文件。文本文件(排除文字编码的问题)在任何环境下都能显示和编辑。我们称之为源代码 。通过对源代码进行编译,就可以得到本地代码。
同样机型的计算机,可安装的操作系统类型也会有多种选择。
BIOS 存储在 ROM 中,是预先内置在计算机主机内部的程序。BIOS 除了键盘、磁盘、显卡等基本控制程序外,还有启动“引导程序”的功能。引导程序 是存储在启动驱动器起始区域的小程序。操作系统的启动驱动器一般是硬盘,不过有时也可以是 CD-ROM 或软盘。 开机后,BIOS 会确认硬件是否正常运行,没有问题的话就会启动引导程序。引导程序的功能是把在硬盘等记录的 OS 加载到内存中运行。虽然启动应用是 OS 的功能,但 OS 并不能自己启动自己,而是通过引导程序来启动。
Dump 是指把文件的内容,每个字节用 2 位十六进制数来表示的方式。
能够把 C 语言等高级编程语言编写的源代码转换成本地代码的程 序称为编译器 。每个编写源代码的编程语言都需要其专用的编译器。将 C 语言编写的源代码转换成本地代码的编译器称为 C 编译器。
编译器首先读入代码的内容,然后再把源代码转换成本地代码。
读入的源代码还要经过语法解析、句法解析、语义解析等,才能生成本地代码
此外,还有一种交叉编译器 ,它生成的是和运行环境中的 CPU 不同的 CPU 所使用的本地代码。
编译器转换源代码后,就会生成本地文件。不过,本地文件是无法直接运行的。为了得到可以运行的 EXE 文件,编译之后还需要进行“链接”处理。
编译后生成的不是 EXE 文件,而是扩展名为“.obj”的目标文件。
把多个目标文件结合,生成 1 个 EXE 文件的处理就是链接 ,运行连接的程序就称为链接器 (linkage editor 或连结器)。
Windows 以函数的形式为应用提供了各种功能。这些形式的函数称为 API (Application Programming Interface,应用程序接口)。
Windows 中,API 的目标文件,并不是存储在通常的库文件中,而是存储在名为 DLL (Dynamic Link Library)文件 的特殊库文件中。
无论是 C 语言还是 C++,如果没有在程序中明确释放堆的内存空间,那么即使在处理完毕后,该内存空间仍会一直残留。这个现象称为内存泄露 (memory leak),它是令 C 语言及 C++ 的程序员们十分头疼的一个 bug(程序的错误)。如果内存泄露一直存在的话,就有可能会造成内存不足而导致宕机。这就好比,如果水龙头一直嘀嗒嘀嗒地漏水,那么一晚上的时间水桶就可能会装满并溢出。
操作系统(Operating System)也称为基础软件。操作系统是计算机运行时不可或缺的控制程序,以及在控制程序下运转的为其他软件运行提供操作环境的软件的统称。另外,在操作系统上运行的应用也称为“应用程序”。
汇编语言源文件的扩展名,通常用“.asm”来表示。
栈 是存储临时数据的区域,它的特点是通过 push 指令和 pop 指令进行数据的存储和读出。往栈中存储数据称为“入栈”,从栈中读出数据称为“出栈”。
IN 指令 通过指定端口号的端口输入数据,并将其存储在 CPU 内部的寄存器中。OUT 指令 则是把 CPU 寄存器中存储的数据,输出到指定端口号的端口。
I/O 是 Input/Output 的缩写。显示器、键盘等外围设备都有各自专用的 I/O 控制器。I/O 控制器中有用于临时保存输入输出数据的内存。这个内存就是端口 。
各端口之间通过端口号 进行区分。端口号也称为 I/O 地址 。
IRQ 是用来暂停当前正在运行的程序,并跳转到其他程序运行的必要机制。该机制称为中断处理 。中断处理在硬件控制中担当着重要角色。因为如果没有中断处理,就有可能出现处理无法顺畅进行的情况
获取伪随机数的公式。该公式称为线性同余法 。如果把 Ri 作为当前随机数的话,那么下一个出现的随机数 Ri + 1 就可以用下面的公式来获取。
R i + 1 = (a × Ri + b ) mod c
这种周期性是伪随机数的特征,也是为什么不是真随机数的原因。