80X86寄存器详解

引子 程序员

打算写几篇稍近底层或者说是基础的博文,浅要介绍或者说是回顾一些基础知识, 编程

天然,仍是得从最基础的开始,那就从汇编语言开刀吧, 数据结构

从汇编语言开刀的话,咱们必须还先要了解一些其余东西, 学习

像  CPU ,内存这些知识点仍是理解深入一点的比较好, 编码

因此这一篇博文就绕着 80x86  CPU 中寄存器的基础部分下手,至于其余的一些将会在后续的博文中介绍 。 spa

同时在这里说明一下,本篇博文介绍的算是比较详细的了,并且介绍的知识点也是比较多的,因此形成博文长度过长, 操作系统

若是有兴趣想了解这一块的话,还请自行斟酌好阅读比例,建议分 3 次以上阅览 。 翻译

读者定位 设计

本博文主要将介绍的是  8086 CPU 中的寄存器,既然是 8086 CPU 寄存器简介的话, 指针

天然,面向的是初级一些的读者,其中不会涉及太多难点,同时,全部的介绍,我也会尽量的从基础开始,

而后按部就班的介绍,同时也会尽可能的将知识点介绍详细,

介绍的过程当中也会涉及到一些汇编程序代码,固然,采用的是最简单的方式介绍而已,

本篇博文也就是回顾一些基础知识,读者主要定位于想对 8086 CPU 有所了解,

但愿对整个程序设计的底层有所了解的朋友,并且读者最好是拥有必定的计算机基础和汇编语言基础 。

 开头

首先浅要介绍一下  Intel  CPU 的发展史吧:

Intel CPU 系列,最初是 4 位微处理器 4004,而后到到 8 位微处理器的 8008 ,

再到 8 微微处理器 8080,以及稍后的 16 位微处理器 8086,

由 8086 开始,Intel 进入如今所谓的  x86  时代 。

Intel  8086 为 16 位  CPU ,而由于在 8086 以前的 CPU 都是 8 位 CPU,这样也就形成了不少的外设也只支持 8 位,

所以  Intel  紧接着就退出了 8 位的 8088 CPU,所以  Intel 8088 也就能够看作是 8086 的 8 位版本;

若是是但从汇编语言的角度上来讲,8086 和 8088 是没有区别的,即 8086 上跑的程序能够不加修改的移植到 8088 ,

8088 上跑的程序也能够不加修改的移植到 8086 上,

固然,仍是有些特殊的地方是不一样的,而这些基本上在这里能够忽略掉,

在 8088  CPU 以后,Intel  又推出了  80186 ,80286 ,这两款 CPU 均是 16 位  CPU ,

而对于 80186 来讲,其与 8086 的区别能够简单的看作是 80186 多了几条指令而已,

而 80286 则不一样,80286 的地址总线数目有了变化,

在 8086 , 8088 , 80186 上,CPU 的地址总线都是 20 根,便可最大寻址 220 即达到 1MB 的寻址能力,

而对于 80286 CPU 来讲,其地址总线数目达到了 24 根,从而最大寻址能力为 224 即 16MB,

因为支持更多的物理内存寻址,所以 80286 便开始成为了多任务,多用户系统的核心。

然后来,Intel  又推出了 80386 ,80386 为 32 位微处理器,Intel 80x86 家族的 32 位微处理器始于 80386;

同时 80386 也彻底兼容先前的 8086/8088,80186,80286,而且 80386 全面支持 32 位数据类型和 32 位操做,

而且 80386 的数据总线根数和地址总线根数均达到了 32 根,从而能够最大物理寻址为 232  即 4GB 。

而以后的 80486 也是 32 位微处理器,然后又出来了 Pentium 和 Pentium Pro 等等第五代微处理器,

这些处理器虽然也是 32 位微处理器,可是他们的数据总线和地址总线都有所扩展,

好比 Pentium 的数据总线达到 64 位,而 Pentium Pro 的地址总线位数达到了 36 位 。

             

好,关于 Intel CPU 的介绍就到这里了,下面就要开始回归中心,看 CPU 中的寄存器了,

首先,从学习的角度来讲,从 8086/8088  CPU 下手是不错的选择,而我这里选择的也是 8086 CPU 而已,

说实在的,像 80386 CPU 我也尚未研究过,像奔腾这些,呵呵,扯更远了,

说到底也就只能拿 8086 出来晒晒而已,固然,从 8086 开始也是学习的最佳路径 。

       

说了这么久,到底寄存器是什么呢?其实很简单,寄存器就是个存储信息的单元或者说是器件又或者说是容器而已,

就好比内存也是一个存储介质或者说是存储单元而已,其实寄存器从理解上来讲和内存差很少,

只不过寄存器(这里讨论的寄存器都是 CPU 中的寄存器,不包括外设上的寄存器)位于  CPU  内部,而内存位于 CPU 外部,

并且,寄存器比内存但是珍贵得多啊,就拿内存和硬盘来比,确定是内存在使用上珍贵得多,是 PC 中的稀有资源,

而寄存器是 CPU 中的稀有资源,内存和寄存器相比就像硬盘和内存相比同样 。

而对于一个汇编程序员来讲,CPU 中主要可使用的也就是寄存器而已,汇编程序员可使用指令来读写 CPU 中的寄存器,

从而能够实现对于 CPU 的控制,固然,不一样的 CPU ,寄存器的个数和结构都是不同的,

好比 8086 CPU 中,寄存器的个数也就 14 个而已,

而且 8086 CPU 中全部的寄存器的结构为 16 位,即一个寄存器中能够存放下 2B 即 2 个字节,

而到了 80386 CPU 中,寄存器的个数也比 8086 增多了,好比在 80386 中添加了系统地址寄存器等寄存器,

同时寄存器的结构也变了,好比在 80386 中绝大多数的寄存器为 32 位,而有些寄存器则是 16 位 。

8086  CPU 中寄存器总共为 14 个,且均为 16 位 。

