从0开始进入汇编的世界之每天理解一篇汇编帖-第二节

urfyyyy   ·   发表于 2020-06-17 00:23:02   ·   技术文章

前言

第一节我们大概了解了汇编语言是什么样子的,CPU与内存的关系,三种总线有什么作用,这节我们一起来重点了解一下寄存器之CPU工作原理,本次内容对于无基础的非计算机学科的哥哥们来说稍有复杂,请仔细思考。

上一节链接
从0开始进入汇编的世界之每天理解一篇汇编帖-第一节

一、寄存器(CPU工作原理)

一个CPU由三部分器件组成:控制器、运算器、寄存器,这些器件由内部总线互相连接
内部总线与外部总线的区别:
内部总线实现CPU内部各个器件之间的联系
外部总线实现CPU和主板上其他器件的联系

什么是寄存器?
对于整个PC机来说,有存储器系统,如内存。那对于CPU的内部来说,它也需要暂时存放需要处理的一些数据,寄存器的主要作用就是来存放CPU需要处理的这些临时数据。

8086CPU有14个寄存器,它们的名称为:AX,BX,CX,DX,SI,DI,SP,BP,IP,CS,SS,DS,ES,PSW。这些寄存器都有各自的作用。

1.1、通用寄存器

8086CPU的所有寄存器都是16位,8bit(位)=1个字节,所以可以存放2个字节,也就是一个字。这里引出一个“字”的概念,1字为16位,等于2个字节。
AX,BX,CX,DX通常用来存放一般性的数据,我们称之为通用寄存器。
我们以AX为例,来看一下寄存器的逻辑结构,如下图:

那么如图,一个16位的寄存器最多可以存放多少个数据呢?
我们不难看出,可以存放最多2的16次方,也就是65536个数据,且数据的最大值是65535(因为是从0开始的,所以是65536-1)
我们再看看16位数据在寄存器中的存放情况,10进制数18,二进制位10010,如果不熟悉进制转换的同学,可以使用计算器,win+r,运行cmd,输入calc回车开启计算器,点击左上角,选择“程序员”

选择DEC(十进制),输入18,即可看到下面BIN(二进制数)为10010

下图演示18这个数在寄存器AX中的存放情况

而8086CPU的上一代,8088CPU,寄存器是8位的,为保证兼容性,这四个寄存器都可以分为2个独立的8位寄存器使用。
AX分为 AH和AL
BX分为 BH和BL
CX分为 CH和CL
DX分为 DH和DL
其中H代表high表示高8位,L代表low表示低8位。

下图演示了8086CPU的16位寄存器分为2个独立的8位寄存器的情况

若是兼容以前的8位,则只使用低8位来使用,高8位因为什么都没有,所以全部填0

小结
AX的低8位(0位-7位)构成了AL寄存器,AX的高8位(8位-15位)构成了AH寄存器。AL和AH都是可以独立使用的8位寄存器,也可以合起来一起使用

下图演示了16位寄存器及它所分成的2个8位寄存器的数据存储情况

如上图,如果将AH单独使用,则其存储的值为01001110,也就是16进制的4E,10进制的78;如果将AL单独使用,则其存储的值为00100000,也就是16进制的20,10进制的32;如果将AH和AL一起使用,则AX存储的值为0100111000100000,也就是16进制的4E20,10进制的20000。

同样的,一个8位寄存器所能存储的最大值为2^8-1,也就是255,总共能存256个值。

1.2、“字”在寄存器中的存储

刚才有说过“字”这个概念,一个字可以存储在一个16位寄存器中,这个字的高位字节和低位字节自然就存储在这个寄存器的高8位寄存器和低8位寄存器中。

任何数据,到了计算机中都是以二进制的形式存放的,为了描述不同的问题,又经常将它们用不同的进制来表示,虽然计算机只认识0和1,但我们人类看到这么低级的0和1来表示一个数肯定是受不了的,不利于我们的思维,所以呢,我们使用16进制来进行表示,因为16进制既没有二进制那么冗长,又和二进制有一个很好的转换。

这里是说,16进制数的1位,就相当于二进制数的4位,如0100111000100000这个二进制数可以表示为4(0100)E(1110)2(0010)0(0000)。4位二进制数刚好表示1位16进制数。这样相对CPU而言,它好我也好。

