计算机系统漫游

本系列是我学习计算机系统结构的一些笔记和思考,主要学习书籍为《深入理解计算机系统》(CSAPP)。这一篇对应本书的第一章。

学习计算机系统的作用

计算机系统由硬件 + 系统软件组成,他们共同工作来运行应用程序。

当我们编写并运行一个程序,学习计算机体系结构的知识,可以帮助我们了解在执行这个程序时,系统背后发生了什么以及为什么会这样。从而让我们少踩坑,同时做出更好的优化。


信息 = 位 + 上下文

文件的ASCII表示

程序都是从源文件开始的,例如下面的 hello 程序,我们可以把这一段文本保存为 hello.c 文件,这个文件称为源文件。

1
2
3
4
5
6
7
#include <stdio.h>

int main()
{
printf("hello, world\n");
return 0;
}

在计算机中,源文件是由 0 和 1 组成的位(bit)序列。大部分现代计算机都用 ASCII 标准来表示文本。例如 hello.c 文件开头的 # 字符,对应的 ASCII 码整数值是 35,每一行行尾的换行符 \n,对应的整数值是 10。 这些整数值又是以二进制保存的,例如,35 的二进制是 0010 0011

1-2hello_c_ascii

文本文件和二进制文件

hello.c 这样,只包含 ASCII 字符的文件称为文本文件,其他文件称为二进制文件

比特和上下文

计算机系统的所有信息都只是 0 和 1 组成的比特流,包括磁盘文件、内存中的程序、网络传输的数据等等,这些比特流代表什么含义,只有通过我们读取到它们时的 上下文 来区分。一段相同的比特流,在不同的上下文中,可能代表不同的含义。


程序的格式转换

hello.c 文件是由 C 语言编写的, C语言我们能够读懂,但计算机并不能读懂。要让计算机读懂这段程序,就需要把源程序翻译成计算机能读懂的目标程序。

在 Linux 系统中,通过下面这个命令,由 编译器驱动程序 来完成源文件到目标文件的转换:

1
linux> gcc -o hello hello.c

这个命令读取一个 hello.c 文件,经过一系列转换,输出一个 hello 文件。 这个 hello 文件便能够被计算机读懂并执行。

这中间主要经历四个过程:

1-3compiler

  1. 预处理器(ccp) :将 hello.c 文件 include 的 .h 文件头,插入到程序文本中,输出 hello.i
  2. 编译器(ccl) :将 hello.i 翻译成汇编语言,输出 hello.s
  3. 汇编器(as) :将汇编语言翻译成机器语言,输出hello.o
  4. 链接器(ld)hello 程序调用了 printf 函数,这个函数在 C语言库中以及预编译好了,链接器负责把 hello.oprintf.o 合并,输出可执行文件 hello

生成的 hello 文件,可以在 Linux 下执行

1
2
3
linux> ./hello
hello world
linux>

系统的硬件组成

总线

总线是一组电子管道,用来在系统的各个组件中传输信息。总线传输的单位是字(word),一个字通常为 4 个字节(32位)或 8 个字节(64位)。

I/O设备

I/O设备用于连接系统与外部世界,例如键盘、鼠标、显示器、磁盘。每个I/O设备都通过 控制器适配器 与 I/O总线 相连接。两者的作用都是在 I/O总线 和 I/O设备 之间传递信息。

  • 控制器:I/O设备本身,或系统主板上的芯片组
  • 适配器:插在主板插槽上的卡

主存

主存用于在执行程序时,临时存放程序和数据。主存由 DRAM芯片 组成,在逻辑上是线性的字节数组,每个字节具有唯一的地址(数组下标),地址从零开始。

处理器

处理器的全称是中央处理单元(CPU),用于解释(或执行)主存中的指令。处理器的核心是一个大小为一个字的寄存器,称为程序计数器(PC),程序计数器指向主存中的某条指令,按照一定的指令执行模型(由指令集架构决定)执行程序。CPU常见的动作有:

  • 加载:从主存复制一个字节(或字)到寄存器
  • 存储:从寄存器复制一个字节(或字)到主存
  • 操作:把两个寄存器的内容复制到ALU做算术运算,并将结果存放到一个寄存器中
  • 跳转:从指令中抽取一个字,复制到程序计数器中

