在 unix 系统中,我们可以使用 shell 窗口(windows 系统里是 dos 窗口)执行已经编译好的可执行文件 hello:
unix> ./hello
hello, world
unix>
shell 是一个命令行解释器,它输出一个提示符,等待你输入一个命令行,然后执行这个命令。如果该命令行的第一个单词不是一个内置的外壳命令,那么外壳就会假设这是一个可执行文件的名字,它将加载并运行这个文件。
说白了,shell 解释器就是对命令行进行解释运行,即使是可执行文件,也是由可运行的命令行组成。
在第一篇笔记中提到:计算机系统就是由硬件和系统软件组成,因此想要了解可执行文件如何运行起来的,需要知道计算机系统到底有那些硬件和软件。
下图展示的是一个典型计算机系统需要具备的硬件结构:
一个典型系统的硬件组成图中:CPU 是中央处理单元 ;ALU 是算术 / 逻辑单元 ;PC 是程序计数器 ;USB 是通用串行总线。
1. 总线
贯穿整个系统的是一组电子管道,称做总线,它携带信息字节并负责在各个部件间传递。
硬件之间进行信息交流需要有一个统一的标准,也就是二进制信息传递规则,为了高效考虑,通常总线被设计成传送定长的字节块,也就是字(word)。字中的字节数(即字长)是一个基本的系统参数,在各个系统中的情况都不尽相同。
操作系统中的 32 位(4 个字节)或 64 (8 个字节)位就叫总线的字长单位。
2. I/O 设备
输入 / 输出(I/O)设备是系统与外部世界的联系通道。每个 I/O 设备都通过一个控制器或适配器与 I/O 总线相连。
3. 主存
主存是一个临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。从物理上来说,主存是由一组动态随机存取存储器(DRAM)芯片组成的。从逻辑上来说,存储器是一个线性的字节数组,每个字节都有其唯一的地址(即数组索引),这些地址是从零开始的。
计算机硬件世界里的内存条就是主存。
4. 处理器
中央处理单元(CPU),简称处理器,是解释(或执行)存储在主存中指令的引擎。处理器的核心是一个字长的存储设备(或寄存器),称为程序计数器(PC)。在任何时刻,PC 都指向主存中的某条机器语言指令(即含有该条指令的地址)。
术业有专攻,计算机的世界里,你做你的,它做它的,大家各司其职。 如果想要 hello 可执行文件(存储在主存上)运行起来,就要调用程序计数器执行整个程序的每一条指令,并有专门的硬件来处理数值计算逻辑操作,并且在数值处理过程中会产生临时的数据,这就需要有个地方寄存,等待所有程序执行结束之后,再存到主存上。
处理器从程序计数器(PC)指向的存储器处读取指令,解释指令中的位,执行该指令指示的简单操作,然后更新 PC,使其指向下一条指令,而这条指令并不一定与存储器中刚刚执行的指令相邻。
然而操作是围绕着主存、寄存器文件(register file)和算术 / 逻辑单元(ALU)进行的。寄存器文件是一个小的存储设备,由一些 1 字长的寄存器组成,每个寄存器都有唯一的名字。ALU 计算新的数据和地址值。
下面列举一些简单操作的例子,CPU 在指令的要求下可能会执行以下操作 :
1. 从键盘上读取 hello 命令
当在 unix 系统的 shell 程序命令行里输入"./hello"的时候, shell 程序会将用户输入的字符逐个加载到寄存器里,再把它存储到存储器中,此时 shell 干的事情就是将敲打出来的字符符号显示在显示器上,还没有执行这个可执行文件, 直到用户敲击回车键,则 shell 程序就开始将这个 hello 可执行文件运行起来:
从键盘上读取 hello 命令从磁盘加载可执行文件到主存:
计算机还可以利用直接存储器存取(DMA)的技术,可以将数据直接从磁盘到达主存(不通过处理器):
从磁盘加载可执行文件到主存
2. 将输出字符串从内存写到显示器
明确一点:可执行文件 hello 执行的最终目的是打印
"hello, world\n"
这个字符串,因此它是数据,其他的字符符号均是数据之外的空格符号及机器语言指令。
首先将 hello 目标文件中的代码和数据(数据就包括"hello, world\n"
这个字符串)从磁盘复制到主存,当主存里加载好了 hello 文件的全部信息,处理器就处理器就开始执行 hello 程序的main 程序中的机器语言指令,这些指令在 PC 中逐行执行,目的就是将"hello, world\n"
这个字符串中的字节信息从主存复制到寄存器文件,再从寄存器文件中复制到显示设备,最终显示在屏幕上:
根据机械原理,较大的存储设备要比较小的存储设备运行得慢,而快速设备的造价远高于同类的低速设备。类似地,处理器从寄存器文件中读数据的速度比从主存中读取几乎要快 100 倍+。
针对这种处理器与主存之间的差异,系统设计者采用了更小、更快的存储设备,即高速缓存存储器(简称高速缓存),作为暂时的集结区域,用来存放处理器近期可能会需要的信息:
典型系统中的高速缓存存储器上图中的高速缓存存储器
位于处理器芯片上,现代硬件系统一般包含着三级高速缓存 :L1、L2 和 L3。其中:L1 和 L2 高速缓存是用一种叫做静态随机访问存储器(SRAM)的硬件技术实现的。
计算机通过让高速缓存里存放可能经常访问的数据的方法,大部分的存储器操作都能在快速的高速缓存中完成,因此高速缓存的目的只有两个:快速、高效率,本书有个重要的结论之一,就是意识到高速缓存存在的应用程序员可以利用高速缓存将他们程序的性能提高一个数量级。
存储设备形成层次结构
在小快高价的处理器和大慢廉价的存储设备之间的差异,使得计算机系统在设计存储系统结构之初就遵循一个很重要很普遍的观念:每个计算机系统中的存储设备都被组织成了一个存储器层次结构。
一个存储器层次结构的示例在这个层次结构中,从上至下,设备变得访问速度越来越慢、容量越来越大,并且每字节的造价也越来越便宜。寄存器文件在层次结构中位于最顶部,也就是第 0 级或记为 L0。这里我们展示的是三层高速缓存 L1 到 L3,占据存储器层次结构的第 1 层到第 3 层。主存在第 4 层,以此类推。
存储器层次结构的主要思想是一层上的存储器作为低一层存储器的高速缓存。因此,寄存器文件就是 L1 的高速缓存,L1 是 L2 的高速缓存,L2 是 L3 的高速缓存,L3 是主存的高速缓存,而主存又是磁盘的高速缓存。在某些具有分布式文件系统的网络系统中,本地磁盘就是存储在其他系统中磁盘上的数据的高速缓存。
正如可以运用不同的高速缓存的知识来提高程序性能一样,程序员同样可以利用对整个存储器层次结构的理解来提高程序性能。
始终牢记计算机系统是由硬件和程序软件组成,而两者归根到底均为了存储信息而服务,即使是在计算机计算的过程中会产生临时的“多余信息”,也需要有一个物理位置用来寄存以备后用。
一个程序要想运行起来并不是想我们简单想象那样一眼就能识别并“跑起来”,而经过计算机中各司其职的各个组件进行相互合作,从而完美地执行一个程序要想计算机做出的结果。
硬件中核心概念:总线是人们常说的操作系统中的位数;主存是存储驱动器(磁盘);I/O设备一般包括鼠标、键盘、显示器,还有更狭义的网络;处理器则是的整个计算机运算的核心,它与前两者在总线的基础之上承载着“控制主权”,它是解释(或执行)存储在主存中指令的引擎,其中记录着一行行指令的叫PC(程序计数器),算术 / 逻辑单元(ALU)负责对数值进行算术或逻辑操作,操作过程中的起始和终止信息在寄存器里反复的更新存储,当处理器执行解释完所有的指令之后,将计算出的结果在存储到主存中。