加深理解:比如还是上面的这个数,10进制的20000,写作16进制位4E20,就可以直观的看出,这个数据是由4E和20这两个8位数据构成,如果AX中存放了这个数4E20,则AH中存放的是4E,AL中存放的是20。这种表示方法便于许多问题的直观分析。所以很多软件和工具也在使用,如Debug

1.3、几条汇编指令

首先声明,汇编指令区分与C/C++,汇编指令是不区分大小写的。

  1. 图上已经解释的很清楚,我就不再做二次解释了。让我们看看上图中,ax寄存器中最后的值是多少
  2. - 首先mov ax,18 这个时候将18这个值送入了ax中,即ax=18,也就是二进制的00010010
  3. - 接着将78送入了ah,也就是将01001110送入了ah,此时ax的值为01001110 00010010,也就是19986,为16进制的4E12
  4. - 紧接着,add ax,8 ax加了8,也就是加了1000,此时ax01001110 00011010,也就是19994,为16进制的4E1A
  5. - 接下来mov ax,bx bx的值给了ax,上面没有说明bx的值,我们假设为0,则此时ax0
  6. - 最后add ax,bx 也就是ax=ax+bx,最后ax的值为0

我们再来做一题,用以加深印象和理解

咱们可以拿出计算器来逐步计算,最后的结果是1044C,居然有5位,那么在寄存器上是放不下的,因为16位寄存器只能存放16位2进制数,也就是4位16进制数字,此时有5位,那么ax中最后的值是多少呢,在这个情况下,由于1044C的最高位“1”不能再ax中保存,则最高位的1会被舍弃,此时ax中的值为044C,至于舍弃了不会有问题吗?舍弃的值去哪里了呢,我们以后再做讨论。

我们再来做最后一题来加深理解和印象

  1. - 第一条指令执行后,ax的值为001A
  2. - 第二条指令执行后,bx的值为0026
  3. - 第三条指令执行,将bx的低8位也就是26ax的低81A相加,并存入al中,此时al的值为26+1A=40ax的值为0040
  4. - 接着将bx的低826ax的高800相加,并交给ah,此时ah的值为26ax的值为2640
  5. - 继续执行指令,将ax的低840bx的高800相加,并交给bh,此时bh40bx的值为4026
  6. - 紧接着,将ax的高8ah变为0,此时ah00,ax0040
  7. - 来到倒数第二条指令,将ax的低8al中的40,与85相加并交给al,此时al的值为40+85=C5ax00C5
  8. - 最后一条指令,加ax的低8al中的C593相加,并交给al,此时alC5+93=158,还是一样的道理,al8位寄存器,只能保存216进制数,所以158中的最高位“1”同样被舍弃,此时al58ax值为0058

注意,此时al是作为一个独立的寄存器使用,和ah没有关系,CPU在执行这条指令时,会认为ah和al是两个独立的寄存器。不要错误的认为,add al,93的指令产生的158中的1,会被进位保存在ah中,add al,93进行的是8位计算。
如果,指令写位:add ax,93 则此时低8位的158中的1,就会进位保存在ah中,CPU在执行这条指令时,会认为只有一个寄存器,那就是16位的ax,进行的是16位计算,指令add ax,93执行后,ax中的值为0158。因为使用的是1个16位的寄存器,相当于ax中的16位数据00C5和另一个16位数0093,结果就是16位的0158

  1. 在进行数据传送或者计算时,要注意指令的两个操作对象的位数应该是一致的,例如:
  2. mov ax,bx (都是16位,正确)
  3. mov bx,cx (都是16位,正确)
  4. mov ax,18 (此时将18H看作0018,是一个16位数,正确)
  5. mov al,18 (此时将18H看作一个8位数,因为18这个16进制数没有超过8位,正确)
  6. add ax,bx (都是16位,正确)
  7. add ax,20000 (此时20000也是一个16位数,正确)
  8. mov ax,bl (试图将一个8位寄存器中的值移向一个16位寄存器中,错误)
  9. mov bh,ax (试图将一个16位寄存器中的值移向一个8位寄存器中,错误)
  10. mov al,20000 8位寄存器中可存放的最大值为2^8-1=2552000016位,错误)
  11. add al,100 100高于8位,将一个高于8位的数移动到8位寄存器中,错误)
  12. 错误原因均为指令的2个操作对象的位数不一致。

1.4、物理地址

