[编程语言][汇编语言]计算机与汇编语言

来源:互联网 发布:linux 查看ftp用户 编辑:程序博客网 时间:2024/05/16 08:57

一、什么是计算机 ?


简单的说计算机是这样一种机器:当我们把一些信息输入到计算机中后,它经过计算后告诉我们信息处理的结果。计算机能够完成很多任务,甚至有些任务是极其复杂的。但计算机在完成这些任务的时候,最终其实是做数值的计算。所以我们叫这种机器为“计算”机。计算机是为了方便人类而产生的,理论上来说计算机能完成的任务人类也是能够完成的。所以,计算机很多方面其实和人类很像。

1、CPU、内存与硬盘

我们去电脑城买电脑的时候,卖场的工作人员通常会介绍一台电脑的配置如何。笔者的笔记本就是i7CPU、8G内存以及1TB硬盘的。什么是CPU?什么是内存?还有,什么是硬盘?这些问题可能对一个熟悉电脑的人来说小菜一碟,但对某些刚刚接触电脑的人来说还是比较困难的。下面我们打开电脑的外壳,看看这些东西到底是什么样子的。

CPU其实是个不到半个手掌大小的方块,它的周围有一些引脚。人们所说的内存其实是一个长方形的条形电路板,也就是内存条。而硬盘是一个铁盒子,我们无法打开这个铁盒子深入地了解硬盘的内部结构。

以上是我们感官的认识,下面我们来了解它们的功能。

CPU(Central Processing Unit,中文名称是中央处理器)是我们计算机中最为重要的一个部件。它完成绝大多数的运算,好比是我们的大脑。

内存(Memory)也被称为内存储器,其作用是用于暂时存放CPU中的运算数据。内存就像是我们手头的草稿纸。我们的大脑不能计算过于复杂的计算,否则就需要借助草稿纸来暂时记下中间的计算结果。CPU其实和我们的大脑是一样的,只能计算一定范围内的运算。对于稍稍复杂一些运算,CPU就需要暂时把中间结果保存在内存中。

硬盘(Hard Disk)主要用于保存数据。我们借助草稿纸得到复杂运算的结果后,为了便于老师批阅,通常是把运算结果填写的练习本上的。CPU可以把运算结果保存在其内部,当然也可以保存在内存中。由于电子硬件的特性,计算机断电以后CPU和内存中保存的数据会完全丢失。硬盘的电子硬件特性决定了它即便是断电,也不会丢失数据。这样我们就可以把运算结果保存在硬盘上。

2、寄存器

什么!寄存器又是个什么东西?

我们的大脑在计算的时候,其实计算发生在大脑内部某个空间的。我们可以通过平时的训练来扩充这个计算空间,这样我们就可以计算更加复杂的运算。说到底,我们的大脑内部也有一个类似草稿纸的空间。对于1+2这种简单运算,我们的大脑完全可以马上得到结果。对于17×8这样的运算,我们也可以比较容易地得到结果:首先计算7×8得到56,然后计算1×8的到8,然后再把之前运算结果的进位5和这次得到的8相加得到13,最终的结果就是136。为了得到了136这个最终结果,我们的大脑产生了56、8和13这些中间结果,而这些中间结果其实是保存在我们大脑内部的“草稿纸”中的。看似还比较容易,但如果我们想计算1234×5678呢?除了个别人,如果不凭借手边的草稿纸,要得到正确结果恐怕就不是那么容易了。

我们的大脑如此,计算机又是怎样的呢?一些高手口中说的“32位计算机”、“64位计算机”,我们可以理解为一台计算机一次运算能够处理的数值的最大长度是32位或是64位的。在这里,我们先不探究32位或64位是怎样的一个长度,只需要了解计算机的运算能力不是万能的,超过一定的范围计算机就无能为力了。计算机的运算功能是由CPU来完成的,所以CPU的运算能力就是计算机的运算能力。在计算机内部也是有“草稿纸”的,这些“草稿纸”就是寄存器(register)。寄存器和内存一样,也是可以保存数值,但是寄存器能够存储数据的能力远远小于内存。

