计算机系统漫游

计算机系统是由硬件和软件系统组成的,它们共同工作来运行应用程序。作为程序员,也需要了解这些组件是如何工作的,以及这些组件是如何影响程序的正确性和性能的。我们从最简单的 hello 程序开始,通过跟踪 hello 程序的生命周期(从被创建,到运行,输出,然终止),来开始对计算机系统的学习。

1. hello 程序创建

hello.c

#include <stdio.h>

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

hello 程序的生命周期是从源程序 hello.c 开始的,它实际上是由 0 和 1 组成的位(bit)序列,8 位称为一个字节。根据 ASCII 标准,每个字节表示一个特定的文本字符,如下图所示,hello.c 程序中的每个字符都对应一个单字节。

这里写图片描述

hello.c 的表示方法说明了一个基本思想:计算机系统中的所有信息,包括磁盘中的文件、存储器中的程序和数据、网络上传的数据,都是一串由 0 和 1 组成的位(bit)序列,区分不同数据对象的唯一方法是我们读到这些数据对象时的上下文。

2. hello 程序编译

2.1 编译系统

前面创建的 hello.c 程序是一个高级 C 语言程序,我们能够读懂,但计算机系统不懂。必须把高级语言转化为计算机系统能够执行的低级机器语言指令,得到的是可执行目标文件 hello。从源程序文件 hello.c 到可执行目标文件 hello,编译过程分为四个阶段:预处理阶段、编译阶段、汇编阶段和链接阶段,这四个阶段分别由预处理器、编译器、汇编器和链接器完成,它们一起构成了编译系统。

编译系统

  • 预处理阶段。预处理器根据以 # 字符开头的命令,向源程序添加相应的头文件。如 hello.c 第一行的 #include,预处理器读取系统头文件 stdio.h,并将其直接插入到程序文本,得到 hello.i 文件。
  • 编译阶段。编译器将 hello.i 翻译成汇编程序文件 hello.s。
  • 汇编阶段。汇编器将汇编程序文件 hello.s 翻译成机器语言指令,并将指令打包成可重定位目标程序,保存在 hello.o 中。hello.o 是二进制文件,而不是文本文件,因此用文本编辑器打开,发现是一堆乱码。
  • 链接阶段。hello 程序调用了 printf 函数,这个函数存在于已经编译好的 printf.o 中,链接器负责将 .o 文件合并,得到一个可执行目标文件 hello,可以被加载到内存中,由系统执行。

2.2 程序员为什么要了解编译系统?

  1. 优化程序性能。为了在 C 程序中做出好的编码选择,我们确实需要了解一些机器代码以及编译器将不同 C 语句转化为机器代码的方式。例如,一个 switch 语句是否总比一系列 if-then-else 语句高效得多?一个函数调用的开销有多大?while 循环比 for 循环更有效吗?指针引用比数组索引更有效吗?
  2. 理解链接时出现的错误。一些令人困扰的程序错误往往都与链接器操作有关。
  3. 避免安全漏洞。缓冲区溢出是造成大多数网络和 Internet 服务器上安全漏洞的主要原因。通过更好地理解编译系统,可以降低这些错误的出现。

3 hello 程序执行

此时,hello.c 源程序已经被编译系统翻译成可执行目标文件 hello,存放在磁盘上。在 Linux 系统的外壳(shell)中可以执行 hello。为了理解 hello 执行的时候发生了什么,我们先对一个典型系统的硬件组成进行简单介绍。

3.1 计算机系统硬件组成

计算机系统硬件组成
如图所示,典型系统的硬件组成包括总线、I/O设备、主寸和处理器。

  • 总线。总线贯穿整个系统,它携带信息字节并负责在各个部件间传递,每次只传一个字,字的长度成为字长,有的机器字长是 4 个字节(32位),有的是 8 个字节(64位)。
  • I/O 设备。输入/输出设备是系统与外部联系的通道。上图包括 4 个 I/O 设备:作为输入的鼠标和键盘、用于输出的显示器、用于存储的磁盘(可执行文件 hello 存于此),网络也可视作一个 I/O 设备。
  • 主存。主存是临时存储设备,在处理器执行程序时,用来存放程序和所需的数据。物理上看,主存是由 DRAM 芯片组成的;逻辑上看,主存是一个线性的字节数组,每个字节都有其唯一的地址。
  • 处理器。处理器是执行主存中指令的引擎,其核心是一个字长的程序计数器(PC)。在任何时刻,PC 都指向主存中的某条机器语言指令。ALU 是算术逻辑单元;寄存器文件是一个小的存储设备,由一些 1 字长的寄存器组成,每个寄存器都有唯一的名字。CPU 在指令的要求下可能会执行以下操作:
加载存储操作跳转
把一个字节/字从主存复制到寄存器,覆盖寄存器原来的内容。把一个字节/字从寄存器复制到主存的某个位置。把两个寄存器的内容复制到ALU,ALU它们进行运算,结果保存到一个寄存器中。从指令本身抽取一个字,将其复制到 PC中。(即改变 PC 的地址实现跳转)

3.2 运行 hello 程序

首先我们在外壳(shell)输入字符串“./hello”以执行 hello 程序,外壳程序将字符逐一从输入设备读入 CPU 的寄存器,再把它放到主存中。

键入回车,外壳会执行一系列代码加载 hello,将 hello 的代码和数据从磁盘复制到主存,利用 DMA(直接存储器存取技术),数据可以不通过处理器而直接从磁盘达到主存。

hello 加载完之后,处理器开始执行 hello 程序中的 main 程序,将“hello. world\n”字符串从主存复制到寄存器文件,再从寄存器文件复制到显示设备,最终显示在屏幕上。

3.3 高速缓存和存储器层次结构

看完上面 hello 的执行过程,可以发现,系统花费了大量时间把信息从一个地方传到另一个地方,系统设计者的一个主要目标是使这些复制操作尽可能快地完成。针对处理器与主存之间速度的差异,系统设计者用更小、更快的存储设备,即高速缓存存储器,作为暂时的集结区域,用于存放处理器近期可能会需要的信息。

高速缓存

上图展示了一个典型系统的中高速缓存,处理器芯片上的 L1 高速缓存大小可达数万字节,访问速度几乎和访问寄存器文件一样快。还有 L2 ,甚至 L3 高速缓存,随着容量增大,访问速度变慢。高速缓存是用 SRAM (静态随机访问存储器)的硬件技术实现的。有了高速缓存,程序的执行性能可以提高一个数量级。

实际上,除了在处理器和主存之间插入多级高速缓存,每个计算机系统中还有更复杂的存储器层次结构,如下图所示,这个层次结构从上到下,访问速度越来越慢,容量越来越大,并且每个字节的造价也越来越便宜。L0 为寄存器文件,L1 到 L3 为三级高速缓存,然后是主存、本地磁盘,最后是远程二级存储。存储器层次结构的主要思想是:一层上的存储器是低一层存储器的高速缓存,如寄存器文件是 L1 的高速缓存,L1 是 L2 的高速缓存等。

存储器层次结构

4. 操作系统管理硬件

当我们加载和运行 hello 程序,以及 hello 程序输出时,外壳和 hello 程序都没有直接访问键盘、显示器、磁盘和主存。它们依靠操作系统来管理硬件。操作系统有两个基本功能:(1)防止硬件被失控的应用程序滥用;(2)向应用程序提供了简单一致的机制来控制复制而又大相径庭的硬件设备。操作系统是通过几个基本的抽象概念来实现这两个功能的。

  • 文件。是操作系统对 I/O 设备的抽象表示。
  • 虚拟存储器。是操作系统对主存和磁盘 I/O 设备的抽象表示。
  • 进程。是对处理器、主存和 I/O 设备的抽象表示。
    操作系统提供的抽象表示

4.1 进程

hello 程序运行时,操作系统会提供一个假象,就好像系统上只有这个程序在运行。这是通过进程的概念来实现的,进程是计算机科学中最重要和最成功的概念之一。进程是操作系统对正在运行程序的一种抽象,一个系统可以同时运行多个进程,这种并发运行时通过处理器在进程间的切换(称为上下文切换)实现的。实际上,在任何时刻,单处理器系统都只能执行一个进程的代码。

进程的上下文切换

如上图所示,有两个进程:外壳进程和 hello 进程。一开始只有外壳进程在运行,输入“./hello”命令后,系统调用将控制权传递给操作系统,操作系统保存当前外壳进程的上下文,并创建新的 hello 进程及其上下文,将控制权传递给新的 hello 进程。hello 进程终止后,操作系统恢复外壳进程上下文,并将控制权传回给它,外壳进程等待下一个命令输入。

4.2 线程