CPU访问内存单元时,要给出内存单元的地址,所有的内存单元构成的存储空间是一个一维的线性空间,每一个内存单元在这个空间中都有唯一的地址,我们将这个唯一的地址称为:物理地址
CPU通过地址总线送入存储器的,必须是一个内存单元的物理地址。


重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点重点


概括来说,16位结构描述了一个CPU具有下面几个方面的特性:
**

  • 运算器一次最多可以处理16位的数据(也就是一次处理2个字节,64位CPU一次处理8个字节);
  • 寄存器的最大宽度为16位;
  • 寄存器和运算器之间的通路位16位;
    **

16位结构的CPU,能够一次性处理、传输、暂时存储的信息最大长度是16位。去到内存单元的地址在通过地址总线传输之前,必须要在CPU中处理、传输、暂时存储。而8086CPU虽然是16位的,但是却有20位地址总线,可以传输20位的地址,达到1MB(2的20次方)的寻址能力,而因CPU为16位,那从这个结构来看,如果地址从内部简单地发出,那么只能传送16位的地址,表现出寻址能力只有64KB(2的16次方)。

这样就造成了一种矛盾,一种冲突,而制造8086CPU的工程师是怎么解决这个矛盾的呢?
8086CPU采用一种在内部使用两个16位地址合成的方法,来形成一个20位的物理地址。
相关的逻辑结构如下图

上述的2个16位地址就是段地址和偏移地址,通过地址加法器合成,组成了一个20位的物理地址,再通过20位的地址总线传输这个20位的物理地址,去内存寻址。
细分如下
1、CPU中的相关部件提供两个16位地址,一个称为段地址,一个称为偏移地址
2、段地址和偏移地址通过内部总线送入一个叫作地址加法器的部件
3、地址加法器将两个16位地址合成为一个20位物理地址
4、地址加法器通过内部总线将20位物理地址送入输入输出控制电路
5、输入输出控制电路将20位物理地址送上地址总线
6、20位物理地址被地址总线传送到存储器

1.5、地址加法器的合成(“段地址x16+偏移地址=物理地址”的本质含义)

那么地址加法器是如何合成的呢?它采用 段地址 x 16 + 偏移地址 = 物理地址 的方法用段地址和偏移地址合成物理地址
我们来看一下如果CPU要访问地址为123C8的内存单元时,地址加法器的工作过程

首先CPU相关部件提供两个16位地址,段地址1230和偏移地址00C8,送入地址加法器中,地址加法器将段地址向左偏移一位,也就是乘以16(16这个数的16进制就是10H),变为12300(这个数就变为20位的16进制数了,对应20位的地址)。然后由12300再加上偏移地址00C8,就变为了123C8,最后传输出去。

我们展开来讲一下偏移一位,举个简单的例子

  • 10进制数字10,向左偏移一位,变成了100,是不是乘以了10;
  • 二进制数字0向左偏移一位,乘以2之后还是0,然而一个二进制数字1,向左偏移1位,变成了10,也就是2,此时是乘以了2。
  • 不难理解,一个16进制数向左偏移一位,就是乘以了16,也就是乘以了16进制的10H
  • 一个X进制的数字向左偏移一位,就是乘以了X

关于段地址 x 16 + 偏移地址 = 物理地址 也就是 基础地址 + 偏移地址 = 物理地址。段地址 x 16 就是基础地址。
原书提供了2个很形象的例子。

第一个例子(基础地址+偏移地址=物理地址):
比如说,学校,体育馆,图书馆同在一条笔直的单行路上,学校位于路的起点,图书馆位于路的终点。如下图:

这时候你问我从学校怎么去图书馆呢,我描述这个问题的话,可以采用以下两种方式告诉你
1、从学校走2826米到图书馆。这个2826米就可以认为是图书馆的物理地址
2、从学校走2000米到体育馆,接着再从体育馆走826米到图书馆。这时候第一个距离2000米,是相对于学校的一个基础地址,第二个距离826米,是相对于基础地址的偏移地址(以基础地址为起点的地址)
第一种方式是直接给出物理地址2826米,而第二种方式是用基础地址+偏移地址来得到物理地址。

第二个例子(段地址 x 16 + 偏移地址 = 物理地址)
还是以这个学校到图书馆举例。我们这个时候加一些限制条件,你和我都是哑巴,我们需要通过我口袋里的几个纸条来对话,显而易见,我们需要一张能容纳4个数字的纸条才能写下这个地址。