即 AX,BX,CX,DX,SP,BP,SI,DI,IP,FLAG,CS,DS,SS,ES 共 14 个。

而这 14 个寄存器按照必定方式又分为了通用寄存器,控制寄存器和段寄存器。

通用寄存器:

AX,BX,CX,DX 称做为数据寄存器:

AX (Accumulator):累加寄存器,也称之为累加器;

BX (Base):基地址寄存器;

CX (Count):计数器寄存器;

DX (Data):数据寄存器;

SP 和 BP 又称做为指针寄存器:

SP (Stack Pointer):堆栈指针寄存器;

BP (Base Pointer):基指针寄存器;

SI 和 DI 又称做为变址寄存器:

SI (Source Index):源变址寄存器;

DI (Destination Index):目的变址寄存器;

控制寄存器:

IP (Instruction Pointer):指令指针寄存器;

FLAG:标志寄存器;

段寄存器:

CS (Code Segment):代码段寄存器;

DS (Data Segment):数据段寄存器;

SS (Stack Segment):堆栈段寄存器;

ES (Extra Segment):附加段寄存器;

           

           

通用寄存器

从上面能够知道,在 8086 CPU 中,通用寄存器有 8 个,分别是 AX,BX,CX,DX,SP,BP,SI,DI ,

至于为何给它们取名作通用寄存器,那是由于,这些个寄存器每个都有本身专门的用途,

好比 CX 做为计数寄存器,则是在使用 LOOP 指令循环时用来指定循环次数的寄存器,

若是它们每个都只有一个专用的做用,那就它们只能称之为专用寄存器了,

正是由于这些个寄存器还能够用来传送数据和暂存数据,因此才称它们为通用寄存器 。

下面就按顺序来一一介绍这几个通用寄存器了:

数据寄存器(AX,BX,CX,DX):

数据寄存器有 AX,BX,CX,DX 四个组成,

因为在 8086 以前的 CPU 为 8 位 CPU,因此为了兼容之前的 8 位程序,

在 8086 CPU 中,每个数据寄存器均可以当作两个单独的寄存器来使用,

由此,每个 16 位寄存器就能够当作 2 个独立的 8 位寄存器来使用了 。

AX 寄存器能够分为两个独立的 8 位的 AH 和 AL 寄存器;

BX 寄存器能够分为两个独立的 8 位的 BH 和 BL 寄存器;

CX 寄存器能够分为两个独立的 8 位的 CH 和 CL 寄存器;

DX 寄存器能够分为两个独立的 8 位的 DH 和 DL 寄存器;

除了上面 4 个数据寄存器之外,其余寄存器均不能够分为两个独立的 8 位寄存器 ;

注意在上面标志中的“独立”二字,这两个字代表 AH 和 AL 做为 8 位寄存器使用时,

能够看作它们是互不相关的,也就是看作两个彻底没有联系的寄存器 X 和 Y 便可,

好比指令   MOV   AH , 12H ,CPU 在执行时根本就不会知道 AL 中是什么鬼东西,由于它只认识  AH 。

下面给出一幅 16 位数据寄存器的结构图:

表示 16 位 寄存器 AX 能够表示成两个 8 位寄存器,

其中 AH 表示高位的 8 位寄存器,AL 表示低位的 8 位寄存器 。

image

AX 寄存器:

如上所说,AX 的另一个名字叫作累加寄存器或者简称为累加器,其能够分为 2 个独立的 8 位寄存器 AH 和 AL;

在写汇编程序时,AX 寄存器能够说是使用率最高的寄存器(不过,总共才那么 14 个寄存器,哪个不常用咯?),

既然 AX 是数据寄存器的话,那么理所固然,其能够用来存放普通的数据,因为其是 16 位寄存器,

天然也就能够存放 16 位数据,可是由于其又能够分为 2 个独立的 8 位寄存器 AH 和 AL ,

因此,在 AH 和 AL 中又能够独立的存放 2 个 8 位的数据,

能够有如下代码(即将 AX 当作普通的寄存器使用,便可以用来暂存数据):

MOV AX,1234H ;向寄存器 AX 传入数据 1234H MOV AH,56H ;向寄存器 AX 的高 8 位寄存器 AH 中传入数据 56H MOV AL,78H ;向寄存器 AX 的低 8 位寄存器 AL 中传入数据 78H
3 条语句的执行过程以下:
image

而既然 AX 又被称做为累加器,天然其还有一点点特殊的地方的:

AX 寄存器还具备的特殊用途是在使用 DIV 和 MUL 指令时使用,

DIV 在 8086 CPU 中是除法指令,而在使用除法的时候有两种状况,即除数能够是 8 位或者是 16 位的,

并且除数能够存放在寄存器中或者是内存单元中,而至于被除数的话,天然,应该由 AX 来代替了,

当除数是 8 位时,被除数必定会是 16 位的,而且默认是放在 AX 寄存器中,

而当除数是 16 位时,被除数必定是 32 位的,由于 AX 是 16 位寄存器,天然,放不下 32 位的被除数,

因此,在这里还须要使用另外一个 16 位寄存器 DX ,

其中 DX 存放 32 位的被除数的高 16 位,而 AX 则存放 32 位的被除数的低 16 位,

同时,AX 的做用还不只仅是用来保存被除数的,当除法指令执行完成之后,

若是除数是 8 位的,则在 AL 中会保存这次除法操做的商,而在 AH 中则会保存这次除法操做的余数,

固然,若是除数是 16 位的话,则 AX 中会保存本次除法操做的商,而 DX 则保存本次除法操做的余数。

上面介绍的是 AX 寄存器在除法操做中的应用,下面还须要介绍一下 AX 在乘法操做中的应用,

当使用 MUL 作乘法运算时,两个相乘的数要么都是 8 位,要么都是 16 位,

若是两个相乘的数都是 8 位的话,则一个默认是放在 AL 中,

而另外一个 8 位的乘数则位于其余的寄存器或者说是内存字节单元中,

