鸿 网 互 联 www.68idc.cn

王爽之《汇编语言》学习重点七

来源:互联网 作者:佚名 时间:2015-08-28 08:04
3.7 CPU提供的栈机制 栈是一种具有特殊的访问方式的储存空间。栈的数据采用后进先出(LIFO)原则。 现今的CPU中都有栈的设计 ,8086CPU也不例外。 8086CPU提供相关的指令来以栈的方式访问内存空间。这意味着,我们基于8086CPU编程的时候,可以将一段内存当做栈

3.7 CPU提供的栈机制

       栈是一种具有特殊的访问方式的储存空间。栈的数据采用后进先出(LIFO)原则。

       现今的CPU中都有栈的设计,8086CPU也不例外。8086CPU提供相关的指令来以栈的方式访问内存空间。这意味着,我们基于8086CPU编程的时候,可以将一段内存当做栈来使用。

      8086CPU提供入栈和出栈指令,最基本的两个是PUSH(入栈)和POP(出栈)。8086CPU的入栈和出栈操作都是以字为单位进行的

      例,我们可以将10000H~1000FH这段内存当做栈来使用:

 

        

 

 

    22

    11

    66

   22

    23

    01

10000H                                       执行指令:

 

                                                                   mov ax,0123H                        

                                                                   push ax

10009H                                                      mov bx,2266H

1000AH                                                     push bx

1000BH                                                     mov cx,1122H

1000CH                                                     push cx

1000DH                                                     pop ax

1000EH                                                      pop bx 

1000FH                                                      pop cx

 

注意,字型数据用两个字节存放,高地址单元存放高8位,低地址单元存放低8位。

        当执行完前三步的进栈操作后,分别将ax , bx, cx 寄存器中的数值压入栈中,如上图所示。

        当进行后三步的出栈操作时,pop ax ,ax = 1122H , 此时将栈顶内存空间的值送到ax中。pop bx , pop cx 同理。

      但是,CPU如何知道10000H~1000FH这段空间被当作栈来使用?当我们push ax 时,要将寄存器中的内容放在当前栈顶单元的上方,成为新的栈顶元素;pop ax等指令执行时, 要从栈顶单元中取出数据,送入寄存器中。那么CPU是怎么知道栈顶元素的呢?

     如同CPU知道当前执行的指令所在位置,用CS、IP 来存放当前指令的段地址和偏移地址。在8086CPU中,有两个寄存器,段寄存器SS和寄存器SP, SS用来存放栈顶的段地址,SP存放偏移地址。任意时刻,SS:SP指向栈顶元素。当执行push和pop指令时,CPU从SS何SP中得到栈顶的地址。

         由此,我们可以完整描述push和pop指令的功能了,例如push ax :

         push ax 的执行,由以下两步完成:

        (1) sp = sp - 2 , SS:SP 指向当前栈顶前面的单元,以当前栈顶前面的单元为新的栈顶;

        (2) 将ax中的内容送入SS:SP执行的内存单元,SS:SP此时指向新栈顶。

        由图可以看出, 8086CPU中,入栈时,栈顶从高地址向低地址方向增长。

        接下来,我们描述POP指令功能。 例如 pop ax :

        pop ax 的执行过程和push ax 刚好相反,由以下两步完成:

        (1) 将SS:SP指向的内存单元的数据送入ax中;

        (2) SP = SP + 2, SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。

       注意:

       当元素出栈后,SS:SP指向新的栈顶,POP操作前的栈顶元素依然存在,但是,它已不在栈中。当再次执行push等入栈操作后,SS:SP移至该位置,并在里面写入新的数据,它将被覆盖。

 

3.8 栈顶超界的问题

      8086CPU不保证我们对栈的操作不会超界。这就是说,8086CPU只知道栈顶在何处(由SS:SP指示),而不知道读者安排的栈空间有多大。这点就好像,CPU只知道当前要执行的指令在何处(由CS:IP指示),而不知道读者要执行的指令由多少。从这两点上我们可以看出8086CPU的工作原理,它只考虑当前的情况;当前的栈顶在何处、当前要执行的指令是哪一条。

      我们在编程时要自己操心栈顶超界的问题,要根据可能用到的最大栈空间,来安排栈的大小,防止入栈的数据太多而导致超界;执行出栈操作的时候也要注意,以防栈空的时候继续出栈而导致的超界。

 

 

 


 

问题3.10

      在10000H处写入字型数据2266H,完成下列代码:

      ___________

      ___________

      ___________

      mov ax,2266H

      push ax

 

 