这些动作都会覆盖掉目标地址的值

高速缓存

CPU从寄存器读取数据,比在主存中几乎要快100倍,在主存中读数据又比在磁盘中读要快1000万倍。为了更快地处理数据,现代计算机通常会在CPU寄存器和内存之间加两个高速缓存寄存器(cache memory),用于存放CPU近期要处理的数据。这两个高速缓存寄存器被称为 L1 和 L2 高速缓存,是用一种叫做 SRAM 的硬件技术实现的。

存储设备的层次结构

越接近处理器的存储设备,更小、更快、更贵。层次结构链为:

寄存器 -> L1高速缓存(SRAM) -> L2高速缓存(SRAM) -> L3高速缓存(SRAM) -> 主存(DRAM) -> 本地二级存储(磁盘) -> 远程二级存储(网络)


操作系统

事实上,当我们执行程序时,程序并不在硬件结构上直接执行(不直接对接键盘、显示器、主存等),而是通过操作系统。

操作系统是应用程序和硬件中间的一层软件。主要有两个作用:

  1. 防止硬件被失控的应用程序滥用
  2. 向应用程序提供简单一致的机制来控制复杂各异的硬件设备

操作系统通过抽象出进程、虚拟内存、文件等概念,来实现这一目标。例如,文件是对I/O设备的抽象,虚拟内存是对主存和磁盘的抽象,进程是对CPU、主存和I/O设备的抽象。

进程

进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程都好像独占地使用硬件。在单核CPU中,一个进程的指令和另一个进程的指令可以交错运行,称为 并发,这是操作系统提供的一种机制,也称为 上下文切换。多核CPU能够真正实现多个进程同时运行,称为 并行, 但进程数大于CPU核心个数时,同样也需要交错运行。(不考虑超线程处理器和指令集并行等新一代硬件技术的情况下)

执行权由一个进程到另一个进程的转换由操作系统内核(kernel)管理,内核不是一个进程,而是系统管理全部进程所用的代码和数据结构。

当应用程序读写文件等系统操作时,会执行 系统调用 指令,将控制权转交给内核。内核执行完对应的操作后返回给应用程序。通常,在进程发出 IO 操作时,内核会挂起该进程,将 CPU 执行权转交给其他进程,直至 IO 响应(如磁盘中断),才会重新唤醒该进程,继续参与上下文切换。

线程

一个进程可以有多个不同的控制流,称为线程。一个进程里面的所有线程共享该进程的代码和数据。现代程序一般都具有多线程模型。

虚拟内存

虚拟内存为进程提供了一个假象,让进程以为自己独占主存。每个进程看到的内存都是一致的,称为虚拟地址空间。Linux 中的虚拟内存一般有以下区域:

  • 程序代码和数据 :即我们的代码和全局变量等数据,程序开始时就被指定了大小
  • : 调用 malloc 和 free 时,分配或释放内存时动态扩展或收缩的一片内存空间
  • 共享库 : 存放C标准库和数学库等共享库的代码和数据区域
  • 用户栈 :函数调用,调用一个函数时增长,函数返回时收缩
  • 内核虚拟内存 :内核保留,不允许用户程序读写

文件

文件就是字节序列,在 UNIX 中,一切I/O设备皆可视为文件,包括磁盘、键盘、显示器、网络等。所有的输入输出都是通过 Unix I/O 的系统函数调用读写文件来实现的。


网络

一个系统和另一个系统,可以通过网络连接到一起。网络也是一种 I/O 设备,系统通过网络适配器读写来自其他设备的数据。我们平时收发电子邮件、发微信、刷微博等,都是基于网络。世界上最大的互联网络称为互联网(Internet)。

一个简单的例子是,我们在本地设备通过 telnet 客户端连接到远程主机的 telnet 服务端,运行远程主机的 hello 程序,远程主机会把 hello 程序的运行结果 hello wolrd 通过网络传送给我们本地,并显示在我们的显示器上。

如今,5G等网络技术的发展正在改变我们的生活。例如华为云电脑,谷歌Stadia,可以让我们通过浏览器直接连接到远程的电脑,操作“云端”上的个人电脑,游玩“云端”上的游戏。


参考书籍:

  • 《深入理解计算机系统》