寄存器(内部访问)

内存中字的储存

我们之前提到过,字由两个字节组成,当我们用16位寄存器来储存时,把字分别储存在连续的两个内存单元中,高位字节放在高地址单元中,低位字节则放在低位地址单元中。

例如上图,0、1两个单元存放了20000(4E20H),这两个单元可以看成起始地址为0的字单元,这里提出了字单元这个概念:存放一个字的两个内存单元。同理,我们不止可以将0、1看作一个字单元,也可以将2、3看作一个字单元,任意两个连续的内存单元组成一个字都可以看成字单元,默认编号小的作为高地址。

DS和[address]

之前提到过,要读写一个单元时要先给出目标内存单元的地址,8086CPU中内存物理地址由段地址x16+偏移地址形成,其实这句话并不完整,8086CPU中DS寄存器通常用来存放要访问的数据的段地址,之前说到过CS寄存器与IP寄存器联用指示要读取的指令的内存单元的物理地址,而DS寄存器区别于CS寄存器的是DS寄存器与如 BX、SI、DI寄存器联用用来指示数据的内存单元的物理地址,这也就解决了内存中所有数据不论是数据还是指令都以二进制数形式存在却能运用的问题。

由于8086CPU的硬件设置问题,8086CPU不支持将数据直接送入段寄存器的操作,我们不能直接使用mov ds,1000H这个指令直接将1000H这个数据送入ds这个段寄存器中,但是我们可以利用一个合法寄存器当作中转,先使用mov ax,1000H将1000H送入ax中,再使用mov ds,ax寄存器中的内容送入ds段寄存器中。

mov bx,1000H     mov ds,bx      mov al,[0]这三条指令实现了将10000H(1000:0)中的数据读取到a1中。

分步看一下,前两步按照上面所说的是将1000H的数据移动到ds段寄存器中,重要是第三步:首先看一下mov al,[0]这个格式al是移动目标寄存器,[0]内存单元的偏移地址,但是只有偏移地址是不能指示一个内存单元的,这时候在指令执行时,8086CPU会自动取DS中的数据为内存单元的段地址,这个段地址和[0]中括号中的偏移地址联合指向目标地址,前面两步我们将1000H送入了ds中,所有现在段地址就是1000H与偏移地址0运算之后得到10000H这个物理地址,所以我们将读取10000H这个地址的内存单元中的数据给al,就实现了整个过程

当然也可以实现将寄存器中的数据读到内存单元的操作mov bx,1000H     mov ds,bx      mov [0],al

同样偏移地址是0,段地址是1000H,这三个指令的意义就是将al寄存器中的数据读取到10000H内存地址的操作。

那么接下来就是用dosbox来实际操作一下这三条指令:

首先改成虚拟c盘,使用dosbox内的debug功能-r查看寄存器的内容

为了方便后续区分,原本10000这个地址的内存里面是没有内容的,我们先写入2222给10000地址的内存单元,我们依靠cx寄存器,先将2222写入cx寄存器

使用指令a写入编程语言,在用t指令依次执行,这里说一下073F:0100就是CS:IP,即执行指令的地址,我们使用t指令执行了第一条指令mov bx,1000   可以看到bx寄存器的内容变成了1000,这条指令占了3个内存单元,所以下面073F:1000自增了3,它后面的那个汇编指令就是内存单元紧接着的下一条指令

我们接着使用t指令把所有的指令执行完

可以看到ax确实变成了2222,我们达到了目的。

字的传送

因为8086CPU是只有16条数据总线,所以一次只能传输16位数据,也就是16位二进制数,2字节,1个字。只要mov指令给出16位的就存其就可以进行字的传送了,详情可以看上面的实验,差不多指令是相同的。

mov、add、sub指令

mov指令可以有一下几种形式:

mov 寄存器,数据    例如:mov ax,8   将8这个数据写入ax寄存器

mov 寄存器,寄存器    例如:mov ax,bx   将寄存器bx的内容写入ax

mov 寄存器,内存单元    例如:mov ax,[0]    将内存单元地址为ds:0000地址的内容写入ax

mov 内存单元,寄存器    例如:mov [0],ax    将ax内的内容写入地址为ds:0000的地址单元

mov 段寄存器,寄存器    例如:mov ds,ax    将ax的内容写入ds中

具体操作也可以看上面的实验,都有对这些语句进行使用

sub和add指令和mov指令一样,都要两个操作对象,sub和add指令正好是相反的,add指令是添加内容,sub是减去内容。

sub和add指令与mov指令的一点区别就是sub和add指令都不能作用于ds段寄存器,而mov指令可以。

add 寄存器,数据    比如:add ax,8    向寄存器ax的内容加上8
add 寄存器,寄存器    比如:add ax,bx    向寄存器ax加bx寄存器的内容写入bx
add 寄存器,内存单元  比如:add ax, [0]  向ax写入ds:0000地址的内存单元的内容相加写ds:0000
add 内存单元,寄存器    比如:add [0],ax    向ds:0000地址的内存单元于ax的内容相加写入ax
sub 寄存器,数据    比如:sub ax, 9    
sub 寄存器,寄存器    比如:sub ax,bx
sub 寄存器,内存单元    比如:sub ax, [0]
sub 内存单元,寄存器    比如:sub[0],ax   