然而很不巧,我身上带的纸条都比较小,只能够写下3个数字,那这样的话,我只能用下图的方式告诉你这个地址。

第一张纸条上写上了200米(段地址),在第二张纸条上写下了826米(偏移地址),然后我提前和你有过约定,你拿到我的纸条后,首先要把第一张纸条上的数字乘以10,然后再加上第二张纸条上的数字,就能得到最终的数字。那么你拿到纸条后就会做这样的计算:200(段地址)x 10 + 826(偏移地址)= 2826(物理地址)
打个比方来说,8086CPU就是这样一个只能提供两张3位数据纸条的CPU。

1.6、段的概念

错误认识:内存被划分成了一个一个的段,每一个段有一个段地址
正确认识:其实,内存并没有分段,段的划分来自于CPU,由于8086CPU用“基础地址(段地址x16)+偏移地址=物理地址”的方式给出内存单元的物理地址,使得我们可以用分段的方式来管理内存


如上图所示,虽然左右两部分是同一个地址空间,但从我们操作的人员的角度上来看,我们可以把它认为成一个段,也可以把它认为成两个段
我们既可以认为(上图左边部分),地址10000H - 100FFH的内存单元组成了一个段,段的起始地址(基础地址)为10000H,那么它的段的地址就是1000H,大小为100FFH - 10000H = FFH(255),因为是计算机从0开始计数,所以0-255有256个数字,也就是100H,大小为100H;
我们还可以认为(上图右边部分),10000H - 1007FH的内存单元组成了一个段,10080H - 100FFH的内存单元组成了一个段,他们的起始地址(基础地址)为10000H 和 10080H,段地址为1000H和1008H,那么它们的大小通过计算都为80H

两点需要注意
1、段地址 x 16 必然是16的倍数,所以一个段的起始位置(基础地址)也一定是16的倍数
2、偏移地址是16位,16位地址的最大值(寻址能力)位2的16次方,也就是64KB,所以一个段的长度最大是64KB


小结

  • CPU访问内存单元时,必须向内存提供内存单元的物理地址
  • 8086CPU在内部用段地址和偏移地址移位相加的方式形成最终的物理地址

观察下图的地址,你有什么发现呢?

结论:CPU可以通过不同的段地址和偏移地址来形成同一个物理地址。
举个例子:同样一个数字5,你可以1+4,2+3,3+2,4+1来获得,都是可以的

也就是说,如果CPU要访问一个物理地址为21F60的内存单元,则它只要给出的段地址(SA)x 16 + 偏移地址(EA)= 21F60 即可。

那么如果给定一个段地址,仅通过变化偏移地址来进行寻址,最多可以定位多少个内存单元呢?
结论:偏移地址为16位,所以变化范围是0000 - FFFF,仅用偏移地址来寻址最多可以寻找到64KB个内存单元。
比如给定一个段地址为1000,用偏移地址0000 - FFFF寻址,则物理地址的范围是10000 - 1FFFF


在8086PC机中,存储单元的地址用两个元素来描述,即段地址和偏移地址
“数据在21F60内存单元中”。这句话一般我们不这样讲,正确的说法是下面两种
1、数据存在内存2000:1F60单元中 (常用)
2、数据存在内存2000段中的1F60单元中

可以根据需求,将地址连续、起始位置(基础地址)位16的倍数的一组内存单元定义为一个段。

二、检测点

1.写出每条汇编指令执行后相关寄存器的值
mov ax,62627 AX=__

mov ah,31H AX=__

mov al,23H AX=__

add ax,ax AX=__

mov bx,826CH BX=__

mov cx,ax CX=__

mov ax,bx AX=__

add ax,bx AX=__

mov al,bh AX=__

mov ah,bl AX=__

add ah,ah AX=__

add al,6 AX=__

add al,al AX=__

mov ax,cx AX=__


2.只能使用目前学过的汇编指令,最多使用4条指令,编程计算2的4次方


3.给定段地址为0001H,仅通过变化偏移地址,CPU的寻址范围为_


4.有一数据存放在内存20000H单元中,现给定段地址为SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为_,最大为_
反过来思考一下,当段地址给定为多少时,CPU无论如何变化偏移地址,都无法寻址到20000这个单元?







