系统漫游

计算机系统不仅只是硬件,而是硬件和系统软件互相交织的集合体,二者只有相互协作才能使应用程序流畅运行。我们将通过追踪一个程序的生命周期来尽可能清晰简洁地介绍计算机系统的基本概念。

Snip20160605_4
图1 hello.c

hello程序的生命周期的起始是从源程序hello.c开始的,正如我们所见,代码以文本形式呈现。现代计算机系统都使用ASCII标准来表示字符,它们可以理解为用单字节来表示的编码。每个字节由八个0或1组成的序列表示,即源程序中每个字符均由唯一的二进制序列来表示。源程序中第一个字符‘#’对应的二进制位为0010 0011,其整数值为35。所以我们可以理解成整个源程序由大量0和1来组成,不过系统在识别的时候,每8个比特为一组,将其翻译成唯一的字符。

由此可见,实际上计算机中所有的数据都是通过0和1来表达。但是我们通过不同的解释方式将解释为文本、整数、浮点数或者是指令。

但是,源程序是不能够直接被计算机识别并执行的,所以接下来要做的工作就是对现有的源代码进行编译,使其被转成计算机可以识别的机器语言指令。在计算机上,完成此项任务的是编译器,最为人熟知的当属*nix的gcc,所以当我们需要将hello.c编译成可执行文件hello时,需要在命令行下键入如下命令:> gcc –o hello hello.c

这条命令行指令调用gcc编译器完成如下几个步骤的工作。

Snip20160604_1
图2 编译系统

预处理阶段:gcc读入源文件hello.c,读取源代码中第一行代码#include <stdio.h>,并将标准输入输出头文件stdio.h的内容直接插入到hello.c中,形成新的完整的源代码hello.i。

编译阶段:这个阶段会将hello.i文件翻译成包含汇编语言程序以及程序其他参数信息的文本文件hello.s。汇编语言相对于C语言属于更低一级的程序设计语言,在汇编语言级,每一条汇编语言代码都可以唯一对应一条机器语言指令。

汇编阶段:汇编阶段的目的是将上一步中的每条汇编语言都转化成机器语言指令,此时每条汇编语言都将被转化成由0和1构成的机器可以直接识别的指令,至于指令是如何被处理器识别并执行的由其对应的指令集结构决定。被翻译完成的机器指令被打包成可重定位目标文件hello.o,而hello.o是单纯的二进制文件,它的编码方式是机器语言指令而非之前的ASCII编码。

链接阶段:在hello程序中,使用到了标准输出函数printf,而printf作为最常用的库函数,常通过动态链接的方式与可重定位目标文件hello.o进行链接。在动态链接的方式中,printf函数被预先编译汇编成目标文件printf.o并被加载在内存中,等有程序需要使用到该函数的时候,通过链接器将hello.o和printf.o两个可重定位目标文件合成成一个完整的可执行文件hello并可以直接被加载并被执行。

此时,源文件hello.c已经被编译器翻译成了可执行目标文件hello并存放在磁盘上。如果想要执行该可执行文件,则需要我们在shell中键入如下内容方可执行:> ./hello

其中./表示当前目录下寻找,所以这句命令行指令的意思就是在当前所在目录下寻找可执行文件hello并加载执行,直到程序终止。在命令行下键入的内容分为两种,一种是内置的shell命令,比如ls、mv、cd等,如果命令行识别到键入的内容不是内置命令便会假设这是一个用户程序并加载此可执行文件。当完成输入后按照程序原本的设定,会在屏幕上输出10次字符串“hello world!”。

我们此时假定可执行目标文件hello在磁盘中,接下来将着重描述从加载到程序执行过程中数据通路,来对整个计算机系统做宏观的了解。在此之前需要大致了解计算机的主要组成模型,在此过程中将会忽略其中的细节。

Snip20160604_2
图3 系统硬件组成

中央处理器:处理器是解释指令的核心部分,处理器按功能可分为运算器和控制器两大部分,运算器主要接收控制信号并对数据进行加工处理,控制器负责协调全机的工作。处理器最核心的部分是程序计数器(PC),程序计数器的内容总是指向下一条将要执行的指令的地址,在使用虚拟存储器机制的机器上,程序计数器的内容是虚拟地址而非物理地址。一般情况下,计算机顺序执行指令,每当某条指令依据程序计数器中地址被取得同时,程序计数器中内容(地址)会加上刚刚取得的那条机器指令的长度,得到下一条机器指令的地址。但是,通常程序很少会一直顺序执行下去,这就涉及程序计数器内容的更新。在hello例子中,for循环会在每次输出字符串“hello world!”之后会跳到之前执行过的指令重新执行,而转跳的过程通常是跳转指令中包含所需跳转的地址和当前程序计数器内容的差值,通过加上这个偏移量完成对程序计数器值得修改,从而完成程序的跳转。通常涉及数值计算的的过程相对于本例中简单输出字符串要复杂得多,通常会涉及到加载覆盖、计算判断、确认跳转、写入内存等过程。

主存:任何程序需要被执行都需要先被加载到主存中。主存从逻辑上来说,能提供的地址空间是连续的线性地址空间,现在基本上编址基本以字节编址的,即主存地址每个基本单位都是一个字节。

总线:总线是连接主存、外设、磁盘和处理器的桥梁,通常我们在考虑计算机程序运行的数据流动中更关心系统总线。总线有两个最重要的参数就是总线宽度和总线频率,二者直接决定了计算机各个环节直接数据传输的速率。总线宽度目前通常是32位,如果每秒总线的传输频率是250Mhz,则总线传输速率是1000MB/s。

外设与磁盘:最常见的外设就是键盘鼠标显示器,是系统与外部世界相连接的部分。不过,外设并不是直接与总线相连,在外设和总线之间都有相关的而适配器用于协调速度、传递操作和同步信息、数据串并行转换等功能。磁盘相比于内存可以长期存储数据,所以可执行文件在载入内存前均存储在磁盘中,只有当需要加载时才从磁盘通过总线将数据送入内存。

有了上述对计算机组成部分的粗略介绍,接下来我们将通过之前hello程序的例子来逐步说明发生了什么。

Snip20160605_1
图4 从键盘读取hello到命令行

当键盘输入hello并敲下enter之后,通过外部中断通知处理器输入已经完成并读取USB接口中的hello字符,写入处理器中的寄存器中,再从寄存器中逐一将hello写入主存交给shell命令作为参数。当shell确认这是外部程序之后,开始将存储在磁盘上的hello可执行文件调入内存。

在调入磁盘文件的过程中,若再通过上述中断的方式来完成,则处理器将会有大量时间用于等待I/O,严重影响系统效率。为了减轻传输数据时处理器的负担,将可执行文件hello从磁盘传入内存的过程常通过DMA方式传递到内存,使用DMA方式在磁盘和内存间传递数据,每次可以完成一个大数据块的传送过程,全程只有在请求DMA和结束处理时需要处理器参与。正因如此,但凡涉及DMA传送的地方都需要专门的DMA控制器参与其中,而DMA控制器和处理器可以并行工作。即DMA控制器在将磁盘上hello文件直接传输到内存的同时处理器也可以执行其他任务,二者在时间是并行。

Snip20160605_2
图5 DMA方式加载文件到主存

 

Advertisements