第三章 寄存器(内存访问)

引言

在第2章中,我们主要从CPU如何执行指令的角度讲解了8086CPU的逻辑结构、形成物理地址的方法、相关的寄存器以及一些指令。

这一章中,我们从访问内存的角度继续学习几个寄存器。

3.1 内存中字的存储

3.2 DS 和 [address]

  • mov指令可完成三种传送功能:

    1. 将数据直接送入寄存器;

      例如:

      mov ax, 2

      将数据2送入寄存器ax

    2. 将一个寄存器中的内容送入另一个寄存器中;

      例如:

      mov bx, ax

      将寄存器ax内容送入寄存器bx

    3. 将一个内存单元中的内容送入一个寄存器。

      例如:

      mov al, [0]

      将偏移地址为0的内存单元的内容送入寄存器al

  • 例如:我们要读取10000H单元的内容可以用如下程序段进行:

    mov bx, 1000H

    mov ds, bx

    mov al, [0]

    上面三条指令将 10000H(1000:0)中的数据读到 al 中。

  • 从哪个内存单元到哪个寄存器中呢?

    mov指令的格式:

    mov 寄存器名, 内存单元地址

    “[...]” 表示一个内存单元,“[...]” 中的 0 表示内存单元的偏移地址。

  • 那么内存单元的段地址(DS)是多少呢?

    执行指令时,8086 CPU自动取 DS 中的数据为内存单元的段地址。

  • 如何用mov指令从10000H中读取数据?

    • 10000H表示为1000:0(段地址:偏移地址)
    • 将段地址1000H放入DS
    • 用 mov al, [0] 完成传送(mov指令中的[]说明操作对象是一个内存单元,[]中的0说明这个内存单元的偏移地址是0,它的段地址默认放在DS中)
  • 把 1000H 直接送入 DS,可以吗?

    mov ds, 1000H

    不可以。

    8086 CPU不支持将数据直接送入段寄存器的操作,ds是一个段寄存器。

    我们一定要通过通用寄存器(ax、bx、cx、dx)才能完成把1000H送入DS。如:

    mov bx, 1000H

    mov ds, bx

  • 问题:写几条指令,将 al 中的数据送入内存单元 10000H?(思考后分析)

    分析问题本质:怎么将数据从寄存器(al)送入内存单元?

    结论:

    mov bx, 1000H

    mov ds, bx

    mov [0], al

3.3 字的传送

  • 因为8086 CPU是16位结构,有16根数据线,所以,可以一次性传送16位的数据,也就是一次性传送一个字。

    比如:

    mov bx, 1000H

    mov ds, bx

    mov ax, [0]

    mov [0], cx

    注:前两条指令确定了段地址;第三条指令解读为“1000:0处的字型数据送入ax”,ax是16位寄存器,所以默认存过去的数据也是16位,如果是al或者ah,则默认存过去的数据是8位,因为al或者ah是8位寄存器;第四条指令解读为“cx中的16位数据送到1000:0处”。

  • 问题3.3:内存中的情况如下

    内存地址 内容
    10000H 23
    10001H 11
    10002H 22
    10003H 66
    内存情况示意

    写出下面指令执行后寄存器ax、bx、cx中的值。

    mov ax, 1000H

    mov ds, ax

    mov ax, [0]

    mov bx, [2]

    mov cx, [1]

    add bx, [1]

    add cx, [2]

    答案:

    注:本实验的重点是寄存器存储的数据宽度为16位,即2个字节,也就是单字。

    ax = 1123

    bx = 8833

    cx = 8833

    TIM截图20180802170856

  • 问题3.4:内存中的情况如下

    内存地址 内容
    10000H 23
    10001H 11
    10002H 22
    10003H 11
    内存情况示意

    写出下面指令执行后寄存器ax、bx、cx中的值。

    mov ax, 1000H

    mov ds, ax

    mov ax, 11316

    mov [0], ax

    mov bx, [0]

    sub bx, [2]

    mov [2], bx

    答案:

    ax : 2c34H

    bx : 1b12H

    cx : 8833H

    TIM截图20180802172642

