计算机系统漫游
2533字约8分钟
计算机系统结构
2021-08-01
本系列是我学习计算机系统结构的一些笔记和思考,主要学习书籍为《深入理解计算机系统》(CSAPP)。这一篇对应本书的第一章。
学习计算机系统的作用
计算机系统由硬件 + 系统软件组成,他们共同工作来运行应用程序。
当我们编写并运行一个程序,学习计算机体系结构的知识,可以帮助我们了解在执行这个程序时,系统背后发生了什么以及为什么会这样。从而让我们少踩坑,同时做出更好的优化。
信息 = 位 + 上下文
文件的ASCII表示
程序都是从源文件开始的,例如下面的 hello 程序,我们可以把这一段文本保存为 hello.c
文件,这个文件称为源文件。
#include <stdio.h>
int main()
{
printf("hello, world\n");
return 0;
}
在计算机中,源文件是由 0 和 1 组成的位(bit)序列。大部分现代计算机都用 ASCII 标准来表示文本。例如 hello.c
文件开头的 #
字符,对应的 ASCII 码整数值是 35
,每一行行尾的换行符 \n
,对应的整数值是 10
。 这些整数值又是以二进制保存的,例如,35
的二进制是 0010 0011
。
文本文件和二进制文件
像 hello.c
这样,只包含 ASCII 字符的文件称为文本文件
,其他文件称为二进制文件
。
比特和上下文
计算机系统的所有信息都只是 0 和 1 组成的比特流,包括磁盘文件、内存中的程序、网络传输的数据等等,这些比特流代表什么含义,只有通过我们读取到它们时的 上下文
来区分。一段相同的比特流,在不同的上下文中,可能代表不同的含义。
程序的格式转换
hello.c
文件是由 C 语言编写的, C语言我们能够读懂,但计算机并不能读懂。要让计算机读懂这段程序,就需要把源程序翻译成计算机能读懂的目标程序。
在 Linux 系统中,通过下面这个命令,由 编译器驱动程序
来完成源文件到目标文件的转换:
linux> gcc -o hello hello.c
这个命令读取一个 hello.c
文件,经过一系列转换,输出一个 hello
文件。 这个 hello
文件便能够被计算机读懂并执行。
这中间主要经历四个过程:
- 预处理器(ccp) :将
hello.c
文件 include 的 .h 文件头,插入到程序文本中,输出hello.i
- 编译器(ccl) :将
hello.i
翻译成汇编语言,输出hello.s
- 汇编器(as) :将汇编语言翻译成机器语言,输出
hello.o
- 链接器(ld) :
hello
程序调用了printf
函数,这个函数在 C语言库中以及预编译好了,链接器负责把hello.o
和printf.o
合并,输出可执行文件hello
生成的 hello
文件,可以在 Linux 下执行
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) -> 本地二级存储(磁盘) -> 远程二级存储(网络)
操作系统
事实上,当我们执行程序时,程序并不在硬件结构上直接执行(不直接对接键盘、显示器、主存等),而是通过操作系统。
操作系统是应用程序和硬件中间的一层软件。主要有两个作用:
- 防止硬件被失控的应用程序滥用
- 向应用程序提供简单一致的机制来控制复杂各异的硬件设备
操作系统通过抽象出进程、虚拟内存、文件等概念,来实现这一目标。例如,文件是对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,可以让我们通过浏览器直接连接到远程的电脑,操作“云端”上的个人电脑,游玩“云端”上的游戏。
参考书籍:
- 《深入理解计算机系统》