而若是两个相乘的数都是 16 位的话,则一个默认存放在 AX 中,

另外一个 16 位的则是位于 16 的寄存器中或者是某个内存字单元中。

同时,当 MUL 指令执行完毕后,若是是 8 位的乘法运算,则默认乘法运算的结果是保存在 AX 中,

而若是是 16 位的乘法运算的话,则默认乘法运算的结果有 32 位,

其中,高位默认保存在 DX 中,而低位则默认保存在 AX 中。

AX 寄存器在 DIV  指令中的使用:

MOV DX,0H ;设置 32 位被除数的高 16 位为 0H MOV AX,8H ;设置 32 位被除数的低 16 位为 8H MOV BX,2H ;设置 16 位除数为 2H DIV BX ;执行计算

4 条语句的执行过程以下:

image

AX 寄存器在 MUL  指令中的使用:

MOV AX,800H ;设置 16 位乘数为 800H MOV BX,100H ;设置 16 位乘数为 100H MOV DX,0H ;清空用来保存乘法结果的高 16 位 MUL BX ;执行计算
4 条语句的执行过程以下:

image

       
BX 寄存器:

首先能够明确的是,BX 做为数据寄存器,代表其是能够暂存通常的数据的,

即在某种程度上,它和 AX 能够暂存通常性数据的功能是同样的,

其一样为了适应之前的 8 位 CPU ,而能够将 BX 当作两个独立的 8 位寄存器使用,即有 BH 和 BL,

除了暂存通常性数据的功能外,BX 做为通用寄存器的一种,BX 主要仍是用于其专属功能 – 寻址(寻址物理内存地址)上,

BX 寄存器中存放的数据通常是用来做为偏移地址使用的,何为偏移地址呢?

既然是偏移地址的话,固然得有一个基地址了,而这个基地址其实就是段地址,这里就涉及到了段寄存器,

固然,在介绍 BX 寄存器的时候,我不会去介绍段寄存器,上面提到 BX 的主要功能是用在寻址上,

那么,其是如何寻址的呢?

对于寻址这个话题,我会在个人下一篇博文中做出详细的介绍,

而这里,我只点一下,在 8086 CPU 中,CPU 是根据 <段地址:偏移地址> 来进行寻址操做的,

而 BX 中存放的数据表示的是偏移地址的话,天然,即可以经过 <段地址:[BX]> 的方式来完成寻址操做了。

为了介绍 BX 在寻址当中的做用,下面我给出一副示意图:

image

上面的示意图表示:能够令 BX = 2,而后经过 DS : [BX] 来访问到内存中段地址为 DS,且偏移量为 2 的内存单元了。

上面介绍的这种寻址方式是 BX 在寻址中最最简单的应用了,而对于稍微复杂的寻址方式,

还能够依赖于 SI,DI,BP 等寄存器来一块儿完成,固然,这会是下一篇博文将要介绍的内容了。

BX 寄存器在寻址中的使用:

MOV BX,5H MOV AH,11H MOV AH,[BX] ;设置 AX 的值为偏移地址为 BX 中的值时所表明的内存单元
3 条语句的执行过程以下:

image

从上图能够看出,在偏移地址为 5 时的内存单元中的数据位 BBH,

image

而从这幅图上面就能够看出,确实经过 [BX] 找到了偏移地址为 5 处的内存单元,而且将内存单元移入了 AH 中。

              

CX 寄存器:

CX 寄存器做为数据寄存器的一种呢,其一样具备和 AX,BX 同样的特色,便可以暂存通常性的数据,

同时还能够将其当作两个独立的 8 位寄存器使用,即有 CH 和 CL 两个 8 位寄存器,

固然,CX 也是有其专门的用途的,CX 中的 C 被翻译为 Counting 也就是计数器的功能,

当在汇编指令中使用循环 LOOP 指令时,能够经过 CX 来指定须要循环的次数,

而 CPU 在每一次执行 LOOP 指令的时候,都会作两件事:

一件就是令 CX = CX – 1,即令 CX 计数器自动减去 1;

还有一件就是判断 CX 中的值,若是 CX 中的值为 0 则会跳出循环,而继续执行循环下面的指令,

固然若是 CX 中的值不为 0 ,则会继续执行循环中所指定的指令 。

CX 寄存器在循环中的使用(输出 5 个白底蓝字的 A):

MOV AX,0B800H MOV DS,AX ;使用 80x25 彩色字符模式,内存地址 0xB8000 - 0xBFFFFF MOV BX,0 ;从 0xB8000 开始 MOV CX,5H ;循环 5 次 MOV DX,41H ;A 的16 进制为 41H MOV AX,01110001B ;显示白底蓝字 s: MOV [BX],DX ;显示 ASCII 字符 ADD BX,1 MOV [BX],AX ;设置字符显示属性 ADD BX,1 LOOP s

语句的执行过程以下:

image

              

DX 寄存器:

DX 寄存器做为数据寄存器的一种,一样具备和 AX,BX,CX 同样的特色,便可以暂存通常性的数据,

同时还能够将其当作两个独立的 8 位寄存器使用,极有 DH 和 DL,

同时,DX 做为一个通用寄存器的话,天然其还有其余的用途,而关于 DX 在其余方面的用途,

其实在前面介绍 AX 寄存器时便已经有所介绍了,

即当在使用 DIV 指令进行除法运算时,若是除数为 16 位时,被除数将会是 32 位,而被除数的高 16 位就是存放在 DX 中,

并且执行完 DIV 指令后,本次除法运算所产生的余数将会保存在 DX 中,

同时,在执行 MUL 指令时,若是两个相乘的数都是 16 位的话,

那么相乘后产生的结果显然须要 32 位来保存,而这 32 位的结果的高 16 位就是存放在 DX 寄存器中 。

DX 寄存器在 DIV  指令中的使用(即 2293812 / 256 = 8960  余数为 52):