组成寄存器和内存的电子元件是不相同的。寄存器的速度比较快,但制造成本比较高;内存的速度比较慢,但制造成本比较低。如果我们只使用内存,那么速度更快的CPU大部分时间是在等待内存;如果我们只使用寄存器,那么一台电脑将不是我们这些普通人能够买得起的。为了获得一定的运算速度,也为了能够降低成本。工程师决定在CPU内部内置一些寄存器来完成CPU的运算功能,同时CPU也需要在内存中保存其运算结果。

在这里我们总结一下:计算机的运算功能是依靠CPU完成的;在运算过程中要CPU要用到其内部的寄存器;而当寄存器无法满足计算的需要时,我们可以把中间结果暂时保存在内存中;当计算完成并且需要永久保存其结果时,我们就把它们保存在硬盘里。

二、机器语言与汇编语言


1、机器语言

我们在学习英语之前,我们听不懂美国人到底说了写什么。同样的,我们计算机也是不能理解“1+2=?”这种极其简单的计算问题。计算机只能理解机器语言,也就是二进制指令,二进制就是计算机世界中的通用语言。“1011 1000 0000 0001 0000 0000 1000 0011 1100 0000 0000 0010”这段二进制指令的含义就是让CPU计算1+2的运算。如果这么多的1和0中写错一个数字,就可能变成另外的含义了。为了方便阅读,我们可以把这段二进制指令以16进制数表示:“B80100 83C002”。可是即便如此,机器语言对我们来说还是很难阅读。

2、汇编语言

我们可以理解汉语,可以理解数学算式。通过学习,我们还可以理解英语、法语。当然,通过学习以及自身超强的记忆力,我们也能够理解机器语言。但这就背离了我们发明计算机的初衷,为了方便运算,我们竟然要花巨大的时间和精力来学习以及记忆一门世界上最难懂的语言。

世界上谁会这么做呢?但不这么做又该如何让计算机明白我们的意图呢?聪明的前辈们发明了汇编语言(assembly language)这种中间语言。借助于汇编语言,我们可以很容易地将自己的意图转换为汇编语言。而汇编语言和机器语言一一对应的关系,又决定了它们之间可以非常容易地进行转换。用汇编语言表达之前机器语言就是:

    mov $1, %ax    add $2, %ax

嗯,有点意思了。mov很像move,add是加法运算的英文单词。只是为什么在1和2的前面有个美元符号$?%ax又是个什么东西?未知的东西好像有些多了,但别着急,这些疑问我们会一一谈到了。

前面的代码片段中的mov、add是助记符(mnemonic),%ax是寄存器。每一行为一条指令。第一条指令的意思是把1搬进(move)ax寄存器,第二条指令的意思是把ax寄存器的数值加上(add)2。以后我们会发现,“B80100”其实就是mov $1, %ax,而”83C002“就是add $2, %ax。看看,有了汇编语言做翻译,我们是不是很容易就让计算机知道我们到底想让它做些什么事情了。

汇编语言就是把机器指令用人们能比较理解的简短的助记符表示出来的语言。这里要提一下的是,汇编语言虽然与机器语言一一对应,但却有多种不同的表达格式。最常用的汇编语言格式为“Intel汇编格式”和“AT&T汇编格式”,而上面的代码片段就是“AT&T汇编格式”的。不过它们之间的差异很小,也很容易掌握,而且它们也很容易相互转换,完全不会影响到我们学习汇编语言本身。

三、第一个汇编程序


前面说了这么多文科知识,我们已经很枯燥了。下面就开始我们自己的第一个汇编程序。不过,我们首先需要知道得到最终的成品之前,我们都需要做哪些事情。

第一步,我们在脑海里把自己的意图转换成一条条的汇编指令后,就可以通过键盘输入到计算机里并且保存为一个文本文件。这个文本文件我们称为源代码文件(source file),简称源文件。其扩展名为“s”,比如“boot.s”文件。源文件就是我们起点。

第二步,我们通过汇编器(assembler)把源代码文件转换成目标文件(object file)。此时,我们得到的目标文件是一个包含二进制指令序列的文件,而这些二进制指令序列就是计算机能够理解的机器语言。这个过程称为汇编。每一个源文件汇编后会得到一个目标文件,当然N个源文件汇编后得到的就是N个目标文件。也就是说,汇编的过程是以源文件为单位进行的,源文件与目标文件是一一对应的。虽然如此,我们的工作还没有结束。