3.4 mov、add、sub指令

  • 已学mov指令的几种形式:

    • mov 寄存器, 数据

      mov ax, 6

    • mov 寄存器, 寄存器

      mov bx, ax

    • mov 寄存器, 内存单元

      mov ax, [0]

    • mov 内存单元, 寄存器

      mov [0], ax

    • mov 段寄存器, 寄存器

      mov ds, ax

    • mov 寄存器, 段寄存器

      mov ax, ds

    注:这里的寄存器指通用寄存器。

  • add 和 sub 指令同 mov 一样,都有两个操作对象。

    • add 寄存器, 数据

      比如:add ax, 8

    • add 寄存器, 寄存器

      比如:add bx, ax

    • add 寄存器, 内存单元

      比如:add ax, [0]

    • add 内存单元, 寄存器

      比如:add [0], ax

    • sub 寄存器, 数据

      比如:sub ax, 8

    • sub 寄存器, 寄存器

      比如:sub bx, ax

    • sub 寄存器, 内存单元

      比如:sub ax, [0]

    • sub 内存单元, 寄存器

      比如:sub [0], ax

  • 它们可以对段寄存器(DS)进行操作吗?

    mov ds, ax (yes)

    mov ax, ds (yes)

    add ds, ax (no)

    add ax, ds (no)

3.5 数据段

  • 前面讲过,对于8086PC机,我们可以根据需要将一组内存单元定义为一个段(可以是代码段、数据段等。用CS指向它,它就是代码段;用DS指向它,它就是数据段。)

  • 我们可以将一组长度为N(N<=64K)、地址连续、起始地址为16的倍数的内存单元,当作专门存储数据的内存空间,从而定义了一个数据段。

    比如,我们用123B0H~123B9H这段空间来存放数据:

    • 段地址:123BH
    • 长度:10字节
  • 如何访问数据段中的数据呢?

    将一段内存当作数据段,是我们在编程时的一种安排,我们可以在具体操作的时候,用 ds 存放数据段的段地址,再根据需要,用相关指令访问数据段中的具体单元。

    例如,我们要将123B0H~123BAH的内存单元定义为数据段,我们现在要累加这个数据段中的前3个单元(单元指字节,字型指字)中的数据,代码如下:

    mov ax, 123BH

    mov ds, ax ;将123BH送入ds中,作为数据段的段地址。

    mov al, 0 ;用al存放累加结果

    add al, [0] ;将数据段第一个单元(偏移地址为0)中的数据加到al中

    add al, [1] ;将数据段第二个单元(偏移地址为1)中的数据加到al中

    add al, [2] ;将数据段第三个单元(偏移地址为2)中的数据加到al中

    若要求写几条指令,累加数据段中的前3个字型数据。则代码如下:

    mov ax, 123BH

    mov ds, ax ;将123BH送入ds中,作为数据段的段地址。

    mov ax, 0 ;用ax存放累加结果

    add ax, [0] ;将数据段第一个单元(偏移地址为0)中的数据加到ax中

    add ax, [2] ;将数据段第二个单元(偏移地址为2)中的数据加到ax中

    add ax, [4] ;将数据段第三个单元(偏移地址为4)中的数据加到ax中

    注意:一个字型数据占两个单元(也就是一个字占两个字节),所以偏移地址是0、2、4。

3.1节~3.5节 小结

  1. 字在内存中存储时,要用两个地址连续的内存单元来存放,字的低位字节存放在低地址单元中,高位字节存放在高地址单元中。
  2. 用mov指令要访问内存单元,可以在mov指令中只给出单元的偏移地址,此时,段地址默认在DS寄存器中。
  3. [address] 表示一个偏移地址为 address 的内存单元。
  4. 在内存和寄存器之间传送字型数据时,高地址单元对应高8位寄存器,低地址单元对应低8位寄存器。
  5. mov、add、sub是具有两个操作对象的指令。jmp是具有一个操作对象的指令。
  6. 可以根据自己的推测,在Debug中实验指令的新格式。

3.6 栈

后进先出

3.7 CPU提供的栈机制

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

