汇编语言
1.1机器语言
机器语言是机器指令的集合,机器指令是一台机器可以执行的命令。
电子计算机的机器指令是一列二进制数字,计算机将其转变为一系列高低电平。
早期计算机可以执行机器指令并进行运算,现在我们常用的PC机,用芯片完成这个功能。
这个芯片就是CPU(central processing unit,中央处理单元),CPU是一种微处理器。
每一种微处理器,由于硬件设计和内部结构的不同,就需要用不同的电平脉冲来控制,使它工作。所以每一种微处理器都有自己的机器语言。
不同种的cpu有不同的机器指令,不同的机器指令是不同的二进制数字,不同的二进制数字是不同的高低电平
早期程序设计均使用机器语言。
应用8086CPU完成运算s=768+12288-1280,机器码如下:
101110000000000000000011
000001010000000000110000
001011010000000000000101
假如程序写错了很难发现
1.2汇编语言的产生
因为机器语言太麻烦,难以辨别和记忆,于是汇编语言诞生了
汇编语言主体是汇编指令。
汇编指令和机器指令的差别在于指令的表达方式。汇编指令是机器指令便于记忆的书写格式。
机器指令1000100111011000 表示把寄存器BX的内容送到AX中。汇编指令则写成mov ax,bx。
这样的写法与人类语言接近,便于阅读和记忆。
操作:寄存器BX的内容送到AX中
机器指令: 1000100111011000
汇编指令: mov ax,bx
寄存器,是CPU中一个可以存储数据的器件,一个CPU中有多个寄存器。AX,BX分别是两个寄存器的代号
人能看懂汇编指令,但是机器只能读懂机器指令,如何让计算机执行汇编指令呢?
这就需要一个将汇编指令转换成机器指令的翻译程序了,我们称其为编译器。
程序员用汇编语言写源程序,再用汇编编译器将其编译为机器码,由计算机最终执行。
1.3汇编语言的组成
汇编语言有三类指令组成:
-
汇编指令:机器码的助记符,有对应的机器码
-
伪指令:没有对应的机器码,由编译器执行,计算机并不执行
-
其他符号:如+、-、*、/等,由编译器识别,没有对应的机器码
汇编语言的核心是汇编指令,它决定了汇编语言的特性。
1.4存储器
CPU是计算机的核心部件,它控制整个计算器的运作并进行运算。
要想让一个CPU工作,就必须向它提供指令和数据。
指令和数据在存储器中存放,也就是我们平时所说的内存
在一台PC机中,内存的作用仅次于CPU。离开了内存,性能再好的CPU也无法工作。这就像再聪明的大脑,没有了记忆也无法进行思考。
磁盘不同于内存,磁盘上的数据或程序如果不读到内存中,就无法被CPU使用。
要灵活地利用汇编语言编程,我们首先要了解CPU是如何从内存读取信息,以及向内存中写入信息的。
1.5指令和数据
指令和数据是应用上的概念。
在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。
CPU在工作的时候把有的信息看作指令,把有的信息看作数据。为同样的信息赋予了不同的意义。
就像围棋的棋子,在棋盒的时候没有任何区别,在对弈的时候就有了不同的意义。
例如,内存中的二进制信息1000100111011000,计算机可以把它看作大小为89D8H的数据来处理,也可以看作指令mov,ax,bx来执行
1000100111011000 —>89D8H(数据)
1000100111011000 —>mov,ax,bx(程序)
1.6 存储单元
存储器被划分成若干个存储单元,每个存储单元从0开始顺序编号
例如一个存储器有128个存储单元,编号从0~127,如图
那么一个存储单元能存储多少信息呢?
我们知道电子计算机的最小信息单位是bit,也就是一个二进制位。
8bit=1Byte(字节)。
微型机存储器的存储单元可以存储一个Byte,即8个二进制位。
一个存储器有128个存储单元,他可以存储128Byte。
微型存储器的容量以字节为最小值单位来计算。对于拥有128个存储单元的存储器,我们可以说,它的容量是128个字节。
对于大容量的存储器一般还用以下单位来计算容量(以下用B来代表Byte):
1KB=1024B 1MB=1024KB 1GB=1024MB 1TB=1024GB
磁盘的容量单位同内存一样,实际上以上单位是微机中常用的计量单位。
1.7 CPU对存储器的读写
以上讲到,存储器被划分成多个存储单元,存储单元从0开始顺序编号。
CPU要从内存中读数据,首先要指定存储单元的地址,也就是说它要先确定读取哪一个存储单元中的数据。
另外,在一台微机中,不只有存储器这一种器件。CPU在读写数据时还要指明
他要对哪一个期间进行操作,进行什么操作,是读数据还是写数据。
CPU要想进行数据的读写,必须和外部器件(准确来说是芯片)进行三类信息的交互
- 存储单元的地址**(地址信息)**
- 器件的选择,读或写命令**(控制信息)**
- 读或写的数据**(数据信息)**
那么CPU通过什么将这三类信息传到存储器芯片中呢?
答案是:总线
电子信息传输处理的都是电信号,电信号用导线传送。
总线就是计算机中专门连接CPU和其他芯片的导线的集合。
根据传送信息的不同,总线分为三类:
- 地址总线
- 控制总线
- 数据总线
过程是这样的:
地址线传输要对第几号存储单元操作,控制线传输读或写命令,数据线传输读或写要用的数据(读数据来自存储单元,写数据来自CPU)。
上边说了CPU是如何进行数据读写的,下边说说如何命令机器进行数据的读写。
1.8 地址总线
CPU是通过地址总线来指定存储器单元的。
可见地址总线上能传送多少个不同的信息,CPU就可以对多少个存储单元进行寻址。
现假设,一个CPU有10根地址总线,让我们来看一下它的寻址情况。我们知道,在电子计算机中,一根导线可以传送的稳定状态只有两种,高电平或是低电平。用二进制表示就是1或0,10 根导线可以传送10位二进制数据。而10位二进制数可以表示多少个不同的数据呢? 2的10次方个。最小数为0,最大数为1023。
图1.4 展示了一个 具有10根地址线的 CPU向内存发出地址信息 11时10根地址线上传送的二进制信息。
一个CPU有N根地址线,则可以说这个CPU的地址总线的宽度为N。这样的CPU最多可以寻找2的N次方个内存单元。
1.9 数据总线
CPU与内存或其他器件之间的数据传送是通过数据总线来进行的。
数据总线的宽度决定了CPU与外界的数据传送速度。
8根数据总线一次可传送一个8位二进制数据,16根数据总线一次可传送两个字节。
8086有16根数据线,可一次传送16位数据,所以可一次传送数据89D8H;而8088只有8根数据线,一次只能传8位数据,所以向内存写入数据89D8H时需要进行两次数据传送。
1.10 控制总线
CPU对外部期间的控制是通过控制总线来进行的。控制总线是一些不同控制线的集合。
总线是一些不同控制线的集合。有多少根控制总线,就意味着CPU提供了对外部器件的多少种控制。
所以,控制总线的宽度决定了CPU对外部器件的控制能力。
前面所讲的内存读或写命令是由几根控制线综合发出的,其中有一根称为“读信号输出”的控制线负责由CPU向外传送读信号,CPU向该控制线上输出低电平表示将要读取数据;有一根称为“写信号输出”的控制线则负责传送写信号。
1.1~1.10 小 结
(1)汇编指令是机器指令的助记符,同机器指令一一对应。
(2)每一种CPU都有自己的汇编指令集。
(3)CPU可以直接使用的信息在存储器中存放。
(4)在存储器中指令和数据没有任何区别,都是二进制信息。
(5)存储单元从零开始顺序编号。
(6)一个存储单元可以存储8个bit,即8位二进制数。
(7)
lByte = 8bit lKB= 1024B 1MB= 1024KB lGB= 1024MB
(8)每一个CPU芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。一个CPU可
以引出3种总线的宽度标志了这个CPU的不同方面的性能:地址总线的宽度决定了CPU的寻
址能力;数据总线的宽度决定了CPU与其他器件进行数据传送时的一次数据传送量;控制总线的宽
度决定了CPU对系统中其他器件的控制能力。
在汇编课程中,我们从功能的角度介绍了3类总线,对实际的连接情况不做讨论。
检测点1.1
(1) 1个CPU的寻址能力为8KB,那么它的地址总线的宽度为_13_
一根地址总线对应一个存储单元也就是1B。
8 K B = 2 13 B 8KB=2^{13}B 8KB=213B
对应13根地址总线。
(2) 1KB 的存储器有个1024存储单元。存储单元的编号从_0_到1023
1KB=1024B
一个存储单元1B
所以有1024个存储单元
编号从0开始,到1023
(3) 1KB的存储器可以存储8192个bit,1024个Byte。
(4) 1GB、1MB、1KB分别是 2 30 、 2 20 、 2 10 2^{30}、2^{20}、2^{10} 230、220、210Byte。
1KB=1024B 1MB=1024KB 1GB=1024MB 1TB=1024GB
(5) 8080、 8088、80286、 80386 的地址总线宽度分别为16根、20根、24根、32根,则它们的寻址能力分别为:64(KB)、1(MB)、16(MB)、4(GB)。
16 根 — — > 2 16 B = 2 6 K B = 64 K B 16根——>2^{16}B=2^6KB=64KB 16根——>216B=26KB=64KB
20 根 — — > 2 20 B = 1 M B 20根——>2^{20}B=1MB 20根——>220B=1MB
24 根 — — > 2 24 B = 2 4 M B = 16 M B 24根——>2^{24}B=2^4MB=16MB 24根——>224B=24MB=16MB
32 根 — — > 2 3 2 B = 2 2 G B = 4 G B 32根——>2^32B=2^2GB=4GB 32根——>232B=22GB=4GB
(6) 8080、 8088、 8086、 80286、80386的数据总线宽度分别为8根、8根、16根、16根、32根。则它们一次可以传送的数据为:1(B)、1(B)、2(B)、2(B)、4(B)。
首先注意到题目问的是数据总线,一根数据总线一次传送一位二进制数。
1B=8b
8根——>8b=1B
8根——>8b=1B
16根——>16b=2B
16根——>16b=2B
32根——>32b=4B
(7)从内存中读取1024字节的数据,8086至少要读512次,80386至少要读256次。
从内存中读取数据,用的是数据总线。
1024 B = 2 10 B = 1024 B 1024B=2^{10}B=1024B 1024B=210B=1024B
8086一次可以读2B,1024/2=512次
80386一次可以读4B,1024/4=256次
(8) 在存储器中,数据和程序以二进制形式存放。
1.1~1.10小结 (4)
在存储器中指令和数据没有任何区别,都是二进制信息。
1.11 内存地址空间(概述)
什么是内存地址空间呢?举例来讲,一个CPU的地址总线宽度为10, 那么可以寻址1024个内存单元,这1024个可寻到的内存单元就构成这个CPU的内存地址空间。下面进行深入讨论。首先需要介绍两部分基本知识,主板和接口卡。
1.12 主板
在每一台PC机中,都有一个主板,主板上有核心器件和一些主要器件,这些器件通过总线(地址总线、数据总线、控制总线)相连。这些器件有CPU、存储器、外围芯片组、扩展插槽等。扩展插槽上一般插有RAM内存条和各类接口卡。
1.13 接口卡
计算机系统中,所有可用程序控制其工作的设备,必须受到CPU的控制。CPU对外部设备都不能直接控制,如显示器、音箱、打印机等。直接控制这些设备进行工作的是插在扩展插槽上的接口卡。扩展插槽通过总线和CPU相连,所以接口卡也过总线同CPU相连。CPU可以直接控制这些接口卡,从而实现CPU对外设的间接控制。简单地讲,就是CPU通过总线向接口卡发送命令,接口卡根据CPU的命令控制外设进行工作。
1.14 各类存储器芯片
一台PC机中,装有多个存储器芯片,这些存储器芯片从物理连接上看是独立的、不同的器件。
这些存储器芯片从读写属性上看分为两类:随机存储器(RAM)和**只读存储器(ROM)**。
随机存储器可读可写,但必须带电存储,关机后存储的内容丢失;
只读存储器只能读取不能写入,关机后其中的内容不丢失。
这些存储器从功能和连接上又可分为以下几类。
-
随机存储器
用于存放供CPU使用的绝大部分程序和数据,主随机存储器一般由两个位置上的RAM组成,装在主板上RAM和插在扩展插槽上的RAM。
-
**装有BIOS(**Basic Input/Output System,基本输入/输出系统)的ROM。
BIOS是由主板和各类接口卡(如显卡、网卡等)厂商提供的软件系
统,可以**通过它利用该硬件设备进行最基本的输入输出。**在主板和某些接口卡上插有存储相应BIOS的ROM。
例如,主板上的ROM中存储着主板的BIOS(通常称为系统BIOS);显卡上的ROM中存储着显卡的BIOS;如果网卡上装有ROM,那其中就可以存储网卡的BIOS。
-
接口卡上的RAM
某些接口卡需要对大批量输入、输出数据进行暂时存储,在其上装有RAM。
**最典型的是显示卡上的RAM,一般称为显存。显示卡随时将显存中的数据向显示器上输出。**换句话说,我们将需要显示的内容写入显存,就会出现在显示器上。
1.15 内存地址空间
上述的那些存储器,在物理上是独立的器件,但是在以下两点上相同。
- 都和CPU的总线相连。
- CPU对它们进行读或写的时候都通过控制线发出内存读写命令。
这也就是说,CPU在操控它们的时候,把它们都当作内存来对待,把它们总的看作一个由若干存储单元组成的逻辑存储器,这个逻辑
存储器就是我们所说的内存地址空间。
在汇编这门课中,我们所面对的是内存地址空间。
在图1.8中,所有的物理存储器被看作一一个由若干存储单元组成的逻辑存储器,每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间。CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据。
假设,图1.8中的内存地址空间的地址段分配如下。
地址0-7FFFH的32KB空间为主随机存储器的地址空间;
地址8000H- 9FFFH的8KB空间为显存地址空间;
地址A000H-FFFFH的24KB空间为各个ROM的地址空间。
这样,CPU向内存地址为1000H 的内存单元中写入数据,这个数据就被写入主随机存储器中;
CPU向内存地址为8000H 的内存单元中写入数据,这个数据就被写入显存中,然后会被显卡输出到显示器上;
CPU向内存地址为C000H的内存单元中写入数据的操作是没有结果的,C000H单元中的内容不会被改变,C00OH单元实际上就是ROM存储器中的一个单元。注意:ROM是只读存储器
内存地址空间的大小受CPU地址总线宽度的限制。
8086CPU的地址总线宽度为20,可以传送 2 20 2^{20} 220个不同的地址信息。(大小从0至 2 20 2^{20} 220-1)。即可以定位 2 20 2^{20} 220个内存单元,则8086PC的内存地址空间大小为1MB。
同理,80386CPU的地址总线宽度为32,则内存地址空间最大为4GB。
我们在基于一个计算机硬件系统编程的时候,必须知道这个系统中的内存地址空间分配情况。
因为当我们想在某类存储器中读写数据的时候,必须知道它的第一个单元的地址和最后一个单元的地址,在能保证读写操作时在于其的存储器中进行。
比如,我们希望像显示器输出一段信息,那么必须将这段信息写到显存中,显卡才能将它输出到显示器上。
要向显存中写入数据,必须知道显存在内存地址空间中的地址。
图1.9告诉我们,从地址0~9FFFF的内存单元中读取数据,实际上就是在读取主随机存储器中的数据;
向地址A0000~ BFFFF的内存单元中写数据,就是向显存中写入数据,这些数据会被显示卡输出到显示器上;
我们向地址C0000 FFFF的内存单元中写入数据的操作是无效的,因为这等于改写只读存储器中的内容。
内存地址空间
最终运行程序的是CPU,我们用汇编语言编程的时候,必须要从CPU的角度考虑问题。 对CPU来讲,系统中的所有存储器中的存储单元都处于一个统一 的逻辑存储器中,它的容量受CPU寻址能力的限制。
这个逻辑存储器即是我们所说的内存地址空间。
对于初学者,这个概念比较抽象,我们在后续的课程中将通过一些编程实践, 来增加感性认识。
第二章 寄存器
内部总线实现CPU内部各个器件之间的联系,外部总线实现CPU和主板上其它器件的联系。
简单地说,在CPU中:
- 运算器进行信息处理
- 寄存器进行信息存储
- 控制器控制各种器件进行工作
- 内部总线连接各种器件,在它们之间进行数据的传送
寄存器是CPU中程序员可以用指令读写的部件。程序员通过改变各种寄存器中的内容来实现对CPU的控制。
不同的CPU,寄存器的个数,结构不相同
2.1 通用寄存器
8086CPU有14个寄存器,都是16位的,可以存放两个字节。寄存器名称分别是:AX、BX、CX、DX、SI、DI SP、BP、IP CS、SS、DS、ES、PSW。
AX、BX、CX、DX这四个寄存器通常用来存放一般性数据,被称为通用寄存器。
想一想,一个16位寄存器所能存储的数据的最大值是多少?
答:数据范围从0x00-0xFFFF,所以最大为65535
8086CPU上一代CPU中的寄存器是8位的,为了兼容,8086CPU中的AX、BX、CX、DX这四个寄存器都可分为两个可独立使用的8位寄存器来用:
AX–>AH和AL; (H=High L=Low)
BX–>BH和BL;
CX–>CH和CL;
DX–>DH和DL;
想一想,一个8位寄存器所能存储的数据的最大值为多少?
答:0~255
2.2 字在寄存器中的存储
出于对兼容性的考虑,8086CPU可以一次性处理以下两种尺寸的数据。
- 字节:记为byte,1byte=8bit,可以存在8位寄存器中。
- 字:记为word,1 word=2byte,一个字由两个字节组成,这两个字节分别称为高位字节和低位字节。
一个字可以存在一个16位寄存器中,这个字的高位字节和低位字节分别占据这个寄存器的高八位寄存器和低八位寄存器。图2.4,一个字型数据20000, 存在AX寄存器中,在AH中存储了它的高8位,在AL中存储了它的低8位。AH和AL中的数据,既可以看成是一个字型数据的高8位和低8位,这个字型数据的大小是20000;又可以看成是两个独立的字节型数据,它们的大小分别是78和32。
十六进制数据后边加H,二进制后边加H,十进制后边什么也不加。
2.3几条汇编指令
在写一条汇编指令或一个寄存器的名称是不区分大小写,如bx和BX含义相同。
接下来看一下CPU执行表2.2中所列的程序段中的每条指令后,对寄存器中的数据进行的改变。
问题 2.1 指令执行后的AX是?
8226H+8226H=1044C
AX为16位寄存器,从右向左取舍
舍去最高位1 ,所以执行指令后的AX是044CH
问题 2.2 指令执行后AX中的数据为多少?
执行add al,93H指令时 AX=00C5H
al指低8位,运算时取AX的C5H, 93H+C5H=158H
al只能存放两位16进制数据,所以高位1舍去(非彻底丢弃),执行指令后的AX=0058H
注意,此时al是作为一个独立的8位寄存器来使用的,和ah没有关系,CPU在执行这条指令时认为ah和al是两个不相关的寄存器。不要错误地认为,诸如add al,93H的指令产生的进位会存储在ah中,add al,93H进行的是8位运算。
在进行数据传送或运算时,要注意指令的两个操作对象的位数应当是一致的,例如:
movaX,bx
movbx,CX
mov ax, 1 8H
mov al, 1 8H
add ax, bx
add ax, 20000
等都是正确的指令,而:
mov ax,bl(在8位寄存器和16位寄存器之间传送数据)
mov bh, ax(在16位寄存器和8位寄存器之间传送数据)
mov al, 20000(8位寄存器最大可存放值为255的数据)
add al, 100H(将一个高于8位的数据加到一个8位寄存器中)
等都是错误的指令,错误的原因都是指令的两个操作对象的位数不一-致。
检测点2.1
(1) 写出每条汇编指令执行后相关寄存器中的值。
mov ax, 62627 AX=F4A3H
62627是十进制,转换成十六进制F4A3H
mov ah, 31H AX=31A3H
mov al, 23H AX=3123H
add ax, ax AX=6246H
mov bx, 826CH BX=826CH
mov CX, ax CX=6246H
mov ax, bx AX= 826CH
add ax, bx AX=04D8H
mov al,bh AX=0482H
mov ah,bl AX=6C82H
add ah, ah AX=D882H
add al, 6 AX=D888H
add al, al AX=D810H
mov ax, CX AX= =6246H
(2)只能使用目前学过的汇编指令,最多使用四条指令,编程计算2的4次方。
mov ax,2;
add ax ax;
add ax ax;
add ax ax;
这道题仅仅针对2的次方,如果是其他数的次方只是用add和mov指令是计算不出来的,例如 3 4 3^4 34
mov ax,3;
add ax ax;
add ax ax;
add ax ax;
2.4 物理地址
我们知道,CPU访问内存单元时,要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间,每个内存单元在这个空间里有唯一的地址,我们把这个地址称为物理地址。
CPU通过地址总线送入存储器的,必须是一个内存单元的物理地址。在CPU向地址总线发出物理地址之前,必须要在内部先形成这个物理地址。
不同的CPU可以有不同的形成物理地址的方式。
现在讨论8086CPU是如何在内部形成内存单元的物理地址的。
2.5 16位结构的CPU
我们说8086CPU的上一代CPU(8080,8085)等是8位机,而8086是16位机,也可以说8086是16位结构的CPU。
那么什么是16位结构的CPU呢?
概括的讲,16位结构(也称16位机,字长为16位)描述了一个CPU具有下面几方面的结构特征:
- 运算器一次最多可以处理16位的数据
- 寄存器的最大宽度为16位
- 寄存器和运算器之间的通路为16位
内存单元的地址在送上地址总线之前,必须在CPU中处理、传输、暂时存储,对于16位结构的CPU,能一次性处理、传输、暂时存储16位的地址。
2.6 8086CPU给出物理地址的方法
8086CPU有20位地址总线,可以传送20位地址,达到1MB寻址能力。
8086CPU又是16位结构,在内部一次性处理、传输、暂时存储的地址为16位。
从8086CPU的内部结构来看,如果将地址从内部简单地出发,那么它只能送出16位的地址,表现出的寻址能力只有64KB。
8086CPU采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址。
8086CPU相关部件的逻辑结构如图2.6所示。
如图2.6所示,当8086CPU要读写内存时:
(1)CPU中的相关部件提供两个16位的地址,一个称为段地址,另-个称为偏移地址;
(2)段地址和偏移地址通过内部总线送入一个称为地址加法器的部件;
(3)地址加法器将两个16 位地址合成为一个 20位的物理地址;
(4)地址加法器通过内部总线将20位物理地址送入输入输出控制电路;
(5)输入输出控制电路将20位物理地址送上地址总线;
(6)20位物理地址被地址总线传送到存储器。
地址加法器采用物理地址=段地址x16+偏移地址的方法用段地址和偏移地址合成物理地址。
例如,8086CPU要访问地址为123C8H的内存单元。
此时,地址加法器的工作过程如图2.7所示(图中数据皆为十六进制表示)。
2.7 “段地址×16+偏移地址=物理地址”的本质含义
”段地址×16+偏移地址=物理地址“的本质含义是:PUC在访问内存时,用一个基础地址(段地址×16)和一个相对于基础地址的偏移地址相加,给出内存单元的物理地址。
8086CPU中,段地址×16可看作是基础地址。
2.8 段的概念
我们注意到,“段地址”这个名称包含着“段”的概念。
其实,内存并没有分段,段的划分来自于CPU,由于8086CPU用“基础地址(段地址×16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式管理内存。
如图2.9 所示,我们可以认为:地址10000H~100FFH的内存单元组成一个段,该段的起始地址(基础地址)为10000H, 段地址为1000H, 大小为100H; 我们也可以认为地址10000H~1007FH、 10080H~ 100FFH的内存单元组成两个段,它们的起始地址(基础地址)为: 10000H和10080H, 段地址为: 1000H和1008H, 大小都为80H。
以后,在编程时可以根据需要,将若干地址连续的内存单元看作一个段,用段地址x16定位段的起始地址(基础地址),用偏移地址定位
段中的内存单元。
有两点需要注意:段地址x16必然是16的倍数,所以一个段的起始地址也一定是16的倍数;偏移地址为16位,16位地址的寻址能力为
64KB,所以一个段的长度最大为64KB。
结论: CPU可以用不同的段地址和偏移地址形成同一个物理地址。
比如CPU要访问21F60H单元,则它给出的段地址SA和偏移地址EA满足SAX 16+EA=21F60H即可。
比如给定段地址1000H,用偏移地址寻址,CPU的寻址范围为:10000H~1FFFFH
在8086PC机中,存储单元的地址用两个元素来描述,即段地址和偏移地址。
“数据在21F60H内存单元中。”
这句话对于8086PC机一般不这样讲,取而代之的是两种类似的说法:
①数据存在内存2000:1F60单元中;*
②数据存在内存的2000H段中的1F60H单元中。这两种描述都表示“数据在内存21F60H单元中”。
可以根据需要,将地址连续、起始地址为16的倍数的一组内存单元定义为一个段。
检测点 2.2
(1) 给定段地址为0001H,仅通过变化偏移地址寻址,CPU的寻址范围为10H到1000FH
(2)有一数据存放在内存20000H单元中,现给定段地址为SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为1001H最大为2000H
提示,反过来思考一下,当段地址给定为多少,CPU无论怎么变化偏移地址都无法寻到20000H单元?
公式:基础地址(段地址×16)+偏移地址=物理地址
解题思路:
最小的段地址意味着最小的基础地址
最小的基础地址=20000H-FFFFH=10001H,但是由于基础地址一定是16的倍数,故最小的基础地址是10010H
基础地址=段地址×16(这是十进制,换成十六进制是10H)
最小的段地址(10010H左移一位)=1001H
最大的段地址,就是以20000H为基础地址的段地址2000H
2.9 段寄存器
我们前边讲到,8086CPU在访问内存时要由相关部件提供内存单元的段地址和偏移地址,送入地址加法器合成物理地址。这时需要看一下是什么部件提供段地址。
段地址在8086CPU的段寄存器中存放。
8086CPU有4个段寄存器:CS,DS,SS,ES。
当8086CPU要访问内存时由这4个段寄存器提供内存单元的段地址。
本章只看一下CS。
2.10 CS和IP
CS和IP是8086CPU中最关键的寄存器,它们指示了CPU当前要读取指令的地址。
CS为代码段寄存器,IP为指令指针寄存器。
在8086PC机中,任意时刻,设CS中的内容为M,IP中的内容为N,8086CPU将从内容M×16+N单元开始,读取一条指令并执行。
也可以这样表述:8086机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。
图2.10展示了8086CPU读取,执行指令的工作原理(图中数字都为16进制)
图2.10说明如下。
(1) 8086CPU当前状态: CS中的内容为2000H, IP中的内容为0000H;
(2)内存20000H- 20009H单元存放着可执行的机器码;
(3)内存20000H~ 20009H单元中存放的机器码对应的汇编指令如下。
地址: 20000H ~20002H,内容: B8 2301,长度: 3Byte, 对应汇编指令: mov ax,0123H
地址: 20003H~ 20005H,内容: BB 0300,长度: 3Byte,对应汇编指令: mov bx,0003H
地址: 20006H~ 20007H,内容: 89 D8,长度: 2Byte, 对应汇编指令: mov ax,bx
地址: 20008H~20009H, 内容: 01 D8,长度: 2Byte, 对应汇.编指令: add ax,bx
下面的一组图(2.11~2.19),以图2.10描述的情况为初始状态,展示了8086CPU读取、执行一条指令的过程。注意每幅图中发生的变化。
下面的一组图(2.20~2.26),以图2.19的情况为初始状态,展示了8086CPU继续读取、执行3条指令的过程。注意IP的变化(下面的描述中,隐蔽了读取每条指令的细节)
通过上面的过程展示,8086CPU的工作过程可以简要描述如下:
- 从CS:IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
- IP=IP+所读取指令的长度,从而指向下一条指令;
- 执行指令。转到步骤1,重复过程。
在8086CPU加点启动或复位后(即CPU刚开始工作时)CS和IP被设置为CS=FFFFH,IP=0000H,即在8086PC机刚启动时,CPU从内存FFFF0H单元中读取指令执行,FFFF0H单元中的指令时8086PC机开机后执行的第一条指令。
CPU在工作的时候吧有的信息看作指令,有的信息看作数据。
问:CPU根据什么将内存中的信息看作指令?
**答:CPU将CS:IP指向的内存单元中的内容看作指令,因为在任何时候,CPU将CS、IP中的内容当作指令的段地址和偏移地址,用他们合成指令的物理地址,到内存中读取指令码,执行。**如果说,内存中的一段信息曾被CPU执行过的话,那么,他所在的内存单元必然被CS:IP指向过。
2.11 修改CS、IP的指令
在CPU中,程序员能够用指令读写的部件只有寄存器,程序员可以通过改变寄存器中的内容实现对CPU的控制。
CPU从何处执行指令是由CS、IP中的内容决定的,程序员可以通过改变CS、IP中的内容来控制CPU执行目标指令。
我们如何改变CS、IP的值呢?
显然,8086CPU 必须提供相应的指令。我们如何修改AX中的值?
可以用mov指令,如mov ax,123 将ax中的值设为123,
显然,我们也可以用同样的方法设置其他寄存器的值,如mov bx,123, mov cx,123, mov dx, 123 等。
其实,8086CPU大部分寄存器的值,都可以用mov指令来改变,mov指令被称为传送指令。
但是,mov指令不能用于设置CS、IP的值,原因很简单,因为8086CPU没有提供这样的功能。
8086CPU为CS、IP提供了另外的指令来改变它们的值。
能够改变CS、IP的内容的指令被统称为转移指令(我们以后会深入研究)。
现在介绍个最简单的可以修改CS、IP 的指令:jmp 指令。
若想同时修改CS、IP 的内容,可用形如 “jmp 段地址:偏移地址” 的指令完成,如
jmp 2AE3:3,执行后: CS=2AE3H,IP=0003H,CPU将从2AE33H处读取指令。
jmp 3:0B16,执行后: CS=0003H, IP=0B16H, CPU将从00B46H处读取指令。
“jmp段地址:偏移地址”指令的功能为:用指令中给出的段地址修改CS,偏移地址修改IP。
若想仅修改IP的内容,可用形如“jmp 某一合法寄存器”的指令完成,如
jmp ax,指令执行前: ax=1000H, CS= 2000H,IP=0003H
指令执行后: ax=1000H,CS= =2000H,IP=1000H
jmp bx,指令执行前: bx= 0B16H, CS= 2000H,IP=0003H
指令执行后: bx=0B16H,CS=2000H, IP=0B16H
“jmp 某一合法寄存器”指令的功能为:用寄存器中的值修改IP。
jmp ax,在含义上好似 : mov IP,ax。
问题 2.3
内存中存放的机器码和对应的汇编指令情况如图2.27所示,设CPU初始状态 : CS =2000H,IP = 000H,请写出指令执行序列。思考后看分析。
2.12 代码段
前面讲过,对于8086PC机,在编程时,可以根据需要,将一组内存单元定义为一个段。
我们可以将长度为N(N<=64KB)的一组代码,存在一组连续、起始地址为16倍数的内存单元中,我们可以任务,这段内存是用来存放代码的,从而定义一个代码段。
比如,将:
mov ax, 0000 (B8 00 00)
add ax, 0123H (05 23 01)
mov bx, ax (8B D8)
jmp bx (FF E3)
这段长度为10个字节的指令,存放在123B0H123B9H的一组内存单元中,我们可以认为,123B0H123B9H这段
内存是用来存放代码的,是一个代码段。段地址为123BH,长度为10个字节。
如何使得代码段中的指令被执行呢?将一段内存当作代码段,仅仅是我们在编程时的一种安排,CPU并不会由于这
种安排,就自动地将我们定义的代码段中的指令当作指令来执行。
CPU只认被CS:IP 指向的内存单元中的内容为指令。所以,要让CPU执行我们放在代码段中的指令,必须要将CS:IP
指向所定义的代码段中的第一条指 令的首地址。对于上面的例子,我们将一段代码存放在123B0H~123B9H 内存
单元中,将其定义为代码段,如果要让这段代码得到执行,可设CS=123BH、 IP=0000H。
2.9~2.12小结
(1)段地址在8086CPU的段寄存器中存放。当8086CPU要访问内存时,由段寄存器提供内存单元的段地址。
8086CPU有4个段寄存器,其中CS用来存放指令的段地址。
(2)CS存放指令的段地址,IP存放指令的偏移地址。8086机中,任意时刻,CPU将CS:IP指向的内容当作指令执行。
(3) 8086CPU的工作过程:
①从CS: IP指向的内存单元读取指令,读取的指令进入指令缓冲器;
②IP指向下一条指令;
③执行指令。(转到步骤①,重复这个过程。)
(4) 8086CPU提供转移指令修改CS、IP 的内容。
检测点2.3
下面的3条指令执行后,CPU几次修改IP?都是在什么时候?最后IP中的值是多少?
mov ax,bx
sub ax,ax
jmp ax
sub ax,ax相当于对ax清零。这种减法操作比直接用mov ax,0更快。
答:
一共修改四次
第一次:读取mov ax,bx之后
第二次:读取sub ax,ax之后(ax清零)
第三次:读取jmp ax之后
第四次:执行jmp ax修改IP
最后IP的值为0000H,因为最后ax中的值为0000H,所以IP中的值也为
0000H
## 实验一 查看CPU和内存、用机器指令和汇编指令编程
1.预备知识: Debug的使用
我们以后所有的实验中,都将用到Debug程序,首先学习一下它的主要用法。
(1) 什么是Debug?
Debug是DOS、Windows 都提供的实模式(8086方式)程序的调试工具。
使用它,可以查看CPU各种寄存器中的内容、内存的情况和在机器码级跟踪程序的运行。
(2)我们用到的Debug功能。
用Debug的R命令查看、改变CPU寄存器的内容;
用Debug的D命令查看内存中的内容;
用Debug的E命令改写内存中的内容;
用Debug的U命令将内存中的机器指令翻译成汇编指令;
用Debug的T命令执行一条机器 指令;
用Debug的A命令以汇编指令的格式在内存中写入一条机器指令。
Debug的命令比较多,共有20多个,但这6个命令是和汇编学习密切相关的。
在以后的实验中,我们还会用到一个P命令。
(3)进入Debug。
Debug是在DOS方式下使用的程序。我们在进入Debug前,应先进入到DOS方式。
用以下方式可以进入DOS。
① 重新启动计算机,进入DOS方式,此时进入的是实模式的DOS。
② 在Windows中进入DOS方式,此时进入的是虚拟8086模式的DOS。
下面说明在Windows 2000 中进入Debug的一种方法,在其它Windows系统中进入的方法与此类似。
选择【开始】菜单中的【运行】命令,如图2.28所示,打开【运行】对话框,如
图2.29所示,在文本框中输入“command”后,单击【确定】按钮。
[外链图片转存中…(img-w3WEuOPy-1595762920072)]
进入DOS方式后,如果显示为窗口方式,可以按下Alt+Enter 键将窗口变为全屏方式。然后运行Debug程序,如图2.30所示。这个程序在不同的Windows系统中所在的路径不尽相同,在Windows 2000 中通常在c:\winntlsystem下。由于系统指定了搜索路径,所以在任何一-个路径中都可以运行。
[外链图片转存中…(img-UpTQikaw-1595762920075)]
(4)用R命令查看、改变CPU寄存器的内容。
我们已经知道了AX、BX、CX、DX、CS、IP这6个寄存器,现在看一下它们之中的内容,如图2.31
所示。其他寄存器如SP、 BP、SI、 DI、DS、ES、SS、标志寄存器等我们先不予理会。
[外链图片转存中…(img-qtLrPJQO-1595762920077)]
注意CS和IP的值,CS=0CA2, IP= 0100,也就是说,内存0CA2:0100处的指令为CPU当前要读取、
执行的指令。
在所有寄存器的下方,Debug还列出了CS:IP所指向的内存单元处所存放的机器码,并将它翻译为
汇编指令。
可以看到,CS:IP 所指向的内存单元为0CA2:0100,此处存放的机器码为027548,对应的汇编指
令为ADD DH,[DI+48] (这条指令的含义我们还不知道,先不必深究)
Debug输出的右下角还有一个信息:“DS:0048 =0”,我们以后会进行说明,这里同样不必深究。
还可以用R命令来改变寄存器中的内容,如图2.32所示。
[外链图片转存中…(img-G6kWABGd-1595762920080)]
若要修改一个寄存器中的值,比如AX中的值,可用R命令后加寄存器名来进行,输
入“r ax”后按Enter 键,将出现“:”作为输入提示,在后面输入要写入的数据后按
Enter键,即完成了对AX中内容的修改。若想看一下修改的结果,可再用R命令查
看,如图2.32所示。
[外链图片转存中…(img-WF8GKQKQ-1595762920083)]
在图2.33中,一进入Debug,用R命令查看,CS:IP 指向0B39:0100,此处存放的机器码为40,
对应的汇编指令是INC AX;
接着,用R命令将IP修改为200,则CS:IP指向0B39:0200, 此处存放的机器码为5B,对应的
汇编指令是POP BX;
接着,用R命令将CS修改为ff00, 则CS:IP指向ff00:0200, 此处存放的机器码为51,对应的汇
编指令是PUSH CX。
(5)用Debug的D命令查看内存中的内容。
用Debug的D命令,可以查看内存中的内容,D命令的格式较多,这里只介绍在本
次实验中用到的格式。
如果我们想知道内存10000H 处的内容,可以用 “d 段地址:偏移地址” 的格式来查
看,如图2.34所示。
[外链图片转存中…(img-jZNMnSsA-1595762920084)]
要查看内存10000H处的内容,首先将这个地址表示为段地址:偏移地址的格式,可以是
1000:0,然后用“d 1000:0”列出1000:0处的内容。
使用“d 段地址:偏移地址”的格式,Debug 将列出从指定内存单元开始的128 个内存单元的
内容。
图2.34中,在使用d 1000:0后,Debug列出了1000:0~ 1000:7F中的内容。
使用D命令,Debug 将输出3部分内容(如图2.34所示)。
① 中间是从指定地址开始的128个内存单元的内容,用十六进制的格式输出,每行的输出从16
的整数倍的地址开始,最多输出16个单元的内容。
从图中,我们可以知道,内存1000:0单元中的内容是72H,内存1000:1 单元中的内容是64H,
内存1000:0~ 1000:F中的内容都在第一行;
内存1000:10中的内容是6DH,内存1000:11 处的内容是61H,内存1000:10~1000:1F 中的内容都在第二行。
注意在每行的中间有一个“-”,它将每行的输出分为两部分,这样便于查看。
比如,要想从图中找出1000:6B 单元中内容,可以1000:60找到行,“-” 前面是1000:60~1000:67
的8个单元,后面是1000:68~1000:6F的8个单元,这样我们就可以从1000:68 单元向后数3个单
元,找到1000:6B 单元,可以看到,1000:6B 中的内容为67H。
② 左边是每行的起始地址。
③ 右边是每个内存单元中的数据对应的可显示的ASCII码字符。
比如,内存单元1000:0、1000:1、 1000:2 中存放的数据是72H、64H、73H, 它对应的
ASCII字符分别是“d”、“s”;内存单元1000:36 中的数据是0AH,它没有对应可显示的ASCII字
符,Debug 就用“.”来代替。
注意,我们看到的内存中的内容,在不同的计算机中是不一样的,也可能每次用Debug看到
的内容都不相同,因为我们用Debug看到的都是原来就在内存中的内容,这些内容受随时都
有可能变化的系统环境的影响。当然,我们也可以改变内存、寄存器中的内容。
我们使用d 1000:9查看1000:9处的内容,Debug 将怎样输出呢?
如图2.35所示。
[外链图片转存中…(img-GdxfhCO9-1595762920086)]
Debug从1000:9 开始显示,一直到1000:88,一共是128 个字节。第一行中的1000:0~1000:8单
元中的内容不显示。
在一进入Debug后,用D命令直接查看,将列出Debug预设的地址处的内容,如图2.36所示。
[外链图片转存中…(img-U4gnKozQ-1595762920087)]
在使用“d 段地址:偏移地址”之后,接着使用D命令,可列出后续的内容,如图2.37所示。
也可以指定D命令的查看范围,此时采用“d 段地址:起始偏移地址 结尾偏移地址”的格式。
比如要看1000:0- 1000:9中的内容,可以用“d 1000:0 9”实现,如图2.38所示。
[外链图片转存中…(img-jjIxJdjA-1595762920088)]
**如果我们就想查看内存单元 10000H 中的内容,可以用图2.39 中的任何一种方法看到,**因为图中的所有"段地址:偏移地址”都表示了10000H 这一物理地址。
[外链图片转存中…(img-QNXl6fPq-1595762920089)]
第三章
第二章中,我们主要从CPU如何执行指令的角度讲解了8086CPU的逻辑结构、形成物理地址的方法、相关寄存器以及一些指令。这章中,我们从访问内存的角度继续学习几个寄存器。
3.1 内存中字的存储
CPU中,用16位寄存器来存储一个字。
高8位存放高位字节,低8位存放低位字节。
在内存中存储时,由于内存单元是字节单元(一个单元存放一个字节),则一个字要用两个地址连续的内存单元来存放,这个字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。比如我们从0地址开始存放20000,这种情况如图3.1所示。
在图3.1中,我们用0、1两个内存单元存放数据20000(4E20H)。0、1两个内存单元用来存储一个字, 这两个单元可以看作一个起始地址为0的字单元(存放一个字的内存单元,由0、1两个字节单元组成)。对于这个字单元来说,0号单元是低地址单元,1号单元是高地址单元,则字型数据4E20H的低位字节存放在0号单元中,高位字节存放在1号单元中。同理,将2、3号单元看作一一个字单元,它的起始地址为2。在这个字单元中存放数据18(0012H), 则在2号单元中存放低位字节12H,在3号单元中存放高位字节00H。
我们提出字单元的概念:字单元,即存放一一个字型数据(16位)的内存单元,由两个地址连续的内存单元组成。高地址内存单元中存放字型数据的高位字节,低地址内存单元中存放字型数据的低位字节。
在以后的课程中,我们将起始地址为N的字单元简称为N地址字单元。
比如一个字单元由2、3两个内存单元组成,则这个字单元的起始地址为2,我们可以说这是2地址字单元。
问题 3.1
对于图3.1:
(1) 0地址单元中存放的字节型数据是多少?
(2) 0 地址字单元中存放的字型数据是多少?
(3) 2地址单元中存放的字节型数据是多少?
(4) 2 地址字单元中存放的字型数据是多少?
(5) 1地址字单元中存放的字型数据是多少?
答:
(1) 0地址单元中存放的字节型数据: 20H;
(2) 0地址字单元中存放的字型数据: 4E20H;
(3)2地址单元中存放的字节型数据:12H;
(4) 2 地址字单元中存放的字型数据: 0012H;
(5) 1地址字单元,即起始地址为1的字单元,它由1号单元和2号单元组成,用这
两个单元存储一一个字型数据,高位放在2号单元中,即: 12H,低位放在1号单元中,
4EH,它们组成字型数据是124EH,大小为: 4686。
从上面的问题中我们看到,任何两个地址连续的内存单元,N号单元和N+1号单元,可以将它们看成两个内存单元,也可看成一个地址为N的字单元中的高位字节单元和低位字节单元。
3.2 DS和[address]
CPU要读写一个内存单元的时候,必须先给出这个内存单元的地址,在8086PC中,内存地址由段地址和偏移地址
组成。8086CPU中有一个DS寄存器,通常用来存放要访问数据的段地址。
比如我们要读取10000H单元的内容,可以用如下的程序段进行。
mov bx,1000H
mov ds,bx
mov al,[0]
上面的3条指令将10000H(1000:0)中的数据读到al中。
下面详细说明指令的含义。
mov al,[0]
前面我们使用mov指令,可完成两种传送:
①将数据直接送入寄存器;
②将一个寄存器中的内存送入另一个寄存器。
也可以使用mov指令将一一个内存单元中的内容送入一个寄存器中。
从哪一一个内存单元送到哪一个寄存器中呢?在指令中必须指明。
寄存器用寄存器名来指明,内存单元则需用内存单元的地址来指明。
显然,此时mov指令的格式应该是: mov 寄存器名,内存单元地址。
“[…]”表示一个内存单元,**“[…]”中的0表示内存单元的偏移地址。**我们知道,只有偏移地址是不能定位一个内存
单元的,那么内存单元的段地址是多少呢?
指令执行时,8086CPU自动取ds中的数据为内存单元的段地址。
再来看一下,如何用mov指令从10000H中读取数据。
10000H 用段地址和偏移地址表示为1000:0,我们先将段地址1000H放入ds,然后用mov al,[0]完成传送。
mov指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds
中,指令执行时,8086CPU会自动从ds中取出。
mov bx, 1000H
mov ds, bx
若要用mov al,[0]完成数据从1000:0 单元到al的传送,这条指令执行时,ds 中的内容应为段地址1000H,所以在
这条指令之前应该将1000H送入ds。
如何把一个数据送入寄存器呢?
我们以前用类似“mov ax,1” 这样的指令来完成,从理论上讲,我们可以用相似的方式: mov ds, 1000H,来将
1000H 送入ds。
可是,现实并非如此8086CPU 不支持将数据直接送入段寄存器的操作,ds是一个段寄存器,所以mov ds, 1000H这条指令是非法的。
那么如何将1000H送入ds呢?
只好用一个寄存器来进行中转,即先将1000H送入一个一般的寄存器,如bx,再将bx中的内容送入ds。
为什么8086CPU不支持将数据直接送入段寄存器的操作?
这属于8086CPU硬件设计的问题,我们只要知道这一一点就行了。
3.3 字的传送
前面我们用mov指令在寄存器和内存之间进行字节型数据的传送。因为8086CPU是16位结构,有16根数据线,所以,可以一次性传送16位的数据,也就是说可以一次性传送一个字。只要在mov指令中给出16位的寄存器就可以进行16位数据的传送了。
比如:
mov bx,1000H
mov ds,bx
mov ax,[0] ;1000:0处的字型数据送入ax
mov [0],cx ;cx中的16位数据送到1000:0处
问题3.3
内存中的情况如图3.2所示,写出下面的指令执行后寄存器ax,bx,cx中的值。
mov ax, 1000H
mov ds, ax
mov ax, [0]
mov bx, [2]
mov cX, [1]
add bx, [1]
add cX, [2]
问题3.4
内存情况如图3.4所示,写出下面的指令执行后内存的值。
mov ax, 1000H
mov ds, ax
mov
ax, 1131 6
mov[ 0] , ax
mov bx, [0]
sub bx,[2]
mov [2] , bx