MOV DX,0023H ;32 位被除数的高 16 位 MOV AX,0034H ;32 位被除数的低 16 位 MOV BX,100H ;16 的除数 DIV BX 

语句的执行过程以下:

image
能够看到在语句结束之后,AX = 2300H  即十进制的 8960,而 DX = 34H即十进制的 52 和咱们的结果是一致的。

DX 寄存器在 MUL  指令中的使用则各位能够参考在 AX 中 MUL 运算的使用,这里就不贴出来了。

          

指针寄存器(BP,SP):

BP 寄存器:

8086  CPU 中的指针寄存器包括两个,即 SP 和 BP ,在这里呢,我先只对 BP 寄存器作介绍,

由于 SP 寄存器实质上必须和 SS 段寄存器一块儿使用,因此,我将会把 SP 寄存器留到后面和 SS 段寄存器一块儿做介绍。

BP (Base Pointer)也就是基指针寄存器,它和其余的几个用来进行寻址操做所使用的寄存器(还有 BX,SI,DI)没有太大的区别,

关于 SI 和 DI 寄存器的下面请见下文。

首先,BP 寄存器做为通用寄存器的一种,说明其是能够暂存数据的,然后,BP 又不是数据寄存器,

也就意味着其不能分割成 2 个独立的 8 位寄存器使用,

然后当以 […] 的方式访问内存单元并且在 […] 中使用了寄存器 BP 的话,

那么若是在指令中没有明确或者说是显示的给出段地址时,

段地址则使用默认的 SS 寄存器中的值(BX,SI,DI 会默认使用 DS 段寄存器),

好比 DS:[BP] 则在这里明确给出了段地址位于 DS 中,

因此,这里表明的内存单元便是段地址为 DS ,偏移量为 BP 寄存器中的值的内存单元,

而若是单单是使用 [BP] 的话,则表明的内存单元是段地址为 SS,偏移量为 BP 寄存器中的值的内存单元。

而且 BP 寄存器主要适用于给出堆栈中数据区的偏移,从而能够方便的实现直接存取堆栈中的数据,

至于堆栈的话,会在后面的博文中介绍。

在 8086 CPU 中,只有 4 个寄存器能够以  […]  的方式使用,这四个寄存器分别是 BX,SI,DI,BP。

下面的  Demo  是 BX 寄存器在寻址中的使用:

MOV BP,0 MOV AX,[BP] ;将 SS:[BP] 表明的内存单元移入 AX 中 MOV AX,CS:[BP] ;将 CS:[BP] 表明的内存单元移入 AX 中

语句的执行过程以下:

image

            

变址寄存器(SI,DI):

首先,变址寄存器和上面介绍的指针寄存器(也就是 BP 和 SP),它们的功能其实都是用于存放某个存储单元地址的偏移,

或者是用于某组存储单元开始地址的偏移,即做为存储器指针使用,固然,因为变址寄存器和指针寄存器都是属于通用寄存器,

因此它们也能够保存算术结果或者说是具备暂存数据的功能,可是由于它们不是数据寄存器,因此没法分割成 2 个独立的 8 位寄存器使用,

关于变址寄存器和指针寄存器的详细使用,笔者将会在下一篇博文中做出最详细的介绍,

SI (Source Index) 是源变址寄存器,DI (Destination Index) 便是目的变址寄存器,

8086 CPU 中的 SI 寄存器和 DI 寄存器其实和 BX 寄存器的功能是差很少的,

只不过 SI 寄存器和 DI 寄存器均不是数据寄存器,因此它们不可以拆分为 2 个独立的 8 位寄存器,

而这也就是 SI 寄存器和 DI 寄存器与BX 寄存器所不一样的地方,

既然,SI,DI 两个寄存器的功能和 BX 差很少,天然,SI 和 DI 中也是能够暂存通常性数据的,

同时,经过使用 SI 和 DI 寄存器也是能够用来完成寻址操做的。

好比下面的代码就是可行的:

MOV SI,0 ;初始化偏移地址为 0 MOV AX,[SI] ;将段地址为 DS 偏移地址为 SI 的内存单元中的值移入 AX 中 MOV AX,DS:[SI] ;将段地址为 DS 偏移地址为 SI 的内存单元中的值移入 AX 中 MOV AX,SS:[SI] ;将段地址为 SS 偏移地址为 SI 的内存单元中的值移入 AX 中 MOV DI,0 ;初始化偏移地址为 0 MOV AX,[DI] ;将段地址为 DS 偏移地址为 DI 的内存单元中的值移入 AX 中 MOV AX,DS:[DI] ;将段地址为 DS 偏移地址为 DI 的内存单元中的值移入 AX 中 MOV AX,SS:[DI] ;将段地址为 SS 偏移地址为 DI 的内存单元中的值移入 AX 中

        

       

其余寄存器(CS,IP,SS,SP,DS,ES)

因为段寄存器老是和其余一些像指针寄存器,变址寄存器,控制寄存器一块儿使用,

因此在这里,我并不会单独介绍段寄存器,而是将段寄存器和一些其余的经常使用寄存器搭配介绍 。

因为下面的介绍中会涉及到不少关于段和栈的概念,而段和栈的介绍又都必须关系到物理内存,

因此在介绍段寄存器以及其余一些呈协做关系的寄存器以前,仍是先来介绍一下这几个基本的概念比较好。

8086 CPU 访问内存(物理地址):

当 CPU 须要访问一个内存单元时,须要给出内存单元的地址,

而每个内存单元在物理内存空间中都有一个惟一的地址,

便可以经过这个地址定位到内存单元,而这个地址即为物理地址。

CPU 经过地址总线将一个内存单元的物理地址送入存储器,

然后 CPU 即可以经过这个物理地址来访问这个物理地址所指向的内存单元了。

那么这个物理地址在 CPU 中是如何造成的呢?

首先,咱们知道 8086 CPU 的地址总线是 20 根,

即每次均可以传输 20 位的地址,从而寻址能力有 220 也就是 1MB 的大小,