第三步,我们需要通过把链接器(linker)把目标文件链接成在相应环境下可以执行的文件,即可执行文件(executable file)。这个过程我们称为链接。如果有多个目标文件,我们可以把它们链接成一个可执行文件。也就是说,在链接过程中,目标文件与可执行文件是多对一的关系。好了,到这里,我们已经得到了我们想要的成品了。

以上这三步是我们必须完成的。当然了,肯定有朋友会问到:“第二部得到的不是已经是机器语言了吗,为什么还需要第三步呢?”说的没错,第二部中的目标文件确实已经包含机器语言了。不过,对于我们练习的小程序,用一个源文件就可以了。但是,如果一个大型项目,甚至是Linux这样的“巨型”项目,就需要分成多个模块,分别进行编辑、汇编,然后再链接成一个可执行文件。另外,目标文件缺少程序加载所必须的信息,而这些信息必须由链接器补充完整。

好像又说了一堆枯燥的文科知识,不过一旦了解了上面的这些内容将有助于我们完成自己的第一个汇编程序。为了降低难度,尽可能少地讲解新知识,我们来编写一个Linux下的“hello, world!”汇编程序。

1、编辑源文件

将下面的源代码编辑在一个文本文件中,并且文件名保存为“hello.s”。

/* * File:        ch1/hello.s * Author:      HuoYun * Created on:  2015/11/30 15:15:47 * Modified on: 2015/11/30 15:15:47 * Describe: *      本程序为了演示汇编以及链接的过程。 * Compile command: *      as hello.s -o hello.o *      ld hello.o -o hello * Usage:        hello */    .section .text    .global _start_start:    # 调用系统调用,在屏幕上显示信息。    mov $4, %eax    mov $1, %ebx    mov $message, %ecx    mov $len, %edx    int $0x80    # 调用系统调用,退出程序。    mov $1, %eax    mov $0, %ebx    int $0x80    .section .datamessage:    .ascii "hello, world!\n"    len = .-message

我们稍后来讲述如何编辑这个汇编源代码。

2、汇编

我们进入保存源代码文件的目录,然后执行下面的命令:

$ as hello.s -o hello.o

如果我们编辑的源代码没有问题,就会得到boot.o这个目标文件。但如果我们在编辑的过程中不小心输错了一个地方,汇编器可能会提示错误。这个时候就需要我们仔细地检查并且更正了。

3、链接

我们继续执行下面的命令:

$ ld hello.o -o hello

这个时候就会的到boot这个可执行文件。当然如果由于某种原因,链接器提示发生了错误,同样的,我们依然要查找并更正错误。

4、执行

当我们正确地得到了可执行文件boot后,我们就可以执行下面的命令来运行我们的第一个汇编程序了:

$ ./hello

如果得到下面的运行结果,说明我们之前的步骤都没有错误。以后我们编译程序时,多数情况下都会使用之前的步骤。所以,以后我们不再详细讲述每个程序的编译过程,而是将编译命令记录在源文件的注释中。

hello, world!

至此,是否有些兴奋和成就感呢?编写程序代码是个枯燥的过程。有的时候我们洋洋洒洒地写了成百上千行代码,可就是无法编译通过,或者编译通过了却没有得到我们预期的效果。这时,我们只能慢慢地寻找bug。找到了一个bug,但发现还有另外的bug。是的,我们就是在编辑、找错、再编辑、再找错这种多次重复的过程中完善程序的,最终我们得到的是一个我们期望的程序。编写程序是乏味的,产生的错误是让人厌烦的。没有一个人一开始就能写出正确无误的代码,大量的编程会降低自己的失误率。学习编程,最忌讳的就是眼高手低,因为事情经常不是你想象的那样。书中剩余的代码示例均已在Ubuntu linux 15.04操作系统下编译完成,我们尽可能地保证代码的正确性,让大家能够得到一样的结果。看到这里,如果你仅仅只是通过之前的描述直到了“hello, world!”汇编程序的允许结果,那请你亲自动手敲敲键盘,看看是否能够得到和我一样的结果。

“纸上得来终觉浅,绝知此事要躬行。”我以这一句作为本章的结束,再一次地说明亲自编写代码的重要性。

0 0
原创粉丝点击