zoukankan      html  css  js  c++  java
  • 【汇编语言】02-寄存器

      一个典型的 CPU 由运算器、控制器、寄存器等器件组成,这些器件靠内部总线相连。前一篇博客说的总线,相对于 CPU 内部来说是外部总线。内部总线实现 CPU 内部各个器件之间的联系,外部总线实现 CPU 和主板上其他器件的联系。

      简而言之,在 CPU 中:

      • 运算器进行信息处理;
      • 寄存器进行信息存储;
      • 控制器控制各种器件工作;
      • 内部总线连接 CPU 内部的各种器件,在其间进行数据传送。

      对于汇编程序员而言,CPU 中的主要部件是寄存器。程序员可以用指令读写寄存器,并通过改变各种寄存器中的内容来实现对 CPU 的控制。

      不同的 CPU 中,寄存器的个数、结构都不尽相同。8086 CPU 有 14 个寄存器,分别是:AX、BX、CX、DX、SI、DI、SP、BP、IP、CS、SS、DS、ES、PSW。我们先列举在这里,后面逐渐详细说明。

    1. 通用寄存器

      8086 CPU 的所有寄存器都是 16 位的,可以存放两个字节。AX、BX、CX、DX 这 4 个寄存器通常用来存放一般性数据,称之为通用寄存器

      我们以 AX 为例,来看看寄存器的逻辑结构:

    16 位寄存器的逻辑结构

      一个 16 位的寄存器可以存放 16 位的数据,其最大值能达到 216 - 1。数据在寄存器中存放情况示例如下图:

    16 位数据在寄存器中的存放情况示例

      8086 CPU 的前代产品,使用的都是 8 位寄存器。为了保证兼容,使得基于上一代 CPU 编写的程序稍加修改就能在 8086 上运行,8086 的 AX、BX、CX、DX 这 4 个通用寄存器,都可以分成两个独立的 8 位寄存器使用。如下图所示:

     

    16 位寄存器拆分为两个 8 位寄存器示意

      • AX 可以分为 AH 和 AL;
      • BX 可以分为 BH 和 BL;
      • CX 可以分为 CH 和 CL;
      • DX 可以分为 DH 和 DL。

      以 AX 为例,来分析 8086 的 16 位寄存器分为两个 8 位寄存器的情况。AX 的低 8 位构成 AL 寄存器,高 8 位构成 AH 寄存器,AH 和 AL 是可以独立使用的寄存器。8 位寄存器能存储的数据最大值是 28 - 1。如下图所示:

    16 位寄存器及所分成的两个 8 位寄存器的数据存储情况

     

    2. 字在寄存器中的存储

      处于兼容性考虑,8086 CPU 可以一次性处理两种尺寸的数据:

      • 字节(byte):一个字节由 8 个 bit 组成,可以存在 8 位寄存器中;
      • 字(word):一个字由两个字节组成,分别称为高位字节和低位字节。一个字可以存放在一个 16 位寄存器中,高低字节自然也就是放在高 8 位寄存器和低 8 位寄存器中。

    字的组成示意

    3. 几条汇编指令

      我们先给出以下几条指令,注意汇编指令和寄存器名称不区分大小写:

    汇编指令 控制 CPU 完成的操作 高级语言的语法描述
    mov ax,18 将18送入寄存器AX AX=18
    mov ah,78 将78送入寄存器AH AH=78
    add ax,8 将寄存器AX中的数值加上8 AX=AX+8
    mov ax,bx 将寄存器BX中的数据送入寄存器AX AX=BX
    add ax,bx 将AX和BX中的数值相加,结果存在寄存器AX中 AX=AX+BX

      接下来,我们假设 AX 和 BX 中的初始数据都是 0000H,看看执行指令后,寄存器中的数据改变。

    程序段中的指令 指令执行后AX中的数据 指令执行后BX中的数据
    mov ax,4E20H 4E20H 0000H
    add ax,1406H 6226H 0000H
    mov bx,2000H 6226H 2000H
    add ax,bx 8226H 2000H
    mov bx,ax 8226H 8226H
    add ax,bx 044CH 8226H

      执行完上述表格的最后一条指令后,ax + bx 的结果实际为 1044CH。但 ax 是 16 位寄存器,只能存放 4 位十六进制的数据,所以最高位的 1 被舍弃,结果为 044CH。

      我们再看一组指令(仍然假设 AX 和 BX 中的初始数据都是 0000H):

    程序段中的指令 指令执行后AX中的数据 指令执行后BX中的数据
    mov ax,001AH 001AH 0000H
    mov bx,0026H 001AH 0026H
    add al,bl 0040H 0026H
    add ah,bl 2640H 0026H
    add bh,al 2640H 4026H
    mov ah,0 0040H 4026H
    add al,85H 00C5H 4026H
    add al,93H 0058H 4026H

      程序最后一条指令执行后,运算结果为 158H,但 al 是作为一个独立的 8 位寄存器来使用的,和 ah 没有关系,只能存放两位十六进制的数据。所以最高位的 1 丢失,ax 中的数据为 0058H。要注意,这里说的丢失是指进位制不能在 8 位寄存器中保存,但 CPU 并不是真的丢弃这个进位值。这一点后续会详细讨论。

      在进行数据传送或运算时,指令的两个操作对象的位数应当是一致的。以下这几种指令是错误用法,务必注意:

    1 mov ax,bl
    2 mov bh,ax
    3 mov al,2000
    4 add al,100H

    4. 物理地址

      CPU 访问内存单元时,要给出内存单元的地址。所有的内存单元构成的存储空间是一个一维线性空间,每个内存单元在这个空间中都有唯一的地址,即物理地址

      CPU 通过地址总线送入存储器的,必须是一个内存单元的物理地址。CPU 向地址总线上发出物理地址之前,必须先在内部形成这个物理地址。不同的 CPU 有不同的物理地址形成方式。

    5. 16 位结构的 CPU

      前文提到,8086 是 8 位机,也可以说,8086 是 16 位结构的 CPU。

      简而言之,16 位结构(也表达为 16 位机、字长为 16 位)描述了一个 CPU 具有以下几方面的特性:

      • 运算器一次最多可以处理 16 位的数据;
      • 寄存器的最大宽度为 16 位;
      • 寄存器和运算器之间的通路为 16 位。

      8806 内部能够一次性处理、传输、暂时存储的信息的最大长度是 16 位的。

    6. 8086 CPU 给出物理地址的方法

      8086 CPU 有 20 位地址总线,寻址能力达到 1MB。但 8086 CPU 是 16 位结构,如果不经处理,只能发出 16 位地址。因此,8086 CPU 在内部合成两个 16 位地址来形成 20 位物理地址。

    8086 CPU 相关部件逻辑结构示意

      如上图所示,当 8086 CPU 要读写内存时:

      • CPU 中的相关部件提供两个 16 位地址——段地址偏移地址
      • 将段地址和偏移地址通过内部总线送入地址加法器
      • 地址加法器将两个 16 位地址合成为一个 20 位的物理地址;
      • 地址加法器通过内部总线将 20 位物理地址送入输入输出控制电路;
      • 输入输出控制电路将 20 位物理地址送上地址总线;
      • 20 位物理地址被地址总线传送到存储器。

      地址加法器采用如下公式,将段地址和偏移地址合成物理地址:

    物理地址 = 基础地址(段地址 X 16) + 偏移地址

      为了更好地理解地址加法器的工作原理,再附上一张示意图:

    地址加法器工作原理示意

      所谓“段地址 X 16”,在计算机学科中有个更常用的描述——左移 4 位。这是因为计算机中的所有信息都是以二进制的形式存储的,从二进制的角度来理解,左移一位,数值就会 X 2。

    7. 段地址 X 16 + 偏移地址 = 物理地址的本质

      本质含义是:CPU 在访问内存时,用一个基础地址(段地址 X 16)和一个相对于基础地质的偏移地址相加,给出内存单元的物理地址。

      这种寻址功能是“基础地址+偏移地址=物理地址”这种寻址模式的一种具体实现方案。

    8. 段的概念

      “段地址”这种说法可能会使人误认为内存被划分成了一个一个的段,每一个段有一个段地址。我们不应这样来理解。

      内存并没有分段,段的划分来自 CPU。8086 CPU 这种“基础地址 + 偏移地址 = 物理地址”的方式使得我们能用分段的方式来管理内存。如下图所示,我们可以认为:地址 10000H~100FFH 的内存单元组成一个段,该段的起始地址是 10000H,段地址是 1000H,大小为 100H。我们也可以认为地址 10000H~1007FH、10080H~100FFH 的内存单元组成两个段,基础地址分别为 10000H、10080H,段地址为 1000H、1008H,大小均为 80H。

    内存分段示意

      在编程时可以根据需要,将若干地址连续的内存单元看作一个段,用段地址 X 16 定位段的起始地址(基础地址),用偏移地址定位段中的内存单元。因为段的起始地址是段地址 X 16,所以段的起始地址一定是 16 的倍数。偏移地址为 16 位,寻址能力是 64KB,所以一个段的长度最大为 64KB。

      CPU 可以用不同的段地址和偏移地址形成同一个物理地址。譬如 CPU 要访问 21F60H 单元,则其给出的段地址 SA 和偏移地址 EA 满足 SA X 16 + EA = 21F60H 即可。在 8086 CPU 中,存储单元的地址用两个元素来描述,即段地址和偏移地址。但我们通常不会说“数据在 21F60H 内存单元中”,而是用以下两种说法来表示:

      • 数据存在内存 2000:1F60 单元中;
      • 数据存在内存的 2000H 段中的 1F60H 单元中。

    9、 段寄存器

      8086 CPU 在访问内存时要由相关部件提供内存的段地址和偏移地址,送入地址加法器合成物理地址。段地址在 8086 CPU 的段寄存器中存放,8086 CPU 有四个段寄存器:CS、DS、SS、ES。当 8086 CPU 要访问内存时,由这四个段寄存器提供内存单元的段地址。

    10. CS 和 IP

      CS 和 IP 是 8086 CPU 中最关键的两个寄存器,它们指示了 CPU 当前要读取指令的地址。CS 为代码段寄存器,IP 为指令指针寄存器。在任意时刻,设 CS 中的内容为 M,IP 中的内容为 N,则 8086 CPU 将从内存 MX16+N 单元开始,读取一条指令并执行。也可以这样表示:8086 机中,任意时刻,CPU 将 CS:IP 指向的内容当作指令执行。

    8086 PC 读取和执行指令的相关部件

      上图展示了 8086 CPU 读取、执行指令的工作原理,说明如下:

      • 8086 CPU 当前状态:CS 中的内容为 2000H,IP 中的内容为 0000H;
      • 内存 20000H~20009H 单元存放着可执行的机器码;
      • 内存 20000H~20009H 单元中存放的机器码对应的汇编指令如下:
        • 此处需补图

      通过以上图解,可知 8086 CPU 工作过程简述如下:

      • 从 CS:IP 指向的内存单元读取指令,读取的指令进入指令缓冲器;
      • IP=IP+所读取指令的长度,从而指向下一条指令;
      • 执行指令。
      • 转到步骤 1,重复以上过程。

      在 8086 CPU 上电启动或复位后,CS 和 IP 被设置为 CS=FFFFH,IP=0000H,即 CPU 从内存 FFFF0H 单元中读取指令执行,FFFF0H 单元中的指令是 8086 CPU 开机后执行的第一条指令。

      前文提到,在内存中,指令和数据没有任何区别,都是二进制信息。CPU 在工作时把有些信息看作指令,有些信息看作数据。那么 CPU 根据什么将内存中的信息看作指令?CPU 将 CS:IP 指向的内存单元中的内容看作指令。换句话说,如果内存中的一段信息曾被 CPU 执行过的话,它所在的内存单元必然被 CS:IP 指向过。

    11. 修改 CS、IP 的指令

      在 CPU 中,程序员能用指令读写的部件只有寄存器,程序员通过改变寄存器中的内容实现对 CPU 的控制。CPU 从何处执行指令是由 CS、IP 中的内容决定的,程序员可以通过改变 CS、IP 中的内容来控制 CPU 执行目标指令。

      前文提到,8086 CPU 中大多数寄存器的值都可以用 mov 来改变,mov 指令也被称为传送指令。但 mov 指令不能用于设置 CS、IP 的值,8086 CPU 为 CS、IP 提供了另外的指令来改变它们的值,此类指令统称为转移指令。

      若想同时修改 CS、IP 的内容,可用形如”jmp 段地址:偏移地址“的指令完成。例如:

    1 jmp 2AE3:3      # 执行后 CS=2AE3H,IP=0003H,CPU将从2AE33H处读取指令
    2 jmp 3:0B16      # 执行后 CS=0003H,IP=0B16H,CPU将从00B46H处读取指令

      若想仅修改 IP 的内容,可用形如”jmp 某一合法寄存器“的指令来完成。例如:

    1 jmp ax          # 将ax寄存器中的值送入IP

    12. 代码段

      前面讲到,对于 8086 PC 机,可以根据需要,将一组内存单元定义为一个段。我们可以将长度为 N(N ≤ 64)的一组代码,存在一组地址连续、起始地址为 16 的倍数的内存单元中。因此,我们可以认为这段内存是用来存放代码的,从而定义了一个代码段。

      譬如我们有一段长度为 10 字节的指令,存放在 123B0H~123B9H 的一组内存单元中。我们可以认为 123B0H~123B9H 这段内存就是用来存放代码的,是一个代码段。其段地址为 123BH,长度为 10 字节。

      将一段内存当作代码段,只是我们编程时的一种安排。CPU 只认被 CS:IP 指向的内存单元中的内容为指令,并不会自动执行我们定义在代码段中的指令。要让 CPU 执行我们放在代码段中的指令,必须要将 CS:IP 指向代码段中的第一条指令的首地址。

  • 相关阅读:
    【python】利用python+tkinter做一个简单的智能电视遥控器
    【selenium】自动化测试中,元素无法点击定位等问题的解决:js的使用方法
    《Selenium 2自动化测试实战 基于Python语言》中发送最新邮件无内容问题的解决方法
    单线程代码事例
    死锁的实例
    大公司的Java面试题集
    xml文件有误
    android开发艺术探索
    Java 获取APK安装程序的包名
    内部类中class声明地方不同,效果不一样
  • 原文地址:https://www.cnblogs.com/murongmochen/p/14106677.html
Copyright © 2011-2022 走看看