分析:

      push ax 是入栈指令,它将在栈顶之上压入新的数据。一定要注意它的执行过程: 首先,将记录栈顶偏移地址的SP寄存器中的内容减2,使SS:SP指向新的栈顶单元,然后再将寄存器中的数据送入SS:SP指向的新的栈顶单元。则得完整程序如下:

     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等栈操作指令,修改的值是SP。也就是说,栈顶的变化范围最大为: 0~FFFFH。

     SS、SP指示栈顶;改变SP后写内存的入栈指令;读内存后改变SP的出栈指令。这就是8086CPU提供的栈操作机制。


栈的综述

(1) 8086CPU提供了栈操作机制,方案如下:

         在SS、SP中存放栈顶的段地址和偏移地址;

         提供入栈和出栈指令,它们根据SS:SP指示的地址,按照栈的方式访问内存单元。

(2) push指令的执行步骤:  1) sp = sp - 2;   2) 向SS:SP指向的字单元中送入数据。

(3) pop  指令的执行步骤:  1) 从SS:SP指向的字单元中读取数据;  2)SP = SP - 2;

(4) 任意时刻,SS:SP指向栈顶元素。

 

(5) 8086CPU只记录栈顶,栈空间的大小我们要自己管理。防止栈操作超界。

(6) 用栈来暂存以后需要恢复的寄存器的内容时,寄存器出栈的顺序要和入栈的顺序相反。(后进先出)

(7) push、pop 实质上是一种内存传送指令,注意它们的灵活应用。

栈是一种非常重要的机制,一定要深入理解,灵活掌握。

 


 

 3.10 栈段

      前面讲过(2.8节),对于8086CPU机,在编程时,我们可以根据需要,将一组内存单元定义为一个段。我们可以将长度为N(N<=64K)的一组连续地址、起始地址为16的倍数的内存单元,当作栈空间来用。如,我们将10010H~1001FH这段长度为16字节的内存空间当作栈来用,以栈的方式进行访问。这段空间就可以称为一个栈段,段地址为1000H,大小为16字节。

      将一段内存当做栈段,仅仅是我们在编程时的一种安排,CPU并不会自动将该段内存以栈方式进行访问。依然要靠SS:SP指向我们定义的栈段。

 


段的综述

      我们可以将一段内存定义为一个段,用一个段地址指示段,用偏移地址访问段内的单元。这完全是我们自己的安排;

      我们可以用一个段存放数据,将它定义为“段数据”;

      我们可以用一个段存放代码,将它定义为“段代码”;

      我们可以用一个段当做栈,将它定义为“栈段”;

      我们可以这样安排,但若要让CPU按照我们的安排来访问这些段,就要:

            对于数据段,将它的段地址放在DS中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据来访问。

            对于代码段,将它的段地址放在CS中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令;

            对于栈段,将它的段地址放在SS中,将栈顶单元的偏移地址放在SP中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当作栈空间来用。

      可见,不管我们如何安排,CPU将内存中的某段内容当作代码,是因CS:IP指向了那里;CPU将某段内存当作栈,是因为SS:SP指向了那里。我们一定要清楚,什么是我们的安排,以及如何让CPU按我们的安排行事。要非常清楚CPU的工作原理,才能在控制CPU来按照我们的安排运行的时候做到游刃有余。

      比如我们将10000H~10001FH安排为代码段,并在里面存储如下代码:

      mov ax, 1000H

      mov ss,  ax

      mov sp,  0020H                                    ; 初始化栈顶

      mov ax,  cs

      mov ds,  ax                                           ;设置数据段地址

      mov ax,  [0]

      add  ax,  [2]

      mov bx,  [4]

      add bx,   [6]

      push ax

      push bx

      pop   ax

      pop   bx

 

      设置CS = 1000H,IP = 0, 这段代码将得到执行。可以看到,在这段代码中,我们又将10000H~1001FH安排为栈段和数据段。

      10000H~1001FH这段内存,既是代码段,又是栈段和数据段。

      一段内存,可以既是代码的储存空间,又是数据的储存空间,还可以是栈空间,也可以什么也不是。关键在于CPU中寄存器的设置,即:

CS、IP、SS、SP、DS的指向。    


实验总结:

 

      如代码:

      mov ax,2000

      mov ss,ax

      mov sp,10

      mov ax,3123

      push ax

      mov ax,3366

      push ax

      当在Debug中用T命令执行完第二步 mov ss,ax 该条指令时,后面的一条指令 mov sp,10 也被自动执行了。此时显示的下一条将被执行的指令是 mov ax,3123 ,而非 mov sp,10 。 不单是 mov ss,ax ,对于如:mov ss,bx, mov ss,[0],pop ss等指令都会发生这种紧跟的后面的一条指令被自动执行的情况。详细原因涉及到了后面要深入研究的内容: 中断机制

     即, Debug的T命令在执行修改寄存器SS的指令时,下一步指令也紧接着被自动执行。

网友评论
<