引言
汇编语言是直接在硬件之上工作的编程语言,首先要了解硬件系统的结构,才能有效的应用汇编语言对其编程。
在本章中,对硬件系统结构的问题进行一部分的探讨,以使后续的课程可在一个好的基础上进行。 当课程进行到需要补充新的基础知识(关于编程结构或其他的)时候,再对相关的基础知识进行介绍和探讨。
本书的原则是,以后用到的知识,以后再说。
汇编课程的研究重点放在如何利用硬件系统的编程结构和指令集有效灵活的控制系统进行工作。 机器语言
机器语言是机器指令的集合。
机器指令展开来讲就是一台机器可以正确执行的命令。
指令:01010000 (PUSH AX)
电平脉冲:
电子脉冲示例图
早期的程序员们将 0、1 数字编程的程序代码打在纸带或卡片上,1打孔,0不打孔,再将程序通过纸带机或卡片机输入计算机,进行运算。
后来呢,逐渐使用高科技……但打洞洞是始祖~
S = 768 + 12288 – 1280
汇编语言的产生
汇编语言的主体是汇编指令
汇编指令和机器指令的差别在于指令的表示方法上。汇编指令是机器指令便于记忆的书写格式。 汇编指令是机器指令的助记符。
机器指令: [**************]0
操作:寄存器 BX 的内容送到AX 中
汇编指令:MOV AX,BX
这样的写法与人类语言接近,便于阅读和记忆。
寄存器:简单的讲是CPU 中可以存储数据的器件,一个CPU 中有多个寄存器。
AX 是其中一个寄存器的代号,
BX 是另一个寄存器的代号。
更详细的内容我们在以后的课程中将会讲到。
计算机能读懂的只有机器指令,那么如何让计算机执行程序员用汇编指令编写的程序呢?
汇编语言的组成
汇编语言由以下3类组成:
1、汇编指令(机器码的助记符)
2、伪指令 (由编译器执行)
3、其它符号(由编译器识别)
汇编语言的核心是汇编指令,它决定了汇编语言的特性。
存储器
CPU 是计算机的核心部件.它控制整个计算机的运作并进行运算,要想让一个CPU 工作,就必须向它提供指令和数据。
指令和数据在存储器中存放,也就是平时所说的内存。
在一台PC 机中内存的作用仅次于CPU 。
离开了内存,性能再好的CPU 也无法工作。
磁盘不同于内存,磁盘上的数据或程序如果不读到内存中,就无法被CPU 使用。
指令和数据
指令和数据是应用上的概念。
在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。
二进制信息:
[**************]0
─> 89D8H (数据)
[**************]0
─> MOV AX,BX (程序)
存储单元
存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号;
例如:
一个存储器有128个存储单元,
编号从0~127。
存储器
对于大容量的存储器一般还用以下单位来计量容量(以下用B 来代表Byte ):
1KB=1024B
1MB=1024KB
1GB=1024MB
1TB=1024GB
磁盘的容量单位同内存的一样,实际上以上单位是微机中常用的计量单位。
CPU 对存储器的读写
CPU 要想进行数据的读写,必须和外部器件(标准的说法是芯片)进行三类信息的交互: 存储单元的地址(地址信息)
器件的选择,读或写命令(控制信息)
读或写的数据(数据信息)
那么CPU 是通过什么将地址、数据和控制信息传到存储芯片中的呢?
电子计算机能处理、传输的信息都是电信号,电信号当然要用导线传送。
在计算机中专门有连接CPU 和其他芯片的导线,通常称为总线。
物理上:一根根导线的集合;
逻辑上划分为:
地址总线
数据总线
控制总线
总线在逻辑上划分的图示:
上节课我们知道CPU 是如何进行数据读写的。可是我们如何命令计算机进行数据的读写呢? 对于8086CPU ,下面的机器码能够完成从3号单元读数据:
机器码: [***********]000000
含义:从3号单元读取数据送入寄存器AX
CPU 接收这条机器码后将完成上面所述的读写工作。
地址总线
CPU 是通过地址总线来指定存储单元的。
地址总线上能传送多少个不同的信息,CPU 就可以对多少个存储单元进行寻址。
那么,地址总线如何发送地址信息呢?
地址总线
一个CPU 有N 根地址总线,则可以说这个CPU 的地址总线的宽度为N 。
这样的CPU 最多可以寻找2的N 次方个内存单元。
CPU 与内存或其它器件之间的数据传送是通过数据总线来进行的。
数据总线的宽度决定了CPU 和外界的数据传送速度。
我们来分别看一下它们向内存中写入数据89D8H 时,是如何通过数据总线传送数据的: 8位数据总线上传送的信息
8位数据总线上传送的信息
16位数据总线上传送的信息
16位数据总线上传送的信息
控制总线
CPU 对外部器件的控制是通过控制总线来进行的。在这里控制总线是个总称,控制总线是一些不同控制线的集合。
有多少根控制总线,就意味着CPU 提供了对外部器件的多少种控制。
所以,控制总线的宽度决定了CPU 对外部器件的控制能力。
控制总线上发送的控制信息
控制总线上发送的控制信息
前面所讲的内存读或写命令是由几根控制线综合发出的:
其中有一根名为读信号输出控制线负责由CPU 向外传送读信号,CPU 向该控制线上输出低电平表示将要读取数据;
有一根名为写信号输出控制线负责由CPU 向外传送写信号。
小结(1)汇编指令是机器指令的助记符,同机器指令一一对应。
(2)每一种CPU 都有自己的汇编指令集。
(3)CPU 可以直接使用的信息在存储器中存放。
(4)在存储器中指令和数据没有任何区别,都是二进制信息。
(5)存储单元从零开始顺序编号。
(6)一个存储单元可以存储 8 个 bit (用作单位写成“b”),即 8 位二进制数。
(7)1B = 8b 1KB = 1024B 1MB = 1024KB 1GB = 1024MB
(8)每一个CPU 芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。 一个CPU 可以引出三种总线的宽度标志了这个CPU 的不同方面的性能:
地址总线的宽度决定了CPU 的寻址能力;
数据总线的宽度决定了CPU 与其它器件进行数据传送时的一次数据传送量;
控制总线宽度决定了CPU 对系统中其它器件的控制能力。
在汇编课程中,我们从功能的角度介绍了这三类总线,对实际的连接情况不做讨论。
内存地址空间(概述)
什么是内存地址空间呢?
一个CPU 的地址线宽度为10,那么可以寻址1024个内存单元,这1024个可寻到的内存单元就构成这个CPU 的内存地址空间。下面深入讨论。
首先需要介绍两部分基本知识,主板和接口卡。
主板
在每一台PC 机中,都有一个主板,主板上有核心器件和一些主要器件。
这些器件通过总线(地址总线、数据总线、控制总线)相连。
接口卡
计算机系统中,所有可用程序控制其工作的设备,必须受到CPU 的控制。
CPU 对外部设备不能直接控制,如显示器、音箱、打印机等。直接控制这些设备进行工作的是插在扩展插槽上的接口卡。
各类存储器芯片
从读写属性上看分为两类:随机存储器(RAM )和只读存储器(ROM )
从功能和连接上分类:
随机存储器RAM
接口卡上的RAM
装有BIOS 的ROM
BIOS :Basic Input/Output System,基本输入输出系统。
BIOS 是由主板和各类接口卡(如:显卡、网卡等)厂商提供的软件系统,可以通过它利用该硬件设备进行最基本的输入输出。在主板和某些接口卡上插有存储相应 BIOS 的 ROM 。
PC 机中各类存储器的逻辑连接情况
内存地址空间
上述的那些存储器在物理上是独立的器件。
但是它们在以下两点上相同:
1、都和CPU 的总线相连。
2、CPU 对它们进行读或写的时候都通过控制线发出内存读写命令。
将各各类存储器看作一个逻辑存储器:
将各各类存储器看作一个逻辑存储器
假设,上图中的内存空间地址段分配如下:
地址0~7FFFH的32KB 空间为主随机存储器的地址空间;
地址8000H~9FFFH的8KB 空间为显存地址空间;
地址A000H~FFFFH的24KB 空间为各个ROM 的地址空间。
所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器;
每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间;
CPU 在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据。
不同的计算机系统的内存地址空间分配情况是不同的。
8086PC 机的内存地址空间分配
8086PC 机的内存地址空间分配
最终运行程序的是CPU ,我们用汇编编程的时候,必须要从CPU 角度考虑问题。
(我们学习这门课程的核心思维)
对CPU 来讲,系统中的所有存储器中的存储单元都处于一个统一的逻辑存储器中,它的容量受CPU 寻址能力的限制。这个逻辑存储器即是我们所说的内存地址空间。
CPU 概述
一个典型的CPU 由运算器、控制器、寄存器等器件组成,这些器件靠内部总线相连。
区别:
内部总线实现CPU 内部各个器件之间的联系。 外部总线实现CPU 和主板上其它器件的联系。 寄存器概述
8086CPU 有14个寄存器 它们的名称为:
AX 、BX 、CX 、DX 、SI 、DI 、SP 、BP 、IP 、CS 、SS 、DS 、ES 、PSW 。 这些寄存器我们以后会陆续介绍,因为“以后用到的知识以后再讲——减负” 通用寄存器
8086CPU 所有的寄存器都是16位的,可以存放两个字节。 AX 、BX 、CX 、DX 通常用来存放一般性数据被称为通用寄存器。 下面以AX 为例,我们看一下寄存器的逻辑结构。
寄存器的逻辑结构
一个16位寄存器可以存储一个16位的数据。(数据的存放情况) 数据:18
二进制表示:10010
在寄存器AX 中的存储:
寄存器AX 数据:20000
二进制表示:[**************]0 在寄存器AX 中的存储:
寄存器AX
一个16位寄存器所能存储的数据的最大值为多少? 答案:2^16-1。
8086上一代CPU 中的寄存器都是8位的,为保证兼容性,这四个寄存器都可以分为两个独立的8位寄存器使用。
AX 可以分为 AH 和 AL; BX 可以分为 BH 和 BL; CX 可以分为 CH 和 CL; DX 可以分为 DH 和 DL 。
8086CPU 的8位寄存器存储逻辑
以AX 为例,8086CPU 的16位寄存器分为两个8位寄存器的情况:
8086CPU 的16位寄存器
AX 的低8位(0位~7位)构成了AL 寄存器,高8位(8位~15位)构成了AH 寄存器。 AH 和AL 寄存器是可以独立使用的8位寄存器。 8086CPU 的8位寄存器数据存储情况
8086CPU 的8位寄存器数据存储情况
8086CPU 的8位寄存器数据存储情况
一个8位寄存器所能存储的数据的最大值是多少? 答案:2^8-1。
字在寄存器中的存储
一个字可以存在一个16位寄存器中,这个字的高位字节和低位字节自然就存在这个寄存器的高8位寄存器和低8位寄存器中。
字在寄存器中的存储 关于数制的讨论
由于一个内存单元可以存放 8位数据,CPU 中的寄存器又可存放 n 个 8位数据。也就是说,计算机中的数据大多是由1~N个8位数据构成的。
用十六进制来表示数据可以直观的看出这个数据是由哪些8位数据构成的。
每两位对应一个八位的二进制数据(修正视频)! 几条汇编指令
汇编指令
CPU 执行下表中的程序段的每条指令后,对寄存器中的数据进行的改变。
汇编指令
汇编指令 物理地址
CPU 访问内存单元时要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间。
我们将这个唯一的地址称为物理地址。 16位结构的CPU
概括的讲,16位结构描述了一个CPU 具有以下几个方面特征:
1、运算器一次最多可以处理16位的数据。 2、寄存器的最大宽度为16位。
3、寄存器和运算器之间的通路是16位的。 8086CPU 给出物理地址的方法
8086有20位地址总线,可传送20位地址,寻址能力为1M 。
8086内部为16位结构,它只能传送16位的地址,表现出的寻址能力却只有64K 。 问题:那么,8086CPU 如何用内部16位的数据转换成20位的地址呢?
自问自答:8086CPU 采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址~
8086CPU 如何用内部16位的数据转换成20位的地址 8086CPU 读写内存时,发生了这么一些事:
CPU 中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址; 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件; 地址加法器将两个16位地址合并成一个20位的地址; 地址加法器工作原理
地址加法器合成物理地址的方法:物理地址=段地址×16+偏移地址 例如:8086CPU 访问地址为123C8H 的内存单元
8086CPU 访问地址为123C8H 的内存单元 由段地址×16引发的血案……
“段地址×16”有一个更为常用的说法就是数据左移4位。(二进制位)
进制转换
我们通过观察移位次数和各种形式数据的关系:
一个数据的二进制形式左移1位,相当于该数据乘以2;
一个数据的二进制形式左移N 位,相当于该数据乘以2的N 次方;
地址加法器如何完成段地址×16的运算?没错,以二进制形式存放的段地址左移4位。 一个馒头引发的分析……
经过进一步的思考,我们可以看出:
一个数据的十六进制形式左移1位,相当于乘以16; 一个数据的十进制形式左移1位,相当于乘以10; 一个数据的X 进制形式左移1位,相当于乘以X 。 段地址×16+偏移地址=物理地址”的本质含义 两个比喻说明:
说明“基础地址+偏移地址 = 物理地址” 的思想:第一个比喻
比如说,学校、体育馆同在一条笔直的单行路上(学校位于路的起点0米处)。 读者在学校,要去图书馆,问我那里的地址,我可以用几种方式描述这个地址?
段地址×16+偏移地址=物理地址
(1)从学校走2826m 到图书馆。这2826可以认为是图书馆的物理地址。 (2)从学校走2000m 到体育馆,从体育馆再走826m 到图书馆。 第一个距离2000m 是相对于起点的基础地址; 第二个距离826m 是将对于基础地址的偏移地址。
说明“段地址×16+偏移地址=物理地址”的思想:第二个比喻
比如我们只能通过纸条来通信,读者问我图书馆的地址,我只能将它写在纸上告诉读者。 显然我必须有一张可以容纳 4 位数据的纸条才能写下2826这个数据:
段地址×16+偏移地址=物理地址
不巧的是,没有能容纳4位数据的纸条,仅有两张可以容纳3位数据的纸条。 这样我只能以这种方式告诉读者2826这个数据:
段地址×16+偏移地址=物理地址
段的概念
误认识:
内存被划分成了一个一个的段,每一个段有一个段地址。其实是:
内存并没有分段,段的划分来自于CPU ,由于8086CPU 用“(段地址×16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。
分段的方式来管理内存
分段的方式来管理内存
以后,在编程时可以根据需要,将若干地址连续的内存单元看作一个段,用段地址×16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元。 两点需要注意
段地址×16 必然是 16的倍数,所以一个段的起始地址也一定是16的倍数;
偏移地址为16位,16 位地址的寻址能力为 64K ,所以一个段的长度最大为64K 。 内存单元地址小结
CPU 访问内存单元时,必须向内存提供内存单元的物理地址。
8086CPU 在内部用段地址和偏移地址移位相加的方法形成最终的物理地址。 思考两个问题
1. 观察下面的地址,读者有什么发现?
内存单元
结论:CPU 可以用不同的段地址和偏移地址形成同一个物理地址。
2. 如果给定一个段地址,仅通过变化偏移地址来进行寻址,最多可以定位多少内存单元? 结论:偏移地址16位,变化范围为0~FFFFH,仅用偏移地址来寻址最多可寻64K 个内存单元。 比如:给定段地址1000H ,用偏移地址寻址,CPU 的寻址范围为:10000H~1FFFFH。 小结
在8086PC 机中,存储单元的地址用两个元素来描述。即段地址和偏移地址。 数据在21F60H 内存单元中。”对于8086PC 机的两种描述:
数据存在内存2000:1F60单元中;
数据存在内存的2000段中的1F60H 单元中。
可根据需要,将地址连续、起始地址为16的倍数的一组内存单元定义为一个段。 检测点2.2
段寄存器
段寄存器就是提供段地址的。
8086CPU 有4个段寄存器:CS 、DS 、SS 、ES
当8086CPU 要访问内存时,由这4个段寄存器提供内存单元的段地址。 CS 和IP
CS 和IP 是8086CPU 中最关键的寄存器,它们指示了CPU 当前要读取指令的地址。
CS 为代码段寄存器; IP 为指令指针寄存器。
8086PC 读取和执行指令相关部件
8086PC 读取和执行指令相关部件 8086PC 读取和执行指令演示 8086PC 工作过程的简要描述
从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器; IP = IP + 所读取指令的长度,从而指向下一条指令; 执行指令。 转到步骤 (1),重复这个过程。
在 8086CPU 加电启动或复位后( 即 CPU 刚开始工作时)CS 和IP 被设置为CS=FFFFH,IP=0000H。
即在8086PC 机刚启动时,CPU 从内存FFFF0H 单元中读取指令执行。 FFFF0H 单元中的指令是8086PC 机开机后执行的第一条指令。
在任何时候,CPU 将CS 、IP 中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,
到内存中读取指令码,执行。
如果说,内存中的一段信息曾被CPU 执行过的话,那么,它所在的内存单元必然被CS:IP指向过。 修改CS 、IP 的指令
在CPU 中,程序员能够用指令读写的部件只有寄存器,程序员可以通过改变寄存器中的内容实现对CPU 的控制。
CPU 从何处执行指令是由CS 、IP 中的内容决定的,程序员可以通过改变CS 、IP 中的内容来控制CPU 执行目标指令。
我们如何改变CS 、IP 的值呢?
8086CPU 必须提供相应的指令
先回想我们如何修改AX 中的值?
mov 指令
如:mov ax,123
mov 指令可以改变8086CPU 大部分寄存器的值,被称为传送指令。
能够通过mov 指令改变CS 、IP 的值吗?
mov 指令不能用于设置CS 、IP 的值,8086CPU 没有提供这样的功能。
8086CPU 为CS 、IP 提供了另外的指令来改变它们的值:转移指令
同时修改CS 、IP 的内容:
jmp 段地址:偏移地址
jmp 2AE3:3
jmp 3:0B16
功能:用指令中给出的段地址修改CS ,偏移地址修改IP 。
仅修改IP 的内容:
jmp 某一合法寄存器
jmp ax (类似于 mov IP,ax )
jmp bx
功能:用寄存器中的值修改IP 。
问题分析:CPU运行的流程
内存中存放的机器码和对应汇编指令情况: (初始:CS=2000H,IP=0000H)
CPU 运行的流程
问题分析结果
(1)mov ax,6622
(2)jmp 1000:3
(3)mov ax,0000
(4)mov bx,ax
(5)jmp bx
(6)mov ax,0123H
(7)转到第(3)步执行
代码段
对于8086PC 机,在编程时,可以根据需要,将一组内存单元定义为一个段。
可以将长度为 N ( N≤64KB )的一组代码,存在一组地址连续、起始地址为 16的倍数的内存单元
中,这段内存是用来存放代码的,从而定义了一个代码段。
代码段
这段长度为 10 字节的字节的指令,存在从123B0H~123B9H的一组内存单元中,我们就可以认为,123B0H~123B9H这段内存单元是用来存放代码的 ,是一个代码段 ,它的段地址为123BH ,长度为10字节。
如何使得代码段中的指令被执行呢?
将一段内存当作代码段,仅仅是我们在编程时的一种安排,CPU 并不会由于这种安排,就自动地将我们定义得代码段中的指令当作指令来执行。
CPU 只认被 CS:IP 指向的内存单元中的内容为指令。
所以要将CS:IP指向所定义的代码段中的第一条指令的首地址。
如刚才的 CS = 123BH,IP = 0000H。
小结
1、段地址在8086CPU 的寄存器中存放。当8086CPU 要访问内存时,由段寄存器提供内存单元的段地址。8086CPU 有4个段寄存器,其中CS 用来存放指令的段地址。
2、CS 存放指令的段地址,IP 存放指令的偏移地址。8086机中,任意时刻,CPU 将CS:IP指向的内容当作指令执行。
3、8086CPU 的工作过程:
从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器;
IP 指向下一条指令;
执行指令。(转到步骤(1),重复这个过程。)
4、8086CPU 提供转移指令修改CS 、IP 的内容。
实验一
查看CPU 和内存,用机器指令和汇编指令编程
DEBUG 工具的使用
R 命令查看、改变CPU 寄存器的内容;
D 命令查看内存中的内容;
E 命令改写内存中的内容;
U 命令将内存中的机器指令翻译成汇编指令;
T 命令执行一条机器指令;
A 命令以汇编指令的格式在内存中写入一条机器指令。
引言
在第2章中,我们主要从 CPU 如何执行指令的角度讲解了8086CPU 的逻辑结构、形成物理地址的方法、相关的寄存器以及一些指令。
这一章中,我们从访问内存的角度继续学习几个寄存器。
内存中字的存储
在0地址处开始存放20000(4E20H ):
内存中字的存储
注意:0号单元是低地址单元,1号单元是高地址单元。
问题:
(1)0地址单元中存放的字节型数据是多少?
(2)0地址字单元中存放的字型数据是多少?
(3)2地址字单元中存放的字节型数据是多少?
(4)2地址单元中存放的字型数据是多少?
(5)1地址字单元中存放的字型数据是多少?
结论
任何两个地址连续的内存单元,N 号单元和 N+1号单元,可以将它们看成两个内存单元 ,也可以看成一个地址为N 的字单元中的高位字节单元和低位字节单元。
DS 和[address]
CPU 要读取一个内存单元的时候,必须先给出这个内存单元的地址;
在8086PC 中,内存地址由段地址和偏移地址组成。
8086CPU 中有一个 DS 寄存器,通常用来存放要访问的数据的段地址。
例如:我们要读取10000H 单元的内容可以用如下程序段进行:
mov bx,1000H
mov ds,bx
mov al,[0]
mov al,[0]
已知的mov 指令可完成的两种传送功能:
将数据直接送入寄存器;
将一个寄存器中的内容送入另一个寄存器中。
除此之外,mov 指令还可以将一个内存单元中的内容送入一个寄存器。
上面三条指令将10000H (1000:0)中的数据读到al 中。
从哪个内存单元送到哪个寄存器中呢?
mov 指令的格式:
mov 寄存器名,内存单元地址
“[…]”表示一个内存单元, “[…]”中的0表示内存单元的偏移地址。
那么内存单元的段地址是多少呢?
执行指令时,8086CPU 自动取DS 中的数据为内存单元的段地址。
如何用mov 指令从10000H 中读取数据?
10000H 表示为1000:0(段地址:偏移地址)
将段地址1000H 放入ds
用mov al,[0]完成传送(mov 指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds 中)
如何把1000H 送入ds ?
传送指令 mov ax,1
相似的方式 mov ds,1000H?
8086CPU 不支持将数据直接送入段寄存器的操作,ds 是一个段寄存器。(硬件设计的问题) mov ds,1000H 是非法的。
数据 -> 通用寄存器 -> 段寄存器
问题:
写几条指令,将al 中的数据送入内存单元10000H ?(思考后分析)
分析问题本质:
怎样将数据从寄存器送入内存单元?
结论:
mov bx,1000H
mov ds,bx
mov [0],al (一种合理的回答)
字的传送
因为8086CPU 是16位结构,有16根数据线,所以,可以一次性传送16位的数据,也就是一次性传送一个字。
字的传送
问题3.3:内存中的情况如下图,写出下面指令执行后寄存器ax ,bx ,cx 中的值。
字的传送
思考后请看听小甲鱼慢慢分析。(单步跟踪)
问题3.4:内存中的情况如下图,写出下面指令执行后寄存器ax ,bx ,cx 中的值。
字的传送
mov 、add 、sub 指令
已学mov 指令的几种形式:
mov 寄存器,数据
mov 寄存器,寄存器
mov 寄存器,内存单元
mov 内存单元,寄存器
mov 段寄存器,寄存器
根据已知指令进行推测:
mov 段寄存器,寄存器
mov 寄存器,段寄存器
mov 寄存器,段寄存器
mov 内存单元,寄存器
mov 内存单元,段寄存器
mov 段寄存器,内存单元
add 和sub 指令同mov 一样,都有两个操作对象。
mov 寄存器,段寄存器
它们可以对段寄存器进行操作吗?(请自行在Debug 中试验)
数据段
前面讲过,对于8086PC 机,我们可以根据需要将一组内存单元定义为一个段(可以是代码段、数据段等) 。
我们可以将一组长度为N (N≤64K)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。
比如我们用123B0H~123B9H这段空间来存放数据:
段地址:123BH
长度:10字节
如何访问数据段中的数据呢?
将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候 ,用 ds 存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。
我们将123B0H~123BAH的内存单元定义为数据段,我们现在要累加这个数据段中的前3个单元中的数据,代码如下:
访问数据段中的数据
问题3.5
写几条指令,累加数据段中的前3个字型数据。
思考后看分析:
累加数据段中的前3个字型数据
注意:一个字型数据占两个单元,所以偏移地址是0、2、4。
小结
(1)字在内存中存储时 ,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放再高地址单元中。
(2)用 mov 指令要访问内存单元,可以在mov 指令中只给出单元的偏移地址,此时,段地址默认在DS 寄存器中。
(3)[address]表示一个偏移地址为address 的内存单元。
(4)在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。
(5)mov 、add 、sub 是具有两个操作对象的指令。jmp 是具有一个操作对象的指令。
(6)可以根据自己的推测,在Debug 中实验指令的新格式。
检测点3.1
栈
我们研究栈的角度:
栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于,最后进入这个空间的数据,最先出去。 用一个盒子和3本书来描述栈的操作方式
出栈的方式
入栈的方式
入栈的方式
出栈的方式
出栈的方式
出栈的方式
栈有两个基本的操作:入栈和出栈。
入栈:将一个新的元素放到栈顶;
出栈:从栈顶取出一个元素。
栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。
栈的操作规则:LIFO (Last In First Out,后进先出)
CPU 提供的栈机制
现今的CPU 中都有栈的设计。
8086CPU 提供相关的指令来以栈的方式访问内存空间。
这意味着,我们在基于8086CPU 编程的时候,可以将一段内存当作栈来使用。
8086CPU 提供入栈和出栈指令: (最基本的)
PUSH (入栈)
POP (出栈)
push ax:将寄存器ax 中的数据送入栈中;
pop ax :从栈顶取出数据送入ax 。
8086CPU 的入栈和出栈操作都是以字为单位进行的。
下面举例说明,我们可以将10000H~1000FH这段内存当作栈来使用。
142010.11
寄存器(内存访问)04 – 零基础入门学习汇编语言16
标签:push 指令, 栈, 汇编语言 汇编语言
是否有疑惑?两个疑问
1、CPU 如何知道一段内存空间被当作栈使用?
2、执行push 和pop 的时候,如何知道哪个单元是栈顶单元?
分析:任意时刻,SS:SP指向栈顶元素。
对于两个疑问的分析
CPU 如何指导当前要执行的指令所在的位置?
答:寄存器CS 和IP 中存放着当前指令的段地址和偏移地址。
8086CPU 中,有两个寄存器:
段寄存器SS 存放栈顶的段地址
寄存器SP 存放栈顶的偏移地址
任意时刻,SS:SP指向栈顶元素。
push 指令的执行过程
push ax
SP=SP–2;
将ax 中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。
push 指令的执行过程
问题3.6:如果我们将10000H~1000FH 这段空间当作栈,初始状态栈是空的,此时,SS=1000H,SP=?
思考后看分析。
push 指令的执行过程
SP = 0010H
我们将10000H~1000FH 这段空间当作栈段,SS=1000H,栈空间大小为16 字节 ,栈最底部的字单元地址为1000:000E。
任意时刻,SS:SP指向栈顶,当栈中只有一个元素的时候,SS = 1000H,SP=000EH。
栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2 ,SP 原来为 000EH ,加 2 后SP=10H 所以,当栈为空的时候,SS=1000H,SP=10H。
换个角度看
任意时刻,SS:SP 指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素
所以SS:SP 只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地
址+2
栈最底部字单元的地址为1000:000E,所以栈空时,SP=0010H
pop 指令的执行过程
pop ax
将SS:SP指向的内存单元处的数据送入ax 中;
SP = SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
pop 指令的执行过程
注意:
出栈后,SS:SP指向新的栈顶 1000EH ,pop 操作前的栈顶元素,1000CH 处的2266H 依然存在 ,但是,它已不在栈中。
当再次执行push 等入栈指令后,SS:SP移至1000CH ,并在里面写入新的数据,它将被覆盖。 栈顶超界的问题
SS 和SP 只记录了栈顶的地址,依靠SS 和SP 可以保证在入栈和出栈时找到栈顶。
可是,如何能够保证在入栈、出栈时,栈顶不会超出栈空间?
都将发生栈顶超界问题,栈顶超界是危险的。
因为我们既然将一段空间安排为栈 ,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己的程序中的,也可能是别的程序中的。(毕竟一个计算机系统并不是只有我们自己的程序在运行)
但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。(但如果是刻意的……那么……呵呵……)
我们当然希望CPU 可以帮我们解决这个问题,比如说在CPU 中有记录栈顶上限和下限的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围 ,然后 ,CPU 在执行push 指令的时候靠检测栈顶上限寄存器,在执行pop 指令的时候靠检测栈顶下限寄存器保证不会超界。
实际情况:8086CPU 中并没有这样的寄存器。
8086CPU 不保证对栈的操作不会超界。
这就是说 8086CPU 只知道栈顶在何处(由SS:SP指示),而不知道读者安排的栈空间有多大。 这点就好像CPU 只知道当前要执行的指令在何处(由CS:SP指示)而不知道读者要执行的指令有多少。
从这两点我们可以看出
8086CPU 的工作机理,只考虑当前的情况:
当前栈顶在何处;
当前要执行的指令是哪一条。
结论
我们在编程的时候要自己操心栈顶超界的问题 ,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致的超界;
执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。
push 和pop 指令是可以在寄存器和内存之间传送数据的。
栈空间当然也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间。 push 和pop 指令的格式
push 寄存器:将一个寄存器中的数据入栈
pop 寄存器:出栈,用一个寄存器接收出栈的数据
例如:
push ax
pop bx
push 段寄存器:将一个段寄存器中的数据入栈
pop 段寄存器:出栈,用一个段寄存器接收出栈的数据
例如:
push ds
pop es
push 内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位)
pop 内存单元:出栈,用一个内存字单元接收出栈的数据
例如:
push [0]
pop [2]
指令执行时 ,CPU 要知道内存单元的地址,可以在 push 、pop 指令中给出内存单元的偏移地址,段地址在指令执行时,CPU 从ds 中取得。
问题3.7
编程:将10000H~1000FH 这段空间当作栈,初始状态是空的,将 AX 、BX 、DS 中的数据入栈。 思考后看分析。
push 、pop 指令
问题3.8
编程:
(1)将10000H~1000FH 这段空间当作栈,初始状态是空的;
(2)设置AX=001AH,BX=001BH;
(3)将AX 、BX 中的数据入栈;
(4)然后将AX 、BX 清零;
(5)从栈中恢复AX 、BX 原来的内容。
思考后看分析。
push 、pop 指令
从上面的程序我们看到,用栈来暂存以后需要恢复的寄存器中的内容时 ,出栈的顺序要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶 ,所以在恢复时,要最先出栈。
问题3.9
编程:
(1)将10000H~1000FH 这段空间当作栈,初始状态是空的;
(2)设置AX=002AH,BX=002BH;
(3)利用栈 ,交换 AX 和 BX 中的数据。
思考后看分析。
push 、pop 指令
问题3.10
我们如果要在10000H 处写入字型数据2266H ,可以用以下的代码完成:
mov ax,1000H
mov ds,ax
mov ,ax,2266H
mov [0],ax
看题目:
补在10000H 处写入字型数据2266H 。
__________
__________
__________
mov ax,2266H
push ax
要求:不能使用“mov 内存单元, 寄存器”这类指令
思考后看分析。
我们看需补全代码的最后两条指令,将ax 中的2266H 压入栈中,也就是说,最终应由push ax将2266H 写入10000H 处。
问题的关键就在于:如何使push ax访问的内存单元是10000H 。
Push 指令是入栈指令。(注意执行过程)
完整的程序:
mov ax,1000H
mov ss,ax
mov sp,2
mov ax,2266H
push ax
push 、pop 实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov 指令不同的是,push 和pop 指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。 同时,push 和pop 指令还要改变 SP 中的内容。
我们要十分清楚的是,push 和pop 指令同mov 指令不同,CPU 执行mov 指令只需一步操作,就是传送,而执行push 、pop 指令却需要两步操作。
执行push 时:先改变SP ,后向SS:SP处传送。
执行pop 时:先读取SS:SP处的数据,后改变SP 。
注意
push 、pop 等栈操作指令,修改的只是SP 。也就是说,栈顶的变化范围最大为:0~FFFFH。 提供:SS 、SP 指示栈顶; 改变SP 后写内存的入栈指令; 读内存后改变SP 的出栈指令。
这就是8086CPU 提供的栈操作机制。
栈的综述
1. 8086CPU提供了栈操作机制,方案如下:
在SS ,SP 中存放栈顶的段地址和偏移地址;
提供入栈和出栈指令,他们根据SS:SP指示的地址,按照栈的方式访问内存单元。
2. push指令的执行步骤:
SP=SP-2;
向SS:SP指向的字单元中送入数据。
3. pop指令的执行步骤:
从SS:SP指向的字单元中读取数据;
SP=SP-2。
4. 任意时刻,SS:SP指向栈顶元素。
5. 8086CPU只记录栈顶,栈空间的大小我们要自己管理。
6. 用栈来暂存以后需要恢复的寄存器的内容时 ,寄存器出栈的顺序要和 入栈的顺序相反。
7. push、pop 实质上是一种内存传送指令,注意它们的灵活应用。
栈是一种非常重要的机制,一定要深入理解,灵活掌握。
引言
汇编语言是直接在硬件之上工作的编程语言,首先要了解硬件系统的结构,才能有效的应用汇编语言对其编程。
在本章中,对硬件系统结构的问题进行一部分的探讨,以使后续的课程可在一个好的基础上进行。 当课程进行到需要补充新的基础知识(关于编程结构或其他的)时候,再对相关的基础知识进行介绍和探讨。
本书的原则是,以后用到的知识,以后再说。
汇编课程的研究重点放在如何利用硬件系统的编程结构和指令集有效灵活的控制系统进行工作。 机器语言
机器语言是机器指令的集合。
机器指令展开来讲就是一台机器可以正确执行的命令。
指令:01010000 (PUSH AX)
电平脉冲:
电子脉冲示例图
早期的程序员们将 0、1 数字编程的程序代码打在纸带或卡片上,1打孔,0不打孔,再将程序通过纸带机或卡片机输入计算机,进行运算。
后来呢,逐渐使用高科技……但打洞洞是始祖~
S = 768 + 12288 – 1280
汇编语言的产生
汇编语言的主体是汇编指令
汇编指令和机器指令的差别在于指令的表示方法上。汇编指令是机器指令便于记忆的书写格式。 汇编指令是机器指令的助记符。
机器指令: [**************]0
操作:寄存器 BX 的内容送到AX 中
汇编指令:MOV AX,BX
这样的写法与人类语言接近,便于阅读和记忆。
寄存器:简单的讲是CPU 中可以存储数据的器件,一个CPU 中有多个寄存器。
AX 是其中一个寄存器的代号,
BX 是另一个寄存器的代号。
更详细的内容我们在以后的课程中将会讲到。
计算机能读懂的只有机器指令,那么如何让计算机执行程序员用汇编指令编写的程序呢?
汇编语言的组成
汇编语言由以下3类组成:
1、汇编指令(机器码的助记符)
2、伪指令 (由编译器执行)
3、其它符号(由编译器识别)
汇编语言的核心是汇编指令,它决定了汇编语言的特性。
存储器
CPU 是计算机的核心部件.它控制整个计算机的运作并进行运算,要想让一个CPU 工作,就必须向它提供指令和数据。
指令和数据在存储器中存放,也就是平时所说的内存。
在一台PC 机中内存的作用仅次于CPU 。
离开了内存,性能再好的CPU 也无法工作。
磁盘不同于内存,磁盘上的数据或程序如果不读到内存中,就无法被CPU 使用。
指令和数据
指令和数据是应用上的概念。
在内存或磁盘上,指令和数据没有任何区别,都是二进制信息。
二进制信息:
[**************]0
─> 89D8H (数据)
[**************]0
─> MOV AX,BX (程序)
存储单元
存储器被划分为若干个存储单元,每个存储单元从0开始顺序编号;
例如:
一个存储器有128个存储单元,
编号从0~127。
存储器
对于大容量的存储器一般还用以下单位来计量容量(以下用B 来代表Byte ):
1KB=1024B
1MB=1024KB
1GB=1024MB
1TB=1024GB
磁盘的容量单位同内存的一样,实际上以上单位是微机中常用的计量单位。
CPU 对存储器的读写
CPU 要想进行数据的读写,必须和外部器件(标准的说法是芯片)进行三类信息的交互: 存储单元的地址(地址信息)
器件的选择,读或写命令(控制信息)
读或写的数据(数据信息)
那么CPU 是通过什么将地址、数据和控制信息传到存储芯片中的呢?
电子计算机能处理、传输的信息都是电信号,电信号当然要用导线传送。
在计算机中专门有连接CPU 和其他芯片的导线,通常称为总线。
物理上:一根根导线的集合;
逻辑上划分为:
地址总线
数据总线
控制总线
总线在逻辑上划分的图示:
上节课我们知道CPU 是如何进行数据读写的。可是我们如何命令计算机进行数据的读写呢? 对于8086CPU ,下面的机器码能够完成从3号单元读数据:
机器码: [***********]000000
含义:从3号单元读取数据送入寄存器AX
CPU 接收这条机器码后将完成上面所述的读写工作。
地址总线
CPU 是通过地址总线来指定存储单元的。
地址总线上能传送多少个不同的信息,CPU 就可以对多少个存储单元进行寻址。
那么,地址总线如何发送地址信息呢?
地址总线
一个CPU 有N 根地址总线,则可以说这个CPU 的地址总线的宽度为N 。
这样的CPU 最多可以寻找2的N 次方个内存单元。
CPU 与内存或其它器件之间的数据传送是通过数据总线来进行的。
数据总线的宽度决定了CPU 和外界的数据传送速度。
我们来分别看一下它们向内存中写入数据89D8H 时,是如何通过数据总线传送数据的: 8位数据总线上传送的信息
8位数据总线上传送的信息
16位数据总线上传送的信息
16位数据总线上传送的信息
控制总线
CPU 对外部器件的控制是通过控制总线来进行的。在这里控制总线是个总称,控制总线是一些不同控制线的集合。
有多少根控制总线,就意味着CPU 提供了对外部器件的多少种控制。
所以,控制总线的宽度决定了CPU 对外部器件的控制能力。
控制总线上发送的控制信息
控制总线上发送的控制信息
前面所讲的内存读或写命令是由几根控制线综合发出的:
其中有一根名为读信号输出控制线负责由CPU 向外传送读信号,CPU 向该控制线上输出低电平表示将要读取数据;
有一根名为写信号输出控制线负责由CPU 向外传送写信号。
小结(1)汇编指令是机器指令的助记符,同机器指令一一对应。
(2)每一种CPU 都有自己的汇编指令集。
(3)CPU 可以直接使用的信息在存储器中存放。
(4)在存储器中指令和数据没有任何区别,都是二进制信息。
(5)存储单元从零开始顺序编号。
(6)一个存储单元可以存储 8 个 bit (用作单位写成“b”),即 8 位二进制数。
(7)1B = 8b 1KB = 1024B 1MB = 1024KB 1GB = 1024MB
(8)每一个CPU 芯片都有许多管脚,这些管脚和总线相连。也可以说,这些管脚引出总线。 一个CPU 可以引出三种总线的宽度标志了这个CPU 的不同方面的性能:
地址总线的宽度决定了CPU 的寻址能力;
数据总线的宽度决定了CPU 与其它器件进行数据传送时的一次数据传送量;
控制总线宽度决定了CPU 对系统中其它器件的控制能力。
在汇编课程中,我们从功能的角度介绍了这三类总线,对实际的连接情况不做讨论。
内存地址空间(概述)
什么是内存地址空间呢?
一个CPU 的地址线宽度为10,那么可以寻址1024个内存单元,这1024个可寻到的内存单元就构成这个CPU 的内存地址空间。下面深入讨论。
首先需要介绍两部分基本知识,主板和接口卡。
主板
在每一台PC 机中,都有一个主板,主板上有核心器件和一些主要器件。
这些器件通过总线(地址总线、数据总线、控制总线)相连。
接口卡
计算机系统中,所有可用程序控制其工作的设备,必须受到CPU 的控制。
CPU 对外部设备不能直接控制,如显示器、音箱、打印机等。直接控制这些设备进行工作的是插在扩展插槽上的接口卡。
各类存储器芯片
从读写属性上看分为两类:随机存储器(RAM )和只读存储器(ROM )
从功能和连接上分类:
随机存储器RAM
接口卡上的RAM
装有BIOS 的ROM
BIOS :Basic Input/Output System,基本输入输出系统。
BIOS 是由主板和各类接口卡(如:显卡、网卡等)厂商提供的软件系统,可以通过它利用该硬件设备进行最基本的输入输出。在主板和某些接口卡上插有存储相应 BIOS 的 ROM 。
PC 机中各类存储器的逻辑连接情况
内存地址空间
上述的那些存储器在物理上是独立的器件。
但是它们在以下两点上相同:
1、都和CPU 的总线相连。
2、CPU 对它们进行读或写的时候都通过控制线发出内存读写命令。
将各各类存储器看作一个逻辑存储器:
将各各类存储器看作一个逻辑存储器
假设,上图中的内存空间地址段分配如下:
地址0~7FFFH的32KB 空间为主随机存储器的地址空间;
地址8000H~9FFFH的8KB 空间为显存地址空间;
地址A000H~FFFFH的24KB 空间为各个ROM 的地址空间。
所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器;
每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间;
CPU 在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据。
不同的计算机系统的内存地址空间分配情况是不同的。
8086PC 机的内存地址空间分配
8086PC 机的内存地址空间分配
最终运行程序的是CPU ,我们用汇编编程的时候,必须要从CPU 角度考虑问题。
(我们学习这门课程的核心思维)
对CPU 来讲,系统中的所有存储器中的存储单元都处于一个统一的逻辑存储器中,它的容量受CPU 寻址能力的限制。这个逻辑存储器即是我们所说的内存地址空间。
CPU 概述
一个典型的CPU 由运算器、控制器、寄存器等器件组成,这些器件靠内部总线相连。
区别:
内部总线实现CPU 内部各个器件之间的联系。 外部总线实现CPU 和主板上其它器件的联系。 寄存器概述
8086CPU 有14个寄存器 它们的名称为:
AX 、BX 、CX 、DX 、SI 、DI 、SP 、BP 、IP 、CS 、SS 、DS 、ES 、PSW 。 这些寄存器我们以后会陆续介绍,因为“以后用到的知识以后再讲——减负” 通用寄存器
8086CPU 所有的寄存器都是16位的,可以存放两个字节。 AX 、BX 、CX 、DX 通常用来存放一般性数据被称为通用寄存器。 下面以AX 为例,我们看一下寄存器的逻辑结构。
寄存器的逻辑结构
一个16位寄存器可以存储一个16位的数据。(数据的存放情况) 数据:18
二进制表示:10010
在寄存器AX 中的存储:
寄存器AX 数据:20000
二进制表示:[**************]0 在寄存器AX 中的存储:
寄存器AX
一个16位寄存器所能存储的数据的最大值为多少? 答案:2^16-1。
8086上一代CPU 中的寄存器都是8位的,为保证兼容性,这四个寄存器都可以分为两个独立的8位寄存器使用。
AX 可以分为 AH 和 AL; BX 可以分为 BH 和 BL; CX 可以分为 CH 和 CL; DX 可以分为 DH 和 DL 。
8086CPU 的8位寄存器存储逻辑
以AX 为例,8086CPU 的16位寄存器分为两个8位寄存器的情况:
8086CPU 的16位寄存器
AX 的低8位(0位~7位)构成了AL 寄存器,高8位(8位~15位)构成了AH 寄存器。 AH 和AL 寄存器是可以独立使用的8位寄存器。 8086CPU 的8位寄存器数据存储情况
8086CPU 的8位寄存器数据存储情况
8086CPU 的8位寄存器数据存储情况
一个8位寄存器所能存储的数据的最大值是多少? 答案:2^8-1。
字在寄存器中的存储
一个字可以存在一个16位寄存器中,这个字的高位字节和低位字节自然就存在这个寄存器的高8位寄存器和低8位寄存器中。
字在寄存器中的存储 关于数制的讨论
由于一个内存单元可以存放 8位数据,CPU 中的寄存器又可存放 n 个 8位数据。也就是说,计算机中的数据大多是由1~N个8位数据构成的。
用十六进制来表示数据可以直观的看出这个数据是由哪些8位数据构成的。
每两位对应一个八位的二进制数据(修正视频)! 几条汇编指令
汇编指令
CPU 执行下表中的程序段的每条指令后,对寄存器中的数据进行的改变。
汇编指令
汇编指令 物理地址
CPU 访问内存单元时要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维的线性空间。
我们将这个唯一的地址称为物理地址。 16位结构的CPU
概括的讲,16位结构描述了一个CPU 具有以下几个方面特征:
1、运算器一次最多可以处理16位的数据。 2、寄存器的最大宽度为16位。
3、寄存器和运算器之间的通路是16位的。 8086CPU 给出物理地址的方法
8086有20位地址总线,可传送20位地址,寻址能力为1M 。
8086内部为16位结构,它只能传送16位的地址,表现出的寻址能力却只有64K 。 问题:那么,8086CPU 如何用内部16位的数据转换成20位的地址呢?
自问自答:8086CPU 采用一种在内部用两个16位地址合成的方法来形成一个20位的物理地址~
8086CPU 如何用内部16位的数据转换成20位的地址 8086CPU 读写内存时,发生了这么一些事:
CPU 中的相关部件提供两个16位的地址,一个称为段地址,另一个称为偏移地址; 段地址和偏移地址通过内部总线送入一个称为地址加法器的部件; 地址加法器将两个16位地址合并成一个20位的地址; 地址加法器工作原理
地址加法器合成物理地址的方法:物理地址=段地址×16+偏移地址 例如:8086CPU 访问地址为123C8H 的内存单元
8086CPU 访问地址为123C8H 的内存单元 由段地址×16引发的血案……
“段地址×16”有一个更为常用的说法就是数据左移4位。(二进制位)
进制转换
我们通过观察移位次数和各种形式数据的关系:
一个数据的二进制形式左移1位,相当于该数据乘以2;
一个数据的二进制形式左移N 位,相当于该数据乘以2的N 次方;
地址加法器如何完成段地址×16的运算?没错,以二进制形式存放的段地址左移4位。 一个馒头引发的分析……
经过进一步的思考,我们可以看出:
一个数据的十六进制形式左移1位,相当于乘以16; 一个数据的十进制形式左移1位,相当于乘以10; 一个数据的X 进制形式左移1位,相当于乘以X 。 段地址×16+偏移地址=物理地址”的本质含义 两个比喻说明:
说明“基础地址+偏移地址 = 物理地址” 的思想:第一个比喻
比如说,学校、体育馆同在一条笔直的单行路上(学校位于路的起点0米处)。 读者在学校,要去图书馆,问我那里的地址,我可以用几种方式描述这个地址?
段地址×16+偏移地址=物理地址
(1)从学校走2826m 到图书馆。这2826可以认为是图书馆的物理地址。 (2)从学校走2000m 到体育馆,从体育馆再走826m 到图书馆。 第一个距离2000m 是相对于起点的基础地址; 第二个距离826m 是将对于基础地址的偏移地址。
说明“段地址×16+偏移地址=物理地址”的思想:第二个比喻
比如我们只能通过纸条来通信,读者问我图书馆的地址,我只能将它写在纸上告诉读者。 显然我必须有一张可以容纳 4 位数据的纸条才能写下2826这个数据:
段地址×16+偏移地址=物理地址
不巧的是,没有能容纳4位数据的纸条,仅有两张可以容纳3位数据的纸条。 这样我只能以这种方式告诉读者2826这个数据:
段地址×16+偏移地址=物理地址
段的概念
误认识:
内存被划分成了一个一个的段,每一个段有一个段地址。其实是:
内存并没有分段,段的划分来自于CPU ,由于8086CPU 用“(段地址×16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存。
分段的方式来管理内存
分段的方式来管理内存
以后,在编程时可以根据需要,将若干地址连续的内存单元看作一个段,用段地址×16定位段的起始地址(基础地址),用偏移地址定位段中的内存单元。 两点需要注意
段地址×16 必然是 16的倍数,所以一个段的起始地址也一定是16的倍数;
偏移地址为16位,16 位地址的寻址能力为 64K ,所以一个段的长度最大为64K 。 内存单元地址小结
CPU 访问内存单元时,必须向内存提供内存单元的物理地址。
8086CPU 在内部用段地址和偏移地址移位相加的方法形成最终的物理地址。 思考两个问题
1. 观察下面的地址,读者有什么发现?
内存单元
结论:CPU 可以用不同的段地址和偏移地址形成同一个物理地址。
2. 如果给定一个段地址,仅通过变化偏移地址来进行寻址,最多可以定位多少内存单元? 结论:偏移地址16位,变化范围为0~FFFFH,仅用偏移地址来寻址最多可寻64K 个内存单元。 比如:给定段地址1000H ,用偏移地址寻址,CPU 的寻址范围为:10000H~1FFFFH。 小结
在8086PC 机中,存储单元的地址用两个元素来描述。即段地址和偏移地址。 数据在21F60H 内存单元中。”对于8086PC 机的两种描述:
数据存在内存2000:1F60单元中;
数据存在内存的2000段中的1F60H 单元中。
可根据需要,将地址连续、起始地址为16的倍数的一组内存单元定义为一个段。 检测点2.2
段寄存器
段寄存器就是提供段地址的。
8086CPU 有4个段寄存器:CS 、DS 、SS 、ES
当8086CPU 要访问内存时,由这4个段寄存器提供内存单元的段地址。 CS 和IP
CS 和IP 是8086CPU 中最关键的寄存器,它们指示了CPU 当前要读取指令的地址。
CS 为代码段寄存器; IP 为指令指针寄存器。
8086PC 读取和执行指令相关部件
8086PC 读取和执行指令相关部件 8086PC 读取和执行指令演示 8086PC 工作过程的简要描述
从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器; IP = IP + 所读取指令的长度,从而指向下一条指令; 执行指令。 转到步骤 (1),重复这个过程。
在 8086CPU 加电启动或复位后( 即 CPU 刚开始工作时)CS 和IP 被设置为CS=FFFFH,IP=0000H。
即在8086PC 机刚启动时,CPU 从内存FFFF0H 单元中读取指令执行。 FFFF0H 单元中的指令是8086PC 机开机后执行的第一条指令。
在任何时候,CPU 将CS 、IP 中的内容当作指令的段地址和偏移地址,用它们合成指令的物理地址,
到内存中读取指令码,执行。
如果说,内存中的一段信息曾被CPU 执行过的话,那么,它所在的内存单元必然被CS:IP指向过。 修改CS 、IP 的指令
在CPU 中,程序员能够用指令读写的部件只有寄存器,程序员可以通过改变寄存器中的内容实现对CPU 的控制。
CPU 从何处执行指令是由CS 、IP 中的内容决定的,程序员可以通过改变CS 、IP 中的内容来控制CPU 执行目标指令。
我们如何改变CS 、IP 的值呢?
8086CPU 必须提供相应的指令
先回想我们如何修改AX 中的值?
mov 指令
如:mov ax,123
mov 指令可以改变8086CPU 大部分寄存器的值,被称为传送指令。
能够通过mov 指令改变CS 、IP 的值吗?
mov 指令不能用于设置CS 、IP 的值,8086CPU 没有提供这样的功能。
8086CPU 为CS 、IP 提供了另外的指令来改变它们的值:转移指令
同时修改CS 、IP 的内容:
jmp 段地址:偏移地址
jmp 2AE3:3
jmp 3:0B16
功能:用指令中给出的段地址修改CS ,偏移地址修改IP 。
仅修改IP 的内容:
jmp 某一合法寄存器
jmp ax (类似于 mov IP,ax )
jmp bx
功能:用寄存器中的值修改IP 。
问题分析:CPU运行的流程
内存中存放的机器码和对应汇编指令情况: (初始:CS=2000H,IP=0000H)
CPU 运行的流程
问题分析结果
(1)mov ax,6622
(2)jmp 1000:3
(3)mov ax,0000
(4)mov bx,ax
(5)jmp bx
(6)mov ax,0123H
(7)转到第(3)步执行
代码段
对于8086PC 机,在编程时,可以根据需要,将一组内存单元定义为一个段。
可以将长度为 N ( N≤64KB )的一组代码,存在一组地址连续、起始地址为 16的倍数的内存单元
中,这段内存是用来存放代码的,从而定义了一个代码段。
代码段
这段长度为 10 字节的字节的指令,存在从123B0H~123B9H的一组内存单元中,我们就可以认为,123B0H~123B9H这段内存单元是用来存放代码的 ,是一个代码段 ,它的段地址为123BH ,长度为10字节。
如何使得代码段中的指令被执行呢?
将一段内存当作代码段,仅仅是我们在编程时的一种安排,CPU 并不会由于这种安排,就自动地将我们定义得代码段中的指令当作指令来执行。
CPU 只认被 CS:IP 指向的内存单元中的内容为指令。
所以要将CS:IP指向所定义的代码段中的第一条指令的首地址。
如刚才的 CS = 123BH,IP = 0000H。
小结
1、段地址在8086CPU 的寄存器中存放。当8086CPU 要访问内存时,由段寄存器提供内存单元的段地址。8086CPU 有4个段寄存器,其中CS 用来存放指令的段地址。
2、CS 存放指令的段地址,IP 存放指令的偏移地址。8086机中,任意时刻,CPU 将CS:IP指向的内容当作指令执行。
3、8086CPU 的工作过程:
从CS:IP指向内存单元读取指令,读取的指令进入指令缓冲器;
IP 指向下一条指令;
执行指令。(转到步骤(1),重复这个过程。)
4、8086CPU 提供转移指令修改CS 、IP 的内容。
实验一
查看CPU 和内存,用机器指令和汇编指令编程
DEBUG 工具的使用
R 命令查看、改变CPU 寄存器的内容;
D 命令查看内存中的内容;
E 命令改写内存中的内容;
U 命令将内存中的机器指令翻译成汇编指令;
T 命令执行一条机器指令;
A 命令以汇编指令的格式在内存中写入一条机器指令。
引言
在第2章中,我们主要从 CPU 如何执行指令的角度讲解了8086CPU 的逻辑结构、形成物理地址的方法、相关的寄存器以及一些指令。
这一章中,我们从访问内存的角度继续学习几个寄存器。
内存中字的存储
在0地址处开始存放20000(4E20H ):
内存中字的存储
注意:0号单元是低地址单元,1号单元是高地址单元。
问题:
(1)0地址单元中存放的字节型数据是多少?
(2)0地址字单元中存放的字型数据是多少?
(3)2地址字单元中存放的字节型数据是多少?
(4)2地址单元中存放的字型数据是多少?
(5)1地址字单元中存放的字型数据是多少?
结论
任何两个地址连续的内存单元,N 号单元和 N+1号单元,可以将它们看成两个内存单元 ,也可以看成一个地址为N 的字单元中的高位字节单元和低位字节单元。
DS 和[address]
CPU 要读取一个内存单元的时候,必须先给出这个内存单元的地址;
在8086PC 中,内存地址由段地址和偏移地址组成。
8086CPU 中有一个 DS 寄存器,通常用来存放要访问的数据的段地址。
例如:我们要读取10000H 单元的内容可以用如下程序段进行:
mov bx,1000H
mov ds,bx
mov al,[0]
mov al,[0]
已知的mov 指令可完成的两种传送功能:
将数据直接送入寄存器;
将一个寄存器中的内容送入另一个寄存器中。
除此之外,mov 指令还可以将一个内存单元中的内容送入一个寄存器。
上面三条指令将10000H (1000:0)中的数据读到al 中。
从哪个内存单元送到哪个寄存器中呢?
mov 指令的格式:
mov 寄存器名,内存单元地址
“[…]”表示一个内存单元, “[…]”中的0表示内存单元的偏移地址。
那么内存单元的段地址是多少呢?
执行指令时,8086CPU 自动取DS 中的数据为内存单元的段地址。
如何用mov 指令从10000H 中读取数据?
10000H 表示为1000:0(段地址:偏移地址)
将段地址1000H 放入ds
用mov al,[0]完成传送(mov 指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在ds 中)
如何把1000H 送入ds ?
传送指令 mov ax,1
相似的方式 mov ds,1000H?
8086CPU 不支持将数据直接送入段寄存器的操作,ds 是一个段寄存器。(硬件设计的问题) mov ds,1000H 是非法的。
数据 -> 通用寄存器 -> 段寄存器
问题:
写几条指令,将al 中的数据送入内存单元10000H ?(思考后分析)
分析问题本质:
怎样将数据从寄存器送入内存单元?
结论:
mov bx,1000H
mov ds,bx
mov [0],al (一种合理的回答)
字的传送
因为8086CPU 是16位结构,有16根数据线,所以,可以一次性传送16位的数据,也就是一次性传送一个字。
字的传送
问题3.3:内存中的情况如下图,写出下面指令执行后寄存器ax ,bx ,cx 中的值。
字的传送
思考后请看听小甲鱼慢慢分析。(单步跟踪)
问题3.4:内存中的情况如下图,写出下面指令执行后寄存器ax ,bx ,cx 中的值。
字的传送
mov 、add 、sub 指令
已学mov 指令的几种形式:
mov 寄存器,数据
mov 寄存器,寄存器
mov 寄存器,内存单元
mov 内存单元,寄存器
mov 段寄存器,寄存器
根据已知指令进行推测:
mov 段寄存器,寄存器
mov 寄存器,段寄存器
mov 寄存器,段寄存器
mov 内存单元,寄存器
mov 内存单元,段寄存器
mov 段寄存器,内存单元
add 和sub 指令同mov 一样,都有两个操作对象。
mov 寄存器,段寄存器
它们可以对段寄存器进行操作吗?(请自行在Debug 中试验)
数据段
前面讲过,对于8086PC 机,我们可以根据需要将一组内存单元定义为一个段(可以是代码段、数据段等) 。
我们可以将一组长度为N (N≤64K)、地址连续、起始地址为16的倍数的内存单元当作专门存储数据的内存空间,从而定义了一个数据段。
比如我们用123B0H~123B9H这段空间来存放数据:
段地址:123BH
长度:10字节
如何访问数据段中的数据呢?
将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候 ,用 ds 存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。
我们将123B0H~123BAH的内存单元定义为数据段,我们现在要累加这个数据段中的前3个单元中的数据,代码如下:
访问数据段中的数据
问题3.5
写几条指令,累加数据段中的前3个字型数据。
思考后看分析:
累加数据段中的前3个字型数据
注意:一个字型数据占两个单元,所以偏移地址是0、2、4。
小结
(1)字在内存中存储时 ,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放再高地址单元中。
(2)用 mov 指令要访问内存单元,可以在mov 指令中只给出单元的偏移地址,此时,段地址默认在DS 寄存器中。
(3)[address]表示一个偏移地址为address 的内存单元。
(4)在内存和寄存器之间传送字型数据时,高地址单元和高8位寄存器、低地址单元和低8位寄存器相对应。
(5)mov 、add 、sub 是具有两个操作对象的指令。jmp 是具有一个操作对象的指令。
(6)可以根据自己的推测,在Debug 中实验指令的新格式。
检测点3.1
栈
我们研究栈的角度:
栈是一种具有特殊的访问方式的存储空间。它的特殊性就在于,最后进入这个空间的数据,最先出去。 用一个盒子和3本书来描述栈的操作方式
出栈的方式
入栈的方式
入栈的方式
出栈的方式
出栈的方式
出栈的方式
栈有两个基本的操作:入栈和出栈。
入栈:将一个新的元素放到栈顶;
出栈:从栈顶取出一个元素。
栈顶的元素总是最后入栈,需要出栈时,又最先被从栈中取出。
栈的操作规则:LIFO (Last In First Out,后进先出)
CPU 提供的栈机制
现今的CPU 中都有栈的设计。
8086CPU 提供相关的指令来以栈的方式访问内存空间。
这意味着,我们在基于8086CPU 编程的时候,可以将一段内存当作栈来使用。
8086CPU 提供入栈和出栈指令: (最基本的)
PUSH (入栈)
POP (出栈)
push ax:将寄存器ax 中的数据送入栈中;
pop ax :从栈顶取出数据送入ax 。
8086CPU 的入栈和出栈操作都是以字为单位进行的。
下面举例说明,我们可以将10000H~1000FH这段内存当作栈来使用。
142010.11
寄存器(内存访问)04 – 零基础入门学习汇编语言16
标签:push 指令, 栈, 汇编语言 汇编语言
是否有疑惑?两个疑问
1、CPU 如何知道一段内存空间被当作栈使用?
2、执行push 和pop 的时候,如何知道哪个单元是栈顶单元?
分析:任意时刻,SS:SP指向栈顶元素。
对于两个疑问的分析
CPU 如何指导当前要执行的指令所在的位置?
答:寄存器CS 和IP 中存放着当前指令的段地址和偏移地址。
8086CPU 中,有两个寄存器:
段寄存器SS 存放栈顶的段地址
寄存器SP 存放栈顶的偏移地址
任意时刻,SS:SP指向栈顶元素。
push 指令的执行过程
push ax
SP=SP–2;
将ax 中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。
push 指令的执行过程
问题3.6:如果我们将10000H~1000FH 这段空间当作栈,初始状态栈是空的,此时,SS=1000H,SP=?
思考后看分析。
push 指令的执行过程
SP = 0010H
我们将10000H~1000FH 这段空间当作栈段,SS=1000H,栈空间大小为16 字节 ,栈最底部的字单元地址为1000:000E。
任意时刻,SS:SP指向栈顶,当栈中只有一个元素的时候,SS = 1000H,SP=000EH。
栈为空,就相当于栈中唯一的元素出栈,出栈后,SP=SP+2 ,SP 原来为 000EH ,加 2 后SP=10H 所以,当栈为空的时候,SS=1000H,SP=10H。
换个角度看
任意时刻,SS:SP 指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素
所以SS:SP 只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元的偏移地
址+2
栈最底部字单元的地址为1000:000E,所以栈空时,SP=0010H
pop 指令的执行过程
pop ax
将SS:SP指向的内存单元处的数据送入ax 中;
SP = SP+2,SS:SP指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。
pop 指令的执行过程
注意:
出栈后,SS:SP指向新的栈顶 1000EH ,pop 操作前的栈顶元素,1000CH 处的2266H 依然存在 ,但是,它已不在栈中。
当再次执行push 等入栈指令后,SS:SP移至1000CH ,并在里面写入新的数据,它将被覆盖。 栈顶超界的问题
SS 和SP 只记录了栈顶的地址,依靠SS 和SP 可以保证在入栈和出栈时找到栈顶。
可是,如何能够保证在入栈、出栈时,栈顶不会超出栈空间?
都将发生栈顶超界问题,栈顶超界是危险的。
因为我们既然将一段空间安排为栈 ,那么在栈空间之外的空间里很可能存放了具有其他用途的数据、代码等,这些数据、代码可能是我们自己的程序中的,也可能是别的程序中的。(毕竟一个计算机系统并不是只有我们自己的程序在运行)
但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。(但如果是刻意的……那么……呵呵……)
我们当然希望CPU 可以帮我们解决这个问题,比如说在CPU 中有记录栈顶上限和下限的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围 ,然后 ,CPU 在执行push 指令的时候靠检测栈顶上限寄存器,在执行pop 指令的时候靠检测栈顶下限寄存器保证不会超界。
实际情况:8086CPU 中并没有这样的寄存器。
8086CPU 不保证对栈的操作不会超界。
这就是说 8086CPU 只知道栈顶在何处(由SS:SP指示),而不知道读者安排的栈空间有多大。 这点就好像CPU 只知道当前要执行的指令在何处(由CS:SP指示)而不知道读者要执行的指令有多少。
从这两点我们可以看出
8086CPU 的工作机理,只考虑当前的情况:
当前栈顶在何处;
当前要执行的指令是哪一条。
结论
我们在编程的时候要自己操心栈顶超界的问题 ,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致的超界;
执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。
push 和pop 指令是可以在寄存器和内存之间传送数据的。
栈空间当然也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间。 push 和pop 指令的格式
push 寄存器:将一个寄存器中的数据入栈
pop 寄存器:出栈,用一个寄存器接收出栈的数据
例如:
push ax
pop bx
push 段寄存器:将一个段寄存器中的数据入栈
pop 段寄存器:出栈,用一个段寄存器接收出栈的数据
例如:
push ds
pop es
push 内存单元:将一个内存单元处的字入栈(栈操作都是以字为单位)
pop 内存单元:出栈,用一个内存字单元接收出栈的数据
例如:
push [0]
pop [2]
指令执行时 ,CPU 要知道内存单元的地址,可以在 push 、pop 指令中给出内存单元的偏移地址,段地址在指令执行时,CPU 从ds 中取得。
问题3.7
编程:将10000H~1000FH 这段空间当作栈,初始状态是空的,将 AX 、BX 、DS 中的数据入栈。 思考后看分析。
push 、pop 指令
问题3.8
编程:
(1)将10000H~1000FH 这段空间当作栈,初始状态是空的;
(2)设置AX=001AH,BX=001BH;
(3)将AX 、BX 中的数据入栈;
(4)然后将AX 、BX 清零;
(5)从栈中恢复AX 、BX 原来的内容。
思考后看分析。
push 、pop 指令
从上面的程序我们看到,用栈来暂存以后需要恢复的寄存器中的内容时 ,出栈的顺序要和入栈的顺序相反,因为最后入栈的寄存器的内容在栈顶 ,所以在恢复时,要最先出栈。
问题3.9
编程:
(1)将10000H~1000FH 这段空间当作栈,初始状态是空的;
(2)设置AX=002AH,BX=002BH;
(3)利用栈 ,交换 AX 和 BX 中的数据。
思考后看分析。
push 、pop 指令
问题3.10
我们如果要在10000H 处写入字型数据2266H ,可以用以下的代码完成:
mov ax,1000H
mov ds,ax
mov ,ax,2266H
mov [0],ax
看题目:
补在10000H 处写入字型数据2266H 。
__________
__________
__________
mov ax,2266H
push ax
要求:不能使用“mov 内存单元, 寄存器”这类指令
思考后看分析。
我们看需补全代码的最后两条指令,将ax 中的2266H 压入栈中,也就是说,最终应由push ax将2266H 写入10000H 处。
问题的关键就在于:如何使push ax访问的内存单元是10000H 。
Push 指令是入栈指令。(注意执行过程)
完整的程序:
mov ax,1000H
mov ss,ax
mov sp,2
mov ax,2266H
push ax
push 、pop 实质上就是一种内存传送指令,可以在寄存器和内存之间传送数据,与mov 指令不同的是,push 和pop 指令访问的内存单元的地址不是在指令中给出的,而是由SS:SP指出的。 同时,push 和pop 指令还要改变 SP 中的内容。
我们要十分清楚的是,push 和pop 指令同mov 指令不同,CPU 执行mov 指令只需一步操作,就是传送,而执行push 、pop 指令却需要两步操作。
执行push 时:先改变SP ,后向SS:SP处传送。
执行pop 时:先读取SS:SP处的数据,后改变SP 。
注意
push 、pop 等栈操作指令,修改的只是SP 。也就是说,栈顶的变化范围最大为:0~FFFFH。 提供:SS 、SP 指示栈顶; 改变SP 后写内存的入栈指令; 读内存后改变SP 的出栈指令。
这就是8086CPU 提供的栈操作机制。
栈的综述
1. 8086CPU提供了栈操作机制,方案如下:
在SS ,SP 中存放栈顶的段地址和偏移地址;
提供入栈和出栈指令,他们根据SS:SP指示的地址,按照栈的方式访问内存单元。
2. push指令的执行步骤:
SP=SP-2;
向SS:SP指向的字单元中送入数据。
3. pop指令的执行步骤:
从SS:SP指向的字单元中读取数据;
SP=SP-2。
4. 任意时刻,SS:SP指向栈顶元素。
5. 8086CPU只记录栈顶,栈空间的大小我们要自己管理。
6. 用栈来暂存以后需要恢复的寄存器的内容时 ,寄存器出栈的顺序要和 入栈的顺序相反。
7. push、pop 实质上是一种内存传送指令,注意它们的灵活应用。
栈是一种非常重要的机制,一定要深入理解,灵活掌握。