数据段

之前提到过,为了方便管理,我们可以将内存分成一段一段的,也就是之前说过的代码段,数据段的代码段类似,我们使用代码段这个观念时,将一组长度N<64kb的地址连续且起始地址为16的倍数的内存单元用来存放一串代码,同样的也可以用来存放数据,这就是数据段,和代码段一样这是我们的自己的安排,当要读取这一数据时,要我们手动给出起始地址,CPU才能进行读取。

我们实际操作一下,将数据段前三个单元的数据加到bx中,先定义一下ds的值,后续我们访问1000:0000地址

添加一些内容给我们要用于添加的地址1000:0000,记住一个字占两个字节,也就是两个内存单元

可以看到偏移地址递增1时,原本偏移地址是0,增加这个地址的数据的话就是两个字节11和22,但是由于小端序存储规则,高位存高地址,所以变成了2211,后面添加偏移地址为1的数据就跳过了第一个字节也就是11,变成添加即2211+3322=5533,所以以上实现的能说是字的添加,因为一次添加两个字节就是一个字,但是添加的字之间是有重复的,如果要不重复的话偏移地址就要间隔2也就是add bx,[0]    add bx,[2]。

栈:栈是一种具有特殊访问方式的储存空间,它的规则是最先进入这个空间的数据是最早出去的。

假设经如图方式放入数据,那么要让高等数学出来就要先让上面那两个出来。

CPU提供的栈机制

现今的CPU都有栈的设计,8086CPU提供相关指令来以栈的方式范文内存空间,所以在编程时我们可以将一段内存以栈的形式使用。

8086CPU提供的入栈和出栈指令最基本的是PUSH(入栈),POP(出栈);例如push ax表示将寄存器ax中的数据压入栈中,pop ax则是从栈顶取出数据送入ax,8086CPU的出栈和入栈操作都是以字为单位的也就是一次性操作两个字节,push和pop指令作用对象都只能是寄存器不能是具体数据。

下面我们来实际操作一些这几个指令

先把指令写好

执行先四个指令

将入栈指令都执行完,可以看到ax,bx,cx内的数据

执行剩余出栈操作,可以看到寄存器中的值有了相应的变化,其实出栈操作和mov指令有异曲同工之妙,出栈操作时将出栈的内容与原本存在于寄存器中的内容替换了,mov其实也是这样的。

显然我们在运用栈时,要将出栈的数据送到对应寄存器中,那么我们肯定要先知道栈顶的数据是什么,才能加以运用,在寄存器中,我们通常都是用地址来标记某一个内存单元,之后加以运用,标记栈顶的内存单元也是如此。在8086CPU中,以段寄存器SS和寄存器SP联合:SS:SP来指向栈顶元素,采用和前面提到过的CS:IP和DS:IP一样的运算法则,在执行入栈和出栈的操作时,CPU先从SS:SP获得栈顶的地址。

SS:SP指向的地址是栈顶的元素,所以在执行pop或push操作时要加减2来获得对应的操作位置。

从上图可以看到,原本SS:SP指向的栈顶地址是1000E,在执行push指令时,要先将偏移地址减去2,得到新的地址1000C才有空间进行入栈操作,入栈高位放在高地址,即22放在1000D。pop操作就是和铺设、操作相反而已。

由于每次出栈和入栈都是两个字节为单位,所以就 存在两种特殊情况:

假设地址10000H~1000F为栈空间,以上图举例

1、当栈中只有一个字节的数据时,由于SS:SP始终指向栈顶的元素所以此时SP=000E,当执行pop指令时,栈中只有一个元素,所以只能这一个元素出栈,出栈后SP+2=10010H此时的栈顶也就是栈空间的最底部10010H。

2、当栈中没有元素时,也就不存在栈顶元素,所以站栈就只能指向栈最底部的单元下的单元,即10010H,这就是栈空时栈顶的地址。

栈顶超界问题

我们知道push和pop指令可以往栈输入会输出元素,但是栈的空间时一定的,如果栈的空间已经被数据填满了,那我们再执行push指令就会造成溢出的情况,同理如果栈内已经没有元素了,那么我们再执行pop执行就会造成栈向下溢出的情况。

栈的溢出是十分危险的,我们划分一段内存空间作为栈,那么与这段栈空间相邻的内存可能存放着其他重要的数据,而栈溢出可以将这些数据提取或覆盖掉造成泄露或破坏,8086CPU没有标记栈的上限和下限,他只知道SS:SP指向栈顶地址。

push和pop指令

栈只是一种特殊的数据空间有特殊的存放形式,但是它的本质还是内存空间,push和pop指令是一种能在寄存器和内存之间传输数据的指令,所以它的格式有:push ax或push[0],其中[]中的偏移地址从ds获得

栈段

和前面的数据段和代码段原理类似,都是我们为了方便管理自己定义的一段单元内存作为栈段,CPU当然不会这样认为它只认SS:SP指向的栈顶地址并执行push或pop操作,所以我们在使用栈段时,要定义SS寄存器为我们想要利用的那个地址,结构同样是16结构,所以一个栈段同样最大是64kb。