通常我们认为一个进程只有单一控制流,如 hello 进程只有 main 控制流。但是在现代系统中,一个进程可以由多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。一般来说,线程比进程更高效,所以多线程之间比多进程之间更容易共享数据。

4.3 虚拟存储器

虚拟存储器是一个抽象的概念,它为每个进程提供了一个假象:每个进程都在独占使用主存。每个进程看到的是一致的存储器,称为虚拟地址空间。

虚拟存储器

每个进程的虚拟地址空间由大量准确定义的区构成,每个区都有专门的功能。

  • 程序代码和数据。代码从一个固定地址开始,接着是数据。代码和数据区是直接按照可执行目标文件(在此是 hello)的内容初始化的。
  • 。代码和数据区紧跟着是堆,前者运行时规定了大小,后者可以调用 malloc 和 free 在运行时动态扩展和收缩。
  • 共享库。地址中间一部分存放着像 C 标准库和数学库这样的共享库的代码和数据的区域。
  • 。虚拟地址空间顶部的是用户栈,可以动态扩展和收缩。如调用一个函数时,栈会增长,从一个函数返回时,栈会收缩。
  • 内核虚拟存储器。是操作系统的一部分,不允许应用程序读写或调用。

虚拟存储器的运作需要硬件和操作系统之间精密复杂的交互,其基本思想是把一个进程虚拟存储器的内容存储在磁盘上,然后用主存作为磁盘的高速缓存。

4.4 文件

文件就是字节序列,仅此而已。每个 I/O 设备,包括磁盘、键盘、显示器,甚至网络,都可以视为文件。它向应用程序提供了一个统一的视角,来看待系统中可能含有的所有各式各样的 I/O 设备。

5. 其他重要主题

在上述计算机系统漫游中,我们得出重要结论:计算机系统不仅仅只是硬件。系统是硬件和软件互相交织的集合体,它们必须共同协作以达到运行应用程序的最终目的。我们来再来讨论几个贯穿计算机系统的重要概念作为结尾。

5.1 并发和并行

我们需要计算机做得更多,也需要它运行得更快,怎么办呢?因此引入了并发,并发是一个通用的概念,指一个同时具有多个活动的系统;并行是指用并发使一个系统运行得更快。并行有三个层次,由高到低分别是:线程级并发;指令级并行;单指令多数据并行。

  • 线程级并发。在一个进程中执行多个控制流,即多线程。传统上通过线程间的切换实现并行,随着多核处理器和超线程的出现,线程之间的切换变得更加的快速。
  • 指令级并行。即处理器可以同时执行多条指令,
  • 单指令、多数据并行。允许一条指令产生多个可以并行执行的操作,即 SIMD 并行,如较新的 Intel 和 AMD 处理器并行地对 4 对单精度浮点数做加法的指令。

5.2 计算机系统中的抽象

抽象是计算机科学中最为重要的概念之一。例如,在编程中可以为函数提供抽象的接口,程序员无需了解函数内部工作机制就可以使用这些代码。如文件是对 I/O 设备的抽象;虚拟存储器是对主存和磁盘 I/O 的抽象;进程是对一个正在运行的程序的抽象;而虚拟机是对整个计算机(包括操作系统、处理器和程序)的抽象。

6 总结

由一个 hello 程序的生命周期对整个计算机系统进行了快速的漫游。从中我们知道了,计算机系统是由硬件和软件组成的,共同协作以运行程序。计算机内部的信息实际上是一系列位序列,根据上下文有不同的解释方式。编译系统将源程序经过预处理、编译、汇编和链接四个步骤翻译成可执行目标文件。由于程序运行时,信息常常由一个地方被复制到另一个地方,为了提高传递速度,提出了存储器层次结构,由高到低分别是寄存器文件,L1 高速缓存,L2 高速缓存,L3 高速缓存,主存,磁盘,远程存储器等,由上到下,容量依次变大,但速度越来越慢,每个字节的造价也越来越便宜。

另外,应用程序并不是直接操纵计算机硬件,而是通过操作系统代为管理,这样可以避免失控的应用程序对系统硬件的滥用,同时为应用程序提供了简单统一的机制来控制复杂多样的硬件。具体是通过进程、虚拟存储器和文件等抽象概念实现的。最后还讨论了三个不同层次的并发机制,并说明了计算机系统中抽象的重要性。


参考:《Computer Systems A Programmer’s Perspective》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值