8086CPU提供入栈和出栈指令:(最基本的)

  • push (入栈)

    例如:

    push ax

    将寄存器 ax 中的数据送入栈中;

  • pop (出栈)

    例如:

    pop ax

    从栈顶取出数据送入 ax。

  • 因为8086结构内部寄存器都是以16位(一个字)单位进行操作和运算(一个内存单元仍然是8位,即一个字节)。所以,8086CPU的入栈和出栈操作都是以字为单位进行的。当然,我们现在的windows CPU和酷睿CPU都是以32/64位进行运算的,但是,只要掌握了16位的原理,其它举一反三。

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

  • 两个疑问

    1. CPU如何知道一段内存空间被当作栈使用?
    2. 执行push和pop的时候,如何知道哪个单元是栈顶单元?

    对两个疑问的分析

    CPU如何知道当前要执行的指令所在的位置?

    答:寄存器CS和IP中存放着当前指令的段地址和偏移地址。8086CPU中,有两个寄存器:

    • 段寄存器SS 存放栈顶的段地址
    • 寄存器SP 存放栈顶的偏移地址

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

  • push 指令的执行过程

    push ax

    1. SP = SP - 2
    2. 将ax中的内容送入SS:SP指向的内存单元处,SS:SP此时指向新栈顶。

    屏幕快照 2018-08-05 10.29.25

    push 指令的执行过程

    问题 3.6

    如果我们将10000H~1000FH这段空间当作栈,初始状态栈是空的,此时,SS = 1000H,SP = ?

    分析:当SP = 0010H屏幕快照 2018-08-05 10.31.51

    总结:

    • 我们将10000H~1000FH这段空间当作栈段,SS = 1000H,栈空间大小为16字节,栈最底部的字单元地址为1000:000E。

    • 任意时刻,SS:SP指向栈顶。当栈中只有一个元素的时候,SS = 1000H,SP = 000EH。

    • 栈为空,就相当于栈中唯一的元素出栈,出栈后,SP = SP + 2,SP原来为 000EH,加 2 后 SP = 0010H。

    换个角度看:

    任意时刻,SS:SP指向栈顶元素,当栈为空的时候,栈中没有元素,也就不存在栈顶元素。所以,SS:SP只能指向栈的最底部单元下面的单元,该单元的偏移地址为栈最底部的字单元(16位)的偏移地址 + 2,栈最底部字单元的地址为 1000:000E,所以栈空时,SP = 0010H。

  • pop 指令的执行过程

    pop ax

    1. 将 SS:SP 指向的内存单元处的数据送入 ax 中;
    2. SP = SP + 2,SS:SP 指向当前栈顶下面的单元,以当前栈顶下面的单元为新的栈顶。

    图示:

    屏幕快照 2018-08-05 11.31.53

    注意:出栈后,SS:SP 指向新的栈顶1000EH。pop 操作前的栈顶元素,1000CH处的2266H依然存在,但是,它已不在栈中。当再次执行push等入栈指令后,SS:SP 移至 1000CH ,并在里面写入新的数据,2266H将被覆盖。

3.8 栈顶超界的问题

SS 和 SP 只记录了栈顶的地址,依靠 SS 和 SP 可以保证在入栈和出栈时找到栈顶。可是,如何能够保证在入栈、出栈时,栈顶不会超出栈空间?

当栈满的时候再使用push指令入栈,栈空的时候再使用pop指令出栈,都将发生栈顶越界问题。

  • 栈顶越界是危险的:

    因为我们既然将一段空间安排为栈,那么在栈空间之外的空间里很可能存放了具有其它用途的数据、代码等,这些数据、代码可能是我们自己的程序中的,也可能是别的程序中的。(毕竟一个计算机系统并不是只有我们自己的程序在运行)

但是由于我们在入栈出栈时的不小心,而将这些数据、代码意外地改写,将会引发一连串的错误。(如果是刻意为之,将会引发黑客攻击)

我们当然希望CPU可以帮我们解决这个问题。

比如说在CPU中有记录栈顶上限和下限的寄存器,我们可以通过填写这些寄存器来指定栈空间的范围,然后,CPU在执行push指令的时候靠检测栈顶上限寄存器;在执行pop指令的时候靠检测栈顶下限寄存器保证不会越界。

