写在前面 #
作为一名身在EE心在CS的不合格EE新生,CMU的神课Computer Systems:A Programmer’s Perspective一直令我神往,故而下载了教材阅读,并顺便记录笔记供自查与分享。
文中可能会有各种难以理解的名词,它们会在随后的章节中被解释,我之所以不在这里解释是因为我也还不会(。
信息=二进制位+语境 #
#include <stdio.h>
int main(void){
printf("Hello, World!");
}
这是一个最简单的"Hello, World"C程序,计算机在存储它时通过ASCII标准将其中的字符与换行转化为ASCII码,并以ASCII码的形式储存。存储在计算机中的这段程序其实只是一串有顺序的二进制位,这些二进制位的每八位组成一个"字节",每个字节都代表其中的一个字符。 其他数据也采用了相同的表达方式:在不同的语境中,一串相同的字节可能是整数、浮点、字符串、机器指令、etc。
程序被其他程序翻译为各种形式 #
C语言是一种高级语言,这也就是说计算机并不能直接执行,而需要其他程序将其翻译为计算机能够执行的机器语言。机器语言以二进制文件储存在磁盘中,以"目标程序"的形式储存。 这个过程叫做编译,由被称为编译系统的一系列程序构成。编译系统主要包括四部分:预处理器、编译器、汇编器和链接器;分别对应编译过程的四步。下面以前面的"Hello, World"为例分别介绍这四步。
-
预处理阶段
预处理器检测C程序中#开头的部分并生成处理过的新C程序文件,例如在这一程序的编译中,预处理器cpp会检测到
#include<stdio.h>
并将stdio.h直接插入到原程序文件中,这一步的处理结果一般会带有.i后缀。 -
编译阶段
编译器会将预处理得到的.i文件翻译为一个汇编语言程序,汇编语言是一种低级语言(但是人类依然还能理解一部分),使用不同的编译器编译不同的语言程序都会先得到汇编程序。汇编程序一般带有.s后缀。
-
汇编阶段
汇编器会将汇编程序.s文件翻译为二进制形式的机器指令,并将其封装在一个"可重定位目标文件"中,这类文件一般带有.o后缀,人类已经无法直接理解它了。
-
链接阶段
我们的Hello, World程序中调用了printf函数,但在生成的hello.o文件中并不包括printf的实现。链接器会将存储在系统中的printf.o与汇编得到的hello.o链接在一起,生成最终可供执行的"可执行目标程序"。
编译原理值得了解 #
理解编译原理有利于:
- 优化程序性能
- 理解链接错误
- 避免安全漏洞
处理器读取并处理内存中存储的指令 #
我们可以利用Shell来运行我们编写的Hello程序。Shell是一种命令行解释程序,这意味着它接受命令行输入并执行。我们在Shell中输入./hello, Shell会检测到hello是一个可执行文件,并将它加载到内存中后执行。程序执行完成后,Shell会继续请求命令行输入。
计算机系统的硬件组成 #
总线 #
总线是沿着整个系统运行的一系列电子线路的集合。总线可以将信息位在元件之间来回传输, 总线一般所传输的信息位数是固定的, 这些字节的组块被称作“语词”, “语词”长度一般为4字节(也就是32位)或8字节(也就是64位)
I/O 设备 #
I/O设备以输入或输出的方式建立计算机与外界的连接。I/O设备通过控制器或者适配器连接到I/O总线。控制器是主板上的专门芯片,适配器则是一些连接在主板上的设备。
主内存 #
主内存是一种临时储存设备,在处理器执行程序时会将程序与对应的数据暂存进内存。内存一般采用DRAM(Dynamic Random Access Memory,动态随机访问存储)技术。在软件层面内存会被抽象为一系列线性的数组,并从0开始编号,这一编码被称为内存地址。
处理器 #
这里的处理器是一般说的中央处理器(CPU),是处理并执行内存中存储的指令的元件。在其中有一个单个语词长度的寄存器,被称作PC(Program Counter,程序计数器)。PC在运行中指向内存中的某条机器语言指令(也就是现在要运行的)。 只要计算机接电,处理器就会不断执行程序计数器所指的那条指令,程序会主动更新程序计时器以便执行下一条命令。处理器根据其指令集(ISA)的不同,具有其独有且唯一的指令执行模型,其中包括执行指令的顺序与执行指令的一系列步骤。 CPU能执行的指令实际上只有简单的几条。