可是 8086 CPU 的寄存器只有 16 位,也就是在 8086 CPU 的内部,

一次性处理,传输,暂存的地址都只能是 16 位,

即 8086 CPU 不能完整的保存下一个物理地址(物理地址为 20 位),

若是单单以最简单的方式(即直接用 16 位寄存器来保存物理地址)的话,那么,寻址能力只有 216 ,也就是 64KB,

若是真以如此简单的方式的话,那么地址总线还须要 20 根干吗呢?并且,难不成咱们之后的内存就是 64KB 了吗?

固然不是的,8086 CPU 在这里采起了必定的措施从而使其寻址能力达到 1MB 。

8086 CPU 在内部经过两个 16 位的地址进行合成从而造成一个 20 位的物理地址,由此,8086 CPU 的寻址能力即可以达到 1MB 。

那么 8086 CPU 又是如何将两个 16 位的地址合成为一个20 位的物理地址的呢?

当 CPU 在访问内存时,其会使用一个 16 位的基地址,而后再使用一个 16 位的偏移地址,

经过将基地址和偏移地址传入 8086  CPU 的地址加法器中进行合成便可以构造出 20 位的物理地址。

至于合成的方式以下:

基地址实际上是经过一个 16 位的段地址来造成的,将一个段地址左移 4 位即造成了基地址,

而至于偏移地址的话,天然没必要多说,为 16 位,经过将基地址和偏移地址相加便造成了 20 位的物理地址 。

下面给出一幅示意图来表示物理地址的合成:

image

段:

至于段的话,其实在物理内存中是没有段这一律念的,事实上,段的概念来自于  CPU ,

由于 CPU 拥有段寄存器,既然在 CPU 中拥有了段寄存器,天然,在 CPU 中就确定有段的概念了,

其实段也就是在编程时,咱们将若干个地址连续的内存单元看作是一个段,

而后经过将一个段地址左移 4 位造成基地址,再经过这个基地址来定位这个段的起始地址,

而后,再经过偏移地址即可以精肯定位到段中的内存单元了,因为段的起始地址是一个段地址左移 4 位,

因此很明显,段的起始地址确定是 16 的倍数,并且因为一个段内部,只能经过偏移地址来定位,

而偏移地址为 16 位,因此一个段的长度也就是 216 也就是 64KB 的大小。

在编程时,能够讲一段内存定义成为一个段,而这里,咱们又能够引出数据段,代码段,栈段这三种类型的段 。

何为数据段呢?其实就是咱们自个儿定义一段内存(固然段起始地址确定是 16 的倍数,而且段长度 <= 64KB),

而后咱们在这个段里头存放咱们所须要使用的数据,这就是数据段;

何为代码段呢?其实也很简单,也是咱们本身在编程的时候定义一段内存,而后这段内存用来存放咱们的代码(也就是指令),

既然是存放的代码,天然就称之为代码段;

何为栈段呢?至于栈段的话,有接触过数据结构的朋友应该是很清楚栈的,而这里咱们也就是在内存中分配出一个段,

而后将这个段当作栈来使用,对于栈的介绍,详见下文;

这里呢,顺便还点出几个关于段寄存器的内容,固然下文还会详细介绍的,

首先,对于任何一个段来讲,均有段地址,而这些段地址是存放在段寄存器中(段寄存器的做用也在于此),

可是对于不一样的段,它们默认的段地址存放在不一样的段寄存器中,像

数据段来讲,它的段地址存放在  DS (Data  Segment)寄存器中,

代码段的段地址存放在  CS (Code  Segment)寄存器中,

栈段的段地址存放在  SS (Stack  Segment)寄存器中 。

下面给出一幅在段中寻址的示意图:

image 

上面的示意图中,经过将段地址左移四位,而后与偏移地址相加即可以获得 20 位的物理地址了 。

栈:

8086  CPU 中提供了对栈的支持,而且其还提供了相应的指令来以栈的方式访问内存空间 。

什么是栈?

经过上面在段中的介绍,栈其实就是一个段,再说白一点,也就是一块内存,固然,这块内存是一块连续的内存 。

既然栈是一个段的话,那么固然就能够以使用段的方式来使用栈,固然,除了像段同样的使用栈之外,

栈还提供了其特殊的访问方式(若是和段如出一辙的话,那还须要栈干嘛呢?),

众所周知,栈是先进后出类型的数据结构,在 8086  CPU 中也是如此,

能够经过 ”PUSH“  指令将数据压入栈中,而后再经过 ”POP“  指令将栈顶的元素取出来 。

下面给出一幅示意图来描述栈:

image

即经过 PUSH  10 来将元素 10 放入栈中,由于,先前栈中没有任何数据,因此,10 就会做为栈顶元素存在,

而后再在栈中压入元素 20 ,此时,栈顶中的元素就是 20 了,而后再使用  POP 指令将栈顶元素取出,

此时取出的栈顶元素是 20 ,取出 20 后,栈中便只剩下 10 了,天然 10 就成为了栈顶,

最后再经过 POP 指令将栈顶 10 取出,此时,栈便变成了空栈了 。

好了,在介绍段寄存器以前的基础知识介绍就到这里了,下面开始正式介绍段寄存器以及与它们协做使用的寄存器。

                  

CS 寄存器 和 IP 寄存器:

通过前面对段的介绍,相信各位朋友对段寄存器应该也有必定的了解了,

下面将要介绍的是一组很是很是重要的寄存器,即 CS:IP 。

CS:IP 两个寄存器指示了 CPU 当前将要读取的指令的地址,其中  CS  为代码段寄存器,而   IP  为指令指针寄存器 。

什么叫作指示了 CPU 当前将要读取的指令呢?在 8086  CPU 中,为何  CPU  会自动的执行指令呢?

这些指令确定是存放在内存中的,可是  CPU  怎么知道这些指令存放在内存的那个位置呢?

好比,我有下面的两条指令要执行:

 MOV AX,1234H MOV BX,AX

而假设这两条指令在内存中存放为:

image

很显然, 1000H:0000H 指向的是  MOV  AX,1234H  的首地址,

若是 CPU 要读取到个人指令的话,很显然,必需要知道地址  1000H:0000H ,

而后  CPU  就能够根据这个首地址,将汇编指令  MOV  AX,1234H  所对应的机器码读入到  CPU  的指令寄存器中,

最后即可以在  CPU  中进行处理了。

但关键是   CPU  如何知道个人  1000H:0000H  这个首地址?

其实这就须要使用到  CS:IP  这个寄存器组了 。

当咱们运行一个可执行文件时,很明显,咱们须要另一个程序来将这个可执行文件加载到内存当中,

关于这个加载可执行文件的程序,咱们在这里无论他,点一下便可,

通常是经过操做系统的外壳程序(也就是传说中的  Shell  程序),

Shell  将可执行文件加载到内存中之后,就会设置  CPU  中的两个寄存器,

即设置  CS:IP  两个寄存器指向可执行文件的起始地址,此后  CPU  便从这个起始地址开始读取内存中的指令,而且执行,

好比咱们在写汇编程序时,一般会使用  START  标记,其实这个标记就是用来标记起始地址的,

当将一个汇编程序编译,链接成可执行文件之后,再经过操做系统的  Shell  程序将可执行文件加载到内存中之后,

这个  START  所标记处的地址就是整个可执行文件的起始地址了 。

也就是说,当一个可执行文件加载到内存中之后,CS:IP  两个寄存器便指向了这个可执行文件的起始地址,

而后  CPU  就能够从这个起始地址开始往下读取指令,

当读取完指令后,CS:IP  将会自动的改变,基本上是改变  IP ,从而指向下一条要读取的指令,这样就能够执行这个可执行文件了 。

最后再对  CS:IP  总结一下:

  1. 你想让  CPU  执行哪行指令,你就让  CS:IP  指向保存有指令的那块内存便可。
  2. 任什么时候候,CS:IP  指向的地址中的内容都是  CPU  当前执行的指令。

下面咱们来看一个  Demo,并详细观察其执行的过程:

ASSUME CS:CODES CODES SEGMENT START: MOV AX,1234H MOV BX,AX MOV AH,4CH INT 21H CODES ENDS END START

语句的执行过程以下:

image

从上面的截图中能够看出,当我使用  Shell (在  DOS  下也就是  Command  命令解释器)将可执行文件加载进内存后,

能够看到,整个程序的起始地址为   0C54H : 0000 H  ,而且,能够看到  CS  的地址为  0C54H ,IP  的地址为  0000H,

这正好吻合咱们上面对  CS:IP  的分析,很明显,CPU  将会读取    MOV    AX ,1234H   到 CPU 中而且执行 ,

而后咱们继续向下看:

image

能够看到,咱们单步执行后,AX 中的值编成了  1234H ,而  IP  寄存器中的值变成了  0003H,

对于  AX  中的值的改变,咱们是可以理解的,可是   IP  中的值为何会从  0000H  变到  0003H  呢?

从最上面的一幅关于指令在内存中的存放能够看出    MOV    AX ,1234H   在内存中须要  3 个内存单元存放,

也就是  CPU  为了执行    MOV    AX ,1234H   这条指令,已经将内存中相对应的 3  个内存单元读入内存中了,

执行完这条指令后,天然,CPU  就要将偏移地址向下移动  3  个单元,从而使得  CS:IP  指向下一条须要执行的指令了 ,

为了更深入的理解,咱们再来继续看执行过程,

image

从最上面的一幅关于指令在内存中的存放能够看出    MOV    BX ,AX  在内存中只占  2  个内存单元,

这也就是为何  IP  这一次只向下移动了  2  个单元的缘故 。

                  

关于  CS: IP  的遐想:

从上面关于  CS:IP  的介绍中,咱们能够大胆的猜测,咱们只须要经过手动的改变  CS:IP  所指向的内存地址,

让  CS:IP  指向咱们另外的代码,那么咱们就可让  CPU  执行咱们本身指定的代码了 。

便可以经过修改  CS:IP  来达到咱们想要让  CPU  干什么它就干什么的目的 。

上面的虽然是遐想,可是你们要相信,咱们写的是汇编,不是  JAVA  也不是  NET  ,

因此咱们还真的能够达到上面的目的,也就是说咱们的遐想实际上是能够实现的,固然这仍是有必定的限制的 ,

关于这个遐想呢,可能会在我后续的博文中有所介绍,不过感兴趣的固然能够本身去尝试了,蛮有味的哦 。

              

SS 寄存器和 SP 寄存器:

根据前面对栈的介绍,相信各位对栈也确定是有必定了解了的,更况且,估计你们也是职场打滚多年的,

要是栈都没用过的话,那也确实蛮悲剧的 ,因此,我在这里也不会对栈作十分详细的介绍了,

可是,最基本的介绍仍是要的,毕竟在底层的话,不像高级语言那么方便,能够直接一个  Stack  就 OK 的,

在底层涉及的是栈在内存中的具体实现 。

不知道,大伙有没有注意笔者在本篇博文的上面介绍关于栈的知识时,我并无提到如何找到这个栈,

我只提到了一个栈就是先进后出操做,同时可使用  ”PUSH“ 和  ”POP“ 指令,

而后就是稍微带了一下  SS 这个寄存器的介绍,

咱们虽然在内存中是能够方便的定义一个栈了,可是,咱们为何要定义这么一个栈呢?

天然,是为了操做方便,同时提供给  CPU  使用的,

既然  CPU  要使用的话,天然,CPU  又必须根据必定的方式找到这个栈,

而这就须要使用  SS 和  SP 寄存器了 。

同时,一个栈也就是一块内存区域,经过上面的介绍,咱们也知道了若是要在一块内存中精确地定位到内存单元的话(寻址),

咱们必需要有基地址(也就是段地址左移  4  位)和偏移地址,天然,要在一个栈中寻址的话,也须要段地址和偏移地址,