实际情况:8086CPU中并没有这样的寄存器。

8086CPU不保证对栈的操作不会越界。这就是说,8086CPU只知道栈顶在何处(由SS:SP指示),而不知道读者安排的栈空间有多大。这点就好像,CPU只知道当前要执行的指令在何处(由CS:IP指示)而不知道要执行的指令有多少。也就是说,8086CPU的工作机理,只考虑当前的情况:

  • 当前栈顶在何处;
  • 当前要执行的指令是哪一条。

结论:

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

3.9 push、pop指令

  • push和pop指令是可以在寄存器和内存之间传送数据的。

    栈空间当然也是内存空间的一部分,它只是一段可以以一种特殊的方式进行访问的内存空间。

  • push和pop指令的格式(1)

    • push 寄存器

      将一个寄存器中的数据入栈

    • pop 寄存器

      出栈,用一个寄存器接收出栈的数据

    例如:

    push ax

    pop bx

  • push和pop指令的格式(2)

    • push 段寄存器

      将一个段寄存器中的数据入栈

    • pop 段寄存器

      出栈,用一个段寄存器接收出栈的数据

    例如:

    push ds

    pop es

    注:段寄存器都是以s结尾,如ds、es等;通用寄存器都是以x结尾,如ax、bx等。

  • push和pop指令的格式(3)

    • push 内存单元

      将一个内存单元处的入栈(栈操作都是以字为单位的。因为寄存器是16位的)

    • pop 内存单元

      出栈,用一个内存单元接收出栈的数据

    例如:

    push [0]

    解读:把内容存到 ds:0 这个内存单元中。入栈

    pop [2]

    解读:把 ds:2 这个内存单元中的内容取出。出栈

    注:指令执行时,CPU要知道内存单元的地址,可以在push、pop指令中给出内存单元的偏移地址,段地址在指令执行时,CPU从ds中取得。

    对于数据段,将它的段地址存放在DS段寄存器中,用mov、add、sub等访问内存单元的指令时,CPU就将我们定义的数据段中的内容当作数据段来访问;对于代码段,将它的段地址存放在CS段寄存器中,将段中第一条指令的偏移地址放在IP中,这样CPU就将执行我们定义的代码段中的指令;对于栈段,将它的段地址存放在SS段寄存器中,将栈顶单元的偏移地址存放在SP寄存器中,这样CPU在需要进行栈操作的时候,比如执行push、pop指令等,就将我们定义的栈段当作栈空间来用。

    可见,不管我们如何安排,CPU将内存中某段内存当作代码,是因为 CS:IP 指向了那里;CPU将某段内存当作栈,是因为 SS:SP 指向了那里。

    问题 3.7

    编程:

    将10000H~1000FH这段空间当作栈,初始状态是空的,将AX、BX、DS中的数据入栈。

    分析:我们之前讲过,数据是不能直接和段地址发生关系的。

    解答:

    mov ax, 1000H

    mov ss, ax

    mov sp, 0010H

    push ax

    push bx

    push ds

    注:

    • mov ss, ax

      设置栈的段地址,SS = 1000H,不能直接向段寄存器SS送入数据,所以用ax中转。

    • mov sp, 0010H

      设置栈顶的偏移地址,因为栈为空,所以SP = 0010H。如果对栈为空时,SP的设置还有疑问,复习问题 3.6

    • mov ax, 1000H

      mov ss, ax

      mov sp, 0010H

      这三条指令设置栈顶地址。编程中要自己注意栈的大小。

    问题 3.8

    编程:

    1. 将10000H~1000FH这段空间当作栈,初始状态栈是空的;
    2. 设置AX = 001AH,BX = 001BH;
    3. 将AX、BX中的数据入栈;
    4. 然后将AX、BX清零;
    5. 从栈中恢复AX、BX原来的内容。

    思考后看教材 P65 的分析

    问题 3.9

    教材 P65

    问题 3.10

    教材 P66

3.10 栈段

posted @ 2018-09-17 20:21  houhaibushihai  阅读(665)  评论(0编辑  收藏  举报