程序的开发和执行涉及计算机系统的各个不同层面,因而计算机系统层次结构的思想体现在程序开发和执行过程的各个环节。下面以简单的hello程序为例,简要介绍程序的开发与执行过程,以便加深读者对计算机系统层次结构概念的认识。
编写程序并让其在计算机上执行是为了解决最终用户的应用问题,因而,程序有时也被称为用户程序(User Program)或应用程序(Application Program)。
Hello World的基础知识
扫描上方二维码可观看知识点讲解视频
以下是hello.c的C语言源程序代码。
为了让计算机能执行上述应用程序,程序员应按照以下步骤进行处理。
1)通过程序编辑软件得到hello.c文件。hello.c在计算机中以ASCII字符方式存放,如图1-2所示,图中给出了每个字符对应的ASCII码的十进制值,例如:第一个字节的值是35,代表字符#;第二个字节的值是105,代表字符i;最后一个字节的值是125,代表字符}。通常把用ASCII码字符或汉字字符表示的文件称为文本文件(Text File),源程序文件都是文本文件,是可显示和可读的。
图1-2 hello.c源程序文件在计算机中的存放方式
2)将hello.c进行预处理、编译、汇编和链接,最终生成可执行目标文件。例如,在UNIX系统中,可用GCC编译驱动程序进行处理,命令如下:
上述命令中,最前面的unix>为shell命令行解释器的命令行提示符,gcc为GCC编译驱动程序名,-o表示后面为输出文件名,hello.c为要处理的源程序。从hello.c源程序文件到可执行目标文件hello的转换过程如图1-3所示。
· 预处理阶段:预处理程序(cpp)对源程序中以字符“#”开头的命令进行处理,例如,将#include命令后面的.h文件内容嵌入源程序文件中。预处理程序的输出结果还是一个源程序文件,以.i为扩展名。
· 编译阶段:编译程序(ccl)对预处理后的源程序进行编译,生成一个汇编语言源程序文件,以.s为扩展名,例如,hello. s是一个汇编语言程序文件。因为汇编语言与具体的机器结构有关,所以,对同一台机器来说,不管使用哪种高级语言,编译转换后的输出结果使用的都是同一种汇编语言。
· 汇编阶段:汇编程序(as)对汇编语言源程序进行汇编,生成一个可重定位目标文件(Relocatable Object File),以.o为扩展名,例如,hello.o是一个可重定位目标文件。它是一种二进制文件(Binary File),因为其中的代码是机器指令,数据以及其他信息也都是用二进制表示的,所以它是不可读的,用文本方式打开的时候会显示乱码。
Hello World程序的存储与编译
扫描上方二维码可观看知识点讲解视频
· 链接阶段:链接程序(ld)将多个可重定位目标文件和标准库函数合并为一个可执行目标文件(Executable Object File),可执行目标文件可简称为可执行文件。本例中,链接程序将hello.o和标准库函数printf所在的可重定位目标模块printf.o进行合并,生成可执行文件hello。
图1-3 从hello.c源程序文件到可执行目标文件hello的转换过程
最终生成的可执行文件被保存在磁盘上,可以通过某种方式启动磁盘上的可执行文件运行。
对于一个存放在磁盘上的可执行文件,可以在操作系统提供的用户操作环境中,采用双击对应图标或在命令行中输入可执行文件名等方式来启动执行。在UNIX系统中,可以通过shell命令行解释器来执行一个可执行文件。例如,对于上述可执行文件hello,通过shell命令行解释器启动执行的结果如下:
Hello World程序的运行过程
扫描上方二维码可观看知识点讲解视频
shell命令行解释器会显示提示符unix>,告知用户它准备接收用户的输入,此时,用户可以在提示符后面输入需要执行的命令名,也可以输入一个可执行文件在磁盘上的路径名,例如,上述“./hello”就是可执行文件hello的路径名,其中“./”表示当前目录。在命令后用户需按下Enter键表示结束。图1-4显示了在计算机中启动和执行hello程序的整个过程。
如图1-4所示,shell程序会将用户从键盘输入的每个字符逐一读入CPU寄存器中(对应线①),然后再将其保存到主存储器中,在主存的缓冲区形成字符串“./hello”(对应线②)。等到接收到Enter按键时,shell将调出操作系统内核中相应的服务例程,由内核来加载磁盘上的可执行文件shell到存储器(对应线③)。内核加载完可执行文件中的代码及其所要处理的数据(这里是字符串“hello,world\n”)后,将hello第一条指令的地址送到程序计数器(Program Counter,PC)中,CPU永远都会将PC的内容作为将要执行的指令的地址,因此,处理器随后开始执行hello程序,它将加载到主存的字符串“hello,world\n”中的每一个字符从主存取到CPU的寄存器中(对应线④),然后将CPU寄存器中的字符送到显示器上显示出来(对应线⑤)。
图1-4 启动和执行hello程序的整个过程
从上述过程可以看出,一个用户程序被启动执行,必须依靠操作系统的支持,包括外壳程序和内核服务例程。例如,shell命令行解释器是操作系统外壳程序,它为用户提供了一个启动程序执行的环境,对用户从键盘输入的命令进行解释,并调出操作系统内核来加载用户程序(用户输入命令对应的程序)。显然,用来加载用户程序并使其从第一条指令开始执行的操作系统内核服务例程也是必不可少的。此外,在上述过程中,涉及键盘、磁盘和显示器等外部设备的操作,这些底层硬件是不能由用户程序直接访问的,此时,也需要操作系统内核服务例程的支持,例如,用户程序需要调用内核的read系统调用服务例程读取磁盘文件或调用内核的write系统调用服务例程把字符串写到显示器中等。
从图1-4可以看出,程序的执行过程就是数据在CPU、主存储器和I/O模块之间流动的过程,所有数据的流动都是通过总线、I/O桥接器等进行的。在总线上传输数据之前,需要先将其缓存在存储部件中,因此,除了主存储器本身是存储部件以外,在CPU、I/O桥接器、设备控制器中也有存放数据的缓冲存储部件,例如,CPU中的寄存器堆、设备控制器中的数据缓冲寄存器等。
每个可执行目标文件中都包含机器代码段,可执行文件的执行实际上是对应机器代码段的执行过程。机器代码段由一条一条的机器指令构成。指令(Instruction)是一串用0和1表示的序列,用来指示CPU完成一个特定的原子操作,例如,取数指令(Load Instruction)从存储单元中取出一个数据存放到CPU寄存器中,存数指令(Store Instruction)将CPU寄存器中的内容写入一个存储单元,ALU指令(ALU Instruction)将两个寄存器中的内容进行某种算术或逻辑运算后再将结果送入一个CPU寄存器中,输出指令(Output Instruction)将一个CPU寄存器的内容送到I/O模块的某个缓存器中,等等。
可以看出,上述hello程序的执行过程中,需要通过取数指令将字符串“hello,world\n”中的每个字符从存储器送到CPU寄存器中,然后,再通过输出指令将其从CPU寄存器送到显示适配器(也称显示控制器)中。
指令通常被划分为若干个字段,包括操作码字段、地址码字段和立即数字段等。操作码字段指出指令的操作类型,如加、减、传送、跳转等;地址码字段指出指令所处理的操作数的地址,如寄存器编号、内存单元地址等;立即数字段指出具体的一个操作数或偏移地址等。
图1-5给出了实现两个相邻数组元素交换功能的不同层次语言的描述。在高级语言源程序中,可直观地用三个赋值语句实现;在经编译后生成的汇编语言源程序中,可用4个汇编指令表示,其中,两条指令是取数指令1w(Load Word),后两条指令是存数指令sw(Store Word);在经汇编后生成的机器语言程序中,对应的机器指令是特定格式的二进制代码,例如,第一条lw指令对应的机器代码为“1000 1100 0100 1111 0000 0000 0000 0000”,这是一条MIPS体系结构中的指令,其中,高6位“100011”为操作码,随后5位“00010”为寄存器编号2,再后面5位“01111”为另一个寄存器编号,最后16位表示立即数0。CPU能够通过逻辑电路直接执行这种二进制表示的机器指令。指令执行时通过控制器对指令操作码进行译码,解释成控制信号(Control Signal)控制数据通路执行,例如,控制信号ALUop=add可以控制ALU进行加法操作,RegWr=1可以控制将结果写入寄存器。
图1-5 不同层次语言之间的等价转换
每条指令的执行过程包括:从存储器取指令并计算下一条指令的地址、对指令进行译码、取操作数、对操作数进行运算、将运算结果保存到存储器或寄存器。每次从存储器取指令都是将PC的值作为指令的地址,因此,计算出的下一条要执行指令的地址被送到PC,当前指令执行完后,根据PC的值到存储器中取下一条指令,从而能够周而复始地执行程序中的每条指令。指令的执行由时钟信号(Clock Signal)进行定时,一条指令的执行可能需要一个或多个时钟的时间。