三、检测点答案

1.
mov ax,62627 AX=__

62627转换为16进制,为F4A3H,故AX=F4A3H

mov ah,31H AX=__

ah=31H,则AX=31A3H

mov al,23H AX=__

al=23H,则AX=3123H

add ax,ax AX=__

AX=AX+AX=6246H

mov bx,826CH BX=__

BX=826CH

mov cx,ax CX=__

CX=AX=6246H

mov ax,bx AX=__

AX=BX=826CH

add ax,bx AX=__

ax=ax+bx=104D8H,但是实际只有04D8H,因为AX放不下那么多

mov al,bh AX=__

因为bx=826CH,所以bh=82H,al=bh=82H,又因为AX原本等于04D8H,
ah不变,则AX=0482H

mov ah,bl AX=__

bl=6CH,ah=6CH,之前AX=8282H,现在AL不变,AH变,则AX=6C82H

add ah,ah AX=__

ah=ah+ah=D8H 之前AX=6C82H,al不变,则现在AX=D882H

add al,6 AX=__

6换为16进制,06H,AX之前为D882H,AH未变,AL变为88H则现在AX=D888H

add al,al AX=__

al=88H+88H=110H,AL只能存放8位,110位12位,此时溢出就舍弃,所以al=10H,之前AX=D888H,AH未变,AX=D810H

mov ax,cx AX=__

CX=6246H,ax=cx=6246H

————————————————

2.只能使用目前学过的汇编指令,最多使用4条指令,编程计算2的4次方

目前我们学过的指令:mov,add

mov是数据传送指令,把一个数据的源地址传送到目标地址(寄存器间也是一样),如mov ax,2 那么ax=2

add,两数相加,比如add ax,bx 那么ax=ax+bx

答案:

  1. mov ax,2--------------ax=2
  2. add ax,ax--------------ax=2+2=4
  3. add ax,ax--------------ax=4+4=8
  4. add ax,ax--------------ax=8+8=16

————————————————

3.给定段地址为0001H,仅通过变化偏移地址,CPU的寻址范围为

在8086CPU中使用“基础地址(段地址 x 16)+偏移地址=物理地址”的方式给出内存单元的物理地址。
段地址为0001H
基础地址为00010H
偏移地址最小为0000H
偏移地址最大为FFFFH
所以,CPU的寻址范围为00010H 到 1000fH

————————————————

4.有一数据存放在内存20000H单元中,现给定段地址为SA,若想用偏移地址寻到此单元。则SA应满足的条件是:最小为_,最大为_

已知物理地址为20000H
CPU偏移地址的取值范围为0000H 到FFFFH
则段地址最大为(20000H-0000H)/16=2000H
段地址最小为(20000H-FFFFH)/16=(10001)/16
由于段地址1000的最大寻址范围为1FFFF,因此最小的满足物理地址为20000H条件的情况下,SA的段地址应比1000大1
则SA应满足的条件,最小为1001H最大为2000H

给出解题过程:

  1. 物理地址=段地址X16 + 偏移地址
  2. 偏移地址最大时,SA的值最小
  3. 20000H = SA x 16 + FFFFH
  4. 2000H = SA + FFFFH / 16 (两边的数同时除以16,也可以理解为同时偏移一位)
  5. 2000H = SA + 65535 / 16 (将16进制数FFFF转化为十进制数65535)
  6. 2000H = SA + 4095 (将十进制数4095转化为16进制数FFF
  7. 2000H = SA + FFFH
  8. SA = 2000H - FFFH
  9. SA = 1001H
  10. 偏移地址最小时,SA的值最大
  11. 0000H = SA x 16 + 0H
  12. 2000H = SA + 0H / 16 (两边的数同时除以16,也可以理解为同时偏移一位)
  13. 2000H = SA + 0H
  14. SA = 2000H - 0H
  15. SA = 2000H

————————————————

用户名金币积分时间理由
Track-聂风 100.00 0 2021-03-12 16:04:42 文章通过

打赏我,让我更有动力~

1 条回复   |  直到 2021-3-15 | 779 次浏览

l836918621
发表于 2021-3-15

跟着镜姐学汇编

评论列表

  • 加载数据中...

编写评论内容
登录后才可发表内容
返回顶部 投诉反馈

© 2016 - 2024 掌控者 All Rights Reserved.