而对于一个栈来讲,咱们使用的最多的是什么呢?

固然是栈顶了,由于只有栈顶能够用来存取数据,因此对于一个栈来讲,咱们只须要有栈顶的段地址和偏移地址便可,

而对于栈顶的段地址,其是存放在段寄存器  SS  中的,而对于栈顶的偏移地址,其则是存放在  SP  寄存器中的 。

记住,在任什么时候刻,SS:SP  都是指向栈顶元素 。

其实关于栈的使用仍是比较简单的,可是要注意的是  8086  CPU  并不会保证咱们对栈的操做会不会越界 。

因此咱们在使用栈的时候须要特别注意栈的越界问题 。

当使用  PUSH 指令向栈中压入 1 个字节单元时,SP = SP - 1;即栈顶元素会发生变化;

而当使用  PUSH 指令向栈中压入  2 个字节的字单元时,SP = SP – 2 ;即栈顶元素也要发生变化;

当使用  POP 指令从栈中弹出 1 个字节单元时, SP = SP + 1;即栈顶元素会发生变化;

当使用  POP 指令从栈中弹出 2 个字节单元的字单元时, SP = SP + 2 ;即栈顶元素会发生变化;

下面经过一个  Demo 来介绍栈的使用:

ASSUME CS:CODES CODES SEGMENT START: MOV AX,1000H ;首先是定义好栈的段地址 MOV SS,AX MOV AX,10H ;再定义好栈的长度(初始时刻的栈顶偏移地址即栈的长度) MOV SP,AX MOV AX,0001H PUSH AX MOV AX,0002H PUSH AX MOV AX,0003H PUSH AX MOV AX,0004H PUSH AX MOV AX,0005H PUSH AX POP AX POP AX POP AX POP AX POP AX MOV AH,4CH INT 21H CODES ENDS END START
 
而后咱们来看栈在内存中的结构图:

image

语句的执行过程以下:

首先咱们来看还没有执行上述任何指令时栈中的数据状况:

image

而后咱们再来依次执行上述指令:

image

从上副截图中能够看出已经设置好了  SS:SP ,也就是栈已经设置 OK 了,

下面开始往栈中压入数据了,

image

因为咱们压入栈中的数据为字数据,即占 2 个内存单元,因此,每次  SP = SP – 2 ;

将 5 个字型数据压入栈中后,咱们能够来查看栈中的数据了,

image

所以,在内存中的一个好看点的结构图以下所示:

image

下面开始进行出栈操做了

image

因为咱们弹出栈时的数据为字数据,即占 2 个内存单元,因此,每次  SP = SP + 2 ;

将 5 个字型数据所有弹出栈中后,咱们能够来查看栈中的数据了,

image

能够看到 SP 变成了初始状态了,也就是说栈中全部的数据已经所有弹出了,虽然咱们查看内存时看到的不是 0 ,

可是咱们看到的这些数据都是无效的,咱们这里不理会 。

                  

DS 寄存器和 ES 寄存器:

DS  寄存器和  ES  寄存器都属于段寄存器,其实它们和  CS  寄存器以及  SS  寄存器用起来区别不大,

既然是段寄存器的话,天然它们存放的就是某个段地址了 。

经过上面对基础知识的介绍呢,咱们已经知道,若是  CPU  要访问一个内存单元时,

咱们必需要提供一个指向这个内存单元的物理地址给  CPU ,

而咱们也知道在  8086  CPU  中,物理地址是由段地址左移 4  位,而后加上偏移地址造成的,

因此,咱们也就只须要提供段地址和偏移地址即 OK 。

8086  CPU  呢,提供了一个  DS  寄存器,而且一般都是经过这个  DS  段寄存器来存放要访问的数据的段地址 。

DS(Data  Segment):很显然,DS 中存放的是数据段的段地址 。

可是这里不得再也不点一下,那就是咱们对段的支持是在  CPU  上体现的,而不是在内存中实现了段,

因此事实上咱们使用的段实际上是一个逻辑概念,便是咱们本身定义的,

再说白了,我定义一个段,我说它是数据段那它就是数据段,我说它是代码段那么它就是代码段,

它们其实都是一块连续的内存而已,至于为何要区分为数据段和代码段,

很明显,是用来给咱们编程提供方便的,即咱们在本身的思想上或者说是编码习惯上规定,

数据放数据段中,代码放代码段中 。而咱们在使用数据段的时候,为了方便或者说是代码的编写方便起见,

咱们通常把数据段的段地址放在  DS  寄存器中,固然,若是你硬要以为  DS  不顺眼,那你能够换个  ES  也是同样的,

至于  ES(Extra  Segment)  段寄存器的话,天然,是一个附加段寄存器,若是再说得过度点,

就当它是个扩展吧,当你发现,你几个段寄存器不够用的时候,你能够考虑使用   ES  段寄存器,

在使用方式上,则和其余的段寄存器没什么区别  。

下面看一个介绍使用  DS  寄存器的  Demo:

ASSUME CS:CODES CODES SEGMENT START: MOV AX,1000H MOV DS,AX MOV AL,1 MOV BX,0 MOV CX,5 ;设计一个循环,让其循环 5 次 s: MOV [BX],AL ;这里 [BX] 并无指定段地址哦 INC AL INC BX LOOP s MOV AH,4CH INT 21H CODES ENDS END START

上面的代码所作的事情,就是循环将  1,2,3,4,5 写入到地址  1000H:0000H ,1000H:0001H,

1000H:0002H,1000H:0003H,1000H:0004H  中,

语句的执行过程以下:

首先咱们来看还没有执行上述任何指令时栈中的数据状况:

image

而当循环执行完成之后,咱们再来看内存  1000H:0000H 处的值:

image

在这里,咱们能够看到确实达到了咱们预期的效果,可是你们注意看代码:

 s: MOV [BX],AL ;这里 [BX] 并无指定段地址哦 INC AL INC BX LOOP s 

这里能够看到,咱们在  [BX]  中并无给其指定段地址,而只有一个偏移地址,

可是根据咱们一开始的介绍,必需要有段地址和偏移地址才可以定位内存单元,

莫非这里出问题了?

其实不是的,由于咱们在最前面定义了段地址   DS  为  1000H,

当咱们定义好段地址后,每一次  CPU  执行到  [BX]  时,便会自动或者说是默认的从  DS  中取值,

而且将取得的值做为段地址,所以,当  [BX]  为  0001H  时,CPU  会从   DS  中取得一个  1000H ,

由这两个一合成便可以获得正确的物理地址   1000H:0000H 。

最后还提醒一点,那就是   8086  CPU  不支持直接将一个数据送入段寄存器中,

也就是下面的作法是错误的:

 MOV DS,1000H

           

               

标志寄存器(FLAG):

前面呢,已经介绍了  8086  CPU  14 个寄存器中的 13 个了,下面咱们将介绍最后一个寄存器也就是  FLAG  寄存器,

FLAG  寄存器之因此放到最后一个介绍,是由于其和其余的一些寄存器不一样,像   AX,BX,CX,DX  这些寄存器来讲,

它们都是用来存放数据的,固然  FLAG  中存放的也是数据啦,

呵呵,不过,AX,BX 这些寄存器中的数据是做为一个总体使用的,

最多也就分红一个  AL  和  AH  使用而已,可是在  FLAG  中,数据是按位起做用的,

也就是说,FLAG  中的每个位都表示不一样的状态,

因为一个位也就能表示  0  和  1 ,天然,FLAG  中的每个位就是用来描述状态的,

并且  FLAG  寄存器中存储的信息一般又被称做程序状态字(PSW) 。

下面我给出一幅  FLAG  寄存器中各个位的示意图:

image

从上面这幅图中能够看出,FLAG  的第  0  个位表示的是 CF  ,第 2 个位表示的是  PF  ,与此类推 . . . . 

首先,咱们来看一个列表:

image

上面的这个表怎么看呢?咱们经过看下面一幅截图就知道了 。

image

从上面的标记中能够看出,从左到右依次表明   OF,DF,SF,ZF,PF,CF  标志位的值,

再经过与上面的表格相对照能够知道:

OF = 0 ;

DF = 0 ;

SF = 0 ;

ZF = 0 ;

PF = 0 ;

CF = 0  ;

至于为何咱们在  Debug  模式下,使用  R  命令时,只会列出这几个标志位,我菜的话是由于相对来讲,

列出的这几个标志位更为经常使用,其余的几个标志位并不常用的缘故吧 。

下面咱们就按不一样的位来分别介绍这些位所描述的状态,以及它们表明的意义:

CF(Carry  FLag) - 进位标志(第 0 位):

CF:    进位标志是用来反映计算时是否产生了由低位向高位的进位,或者产生了从高位到低位的借位 。

if(运算过程当中产生了进位或者借位)
{
        CF  =  1;
} else {
        CF  =  0;
}

          

PF(Parity  FLag) - 奇偶标志(第 2 位):

PF:    奇偶标志是用来记录相关指令执行后,其结果的全部的  Bit  位中  1  的个数是否为偶数 。

if(运算结果中 1 的个数为偶数)
{
        PF  =  1;
} else {
        PF  =  0;
}

      

AF(Auxiliary  Carry  FLag) - 辅助进位标志(第 4 位):

AF:    用来辅助进位标志 。

if(字节操做中发生低半个字节向高半个字节借位或者进位  ||  字操做中发生低字节向高字节借位或者进位)
{
       AF = 1;
} else {
       AF = 0;
}

             

ZF(Zero  FLag) – 零标志(第 6 位):

ZF:    记录的是相关的指令执行完毕后,其执行的结果是否为  0 。

if(执行的结果  ==  0)
{
       ZF = 1;
} else {
       ZF = 0;
}

           

SF(Sign  FLag) - 符号标志(第 7 位):

SF:    符号标志,其记录相关指令执行完之后,其结果是否为负数 。

if(运算结果为负数)
{
        SF  =  1;
} else {
        SF  =  0;
}

     

TF(Trap  FLag) - 追踪标志(第 8 位):

TF:    追踪标志,主要是用于调试时使用 。

if(TF  ==  1)
{
       CPU 进入单步方式;
}

     

IF(Interrupt-Enable  FLag) - 中断容许标志(第 9 位):

IF:    中断容许标志,其决定  CPU  是否可以响应外部可屏蔽中断请求(之后会作详细介绍) 。

if(IF  ==  1)
{
        CPU 可以响应外部的可屏蔽中断请求;
} else {
        CPU 不可以响应外部的可屏蔽中断请求;
}

            

DF(Direction  FLag) - 方向标志(第 10 位):

DF:    方向标志,其用于在串处理指令中,用来控制每次操做后  SI  和  DI  是自增仍是自减 。

if(DF == 0)
{
        SI++;
DI++;
} else {
SI--;
DI--;
}
              

OF(OverFlow  FLag) - 溢出标志(第 11 位):

OF:    溢出标志,其一般记录了有符号数运算的结果是否发生了溢出 。

if(运算发生溢出)
{
        OF  =  1;
} else {
        OF  =  0;
}

               

总结

上面呢,从最简单的开始,按部就班的介绍了  8086  CPU  中的各个寄存器,

同时也经过一些  Demo  来列举了各个寄存器的使用,

因为写的比较基础,并且量也比较多,因此,形成博文过长了,读者需必定耐心才能看完,

写本篇博文呢,并非说未来要用汇编去开发个什么东东,

实质上,笔者学习汇编的目的也不在此,只是由于先前在接触到底层的寄存器以及内存时,

笔者总有一丝不爽的感受,老是感受不得要领,因此才会开始汇编的学习,

这次推出本系列博文,本意也并非说要学习汇编作开发,只是为了提高内功而已 。

相关文章
相关标签/搜索