3.7 CPU提供的栈机制
栈是一种具有特殊的访问方式的储存空间。栈的数据采用后进先出(LIFO)原则。
现今的CPU中都有栈的设计,8086CPU也不例外。8086CPU提供相关的指令来以栈的方式访问内存空间。这意味着,我们基于8086CPU编程的时候,可以将一段内存当做栈来使用。
8086CPU提供入栈和出栈指令,最基本的两个是PUSH(入栈)和POP(出栈)。8086CPU的入栈和出栈操作都是以字为单位进行的。
例,我们可以将10000H~1000FH这段内存当做栈来使用:
注意,字型数据用两个字节存放,高地址单元存放高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的指令时,下一步指令也紧接着被自动执行。
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/syf442/archive/2009/08/05/4411566.aspx