汇编语言(8086)-寄存器

时间:2023-01-03 01:18:12

p.s.看《汇编语言(第三版)》--王爽,暂时看到第11章,所以可能不太全面,接下来发现有缺漏、错误的,会继续更新修正。

然后下面写的是部分书上的内容加上我个人的理解,正因如此,由于我知识的局限,可能会出现比较多的错误。

前提简介:

>寄存器是CPU中程序员可以用指令读写的部件。程序员通过改变各种寄存器中的内容来实现对CPU的控制。------《汇编语言(第三版)》第2章


简单来说,我认为寄存器粗略看有点像控制台,用户输入命令(修改寄存器中的数据),然后操作系统执行用户的命令(CPU执行命令)。除此之外,我认为寄存器还有存储功能,能有效提高CPU运算速度(不需要再从内存读取数据)。


既然说到寄存器控制CPU,就说说是如何控制CPU的,在之前,先简单说说一些和这个没有多大关系,但作为前提的内容(当课外阅读吧):

前提:首先所有的程序简单点说都是由代码组成,而且不管最初是什么语言的代码,到最后都转化成二进制数字,然后其中固定的二进制数字串对应着该CPU中相应的动作(就是遇到这串二进制数字,CPU就会执行相应的操作),我们就称这样一个固定的二进制数字串为一条指令吧。然后这些指令都存储在内存中(暂时当它都存储在内存中,实际上是部分在内存,部分在磁盘),然后运行的时候,就是CPU一条一条地从内存提取指令、执行指令


小结:看完上面,我们再来整理一下上面内容,其实与CPU相关(换句话说CPU的动作)只有最后一句,从内存提取指令,然后执行指令,好啦,问题来啦,内存那么大,CPU怎么知道从哪里提取指令?怎么提取指令?然后怎么执行指令?


然后后面两个问题的答案和控制CPU已经没什么大关系了,因为这两个我们都不能控制(推荐看《深入了解计算机系统》),所以说到最后,我们控制CPU实际上就是控制CPU从哪里读取指令(数据)


说到这里又来说说前提(课外阅读...),其实这个也是上面第二个问题(怎么提取指令)的答案,让我们先来看看图片(所有图片均来自《汇编语言(第三版)-王爽》)

汇编语言(8086)-寄存器

从内存读取数据基础版:

1.把需要读写的内存地址传递给内存(送上地址总线)

2.内存把相应地址的数据传递回给内存


但由于当时条件限制,当时8086CPU是16位结构的CPU,就是说,在8086CPU内部,能够一次性处理、传输、暂时存储的信息的最大长度是16位。然后内存单元的地址在送上地址总线之前,必须在CPU中处理、传输、暂时存放,而对于16位CPU能一次性处理、传输、暂时存储16位的地址


而实际上8086CPU外的地址总线有20位,而从内部只能传送出16位地址,这使20位地址总线不能得到充分利用因此采用由2个16位地址合成的方法形成一个20位的物理地址。至于如何合成,又来看看图片:

汇编语言(8086)-寄存器

物理地址=段地址x16+偏移地址

p.s.图片中的数字是16进制,4个二进制数字等于一个16进制数字


从内存读取数据完整版:

1.CPU中的相关部件提供两个16位的地址:段地址,偏移地址

2.段地址和偏移地址通过内部总线送入一个为地址加法器的部件

3.地址加法器将两个16位地址合成一个20位的地址

4.地址加法器通过内部总线将20位物理地址送入输入输出控制电路

5.输入输出控制电路将20位物理地址送上地址总线

6.地址总线将20位物理地址送到存储器

7.存储器将相应的地址上的数据传出


第三个前提

有一个硬件部分叫时钟,然后在每一个时钟周期,CPU都从内存读取进一条指令


我们现在来总结一下前面的3个前提:在每一个时钟周期,CPU根据当前的段地址和偏移地址读取一条指令进来,然后执行。(前面一大段最后总结剩下一句。。。)


好啦,问题又来啦,CPU怎么知道当前的段地址和偏移地址是什么?说到现在,终于和我们的主题套上关系啦,对的,就是通过我们的主角---寄存器,来得知,段地址和偏移地址都有相应的寄存器进行存储,特别是要执行的指令的段地址和偏移地址更是有固定的寄存器(cs,ip)进行存储,然后CPU发出地址,就是直接把cs,ip中的数据发出去。

寄存器:

在8086CPU中一共有14个寄存器:

ax,bx,cx,dx,si,di,sp,bp,ip,cs,ss,ds,es,flag

每个寄存器都是16位,在下面对各个寄存器的逐一介绍中,根据功能或者其特点可能会将几个寄存器划分在一起介绍(所以一个寄存器可能会出现在多个地方,最后会给出总结表)

通用寄存器(ax,bx,cx,dx)

将它们4个划分一起是由于它们的一个共同特性:

都可以分为独立使用的2个8位寄存器来使用

ax:ah,al

bx:bh,bl

cx:ch,cl

dx,dh,dl


h结束的为高位,l的为低位,而且这两个8位寄存器之间相互独立,例如:ah本来为0000,0000,当你向al(其余一样)传入一个数据,但这个数据已经超8位,就假设为1,0000,0000,最后结果为ah:0000,0000,al:0000,0000。传进去的数据的最高位(超出的一位),将会被舍弃,而不是往ah进位。


好吧,实际上除了这个,它们也就没什么其它共同特点了(读写功能,那个所有寄存器都可以干啦)。。。

地址相关寄存器:段地址+偏移地址(cs,ss,ds,es,ip,sp,bp,bx,si,di)

在上面的前提简介的最后一部分中,提到过段地址和偏移地址存储在寄存器中,在这部分中将会详细介绍:

先来大体了解一下里面哪些用来存储段地址,哪些存储偏移地址:

段地址寄存器(段寄存器):cs,ss,ds,es

可用来存储偏移地址的寄存器:ip,sp,bp,si,di

可以看出它们往往是成对出现的,而且可以说都是固定组合,我们就一对对来看吧:

cs:ip:指示指令地址

cs和ip是8086CPU中两个最关键的寄存器,它们指示了CPU当前要读取指令的地址


在8086CPU机中,任意时刻,设cs中的内容为M,ip中的内容为N,8086CPU将从内存Mx16+N单元开始,读取一条指令执行,若该条指令不是跳转指令,则执行完后,L为刚执行完的指令的长度(所占内存单元的个数),N=N+L,然后继续读取下一条指令执行。


换句话说,8086机中,任意时刻,CPU将cs:ip指向的内容当做指令执行,就是说假如你原来存储在某个内存单元的是数据,但是如果你的cs:ip指向了该内存单元,CPU依然会把内存单元中的内容当做指令来执行


而且,cs,ip也是唯二的两个寄存器不能被mov指令修改,只能被转移指令修改。

ss:sp:指示栈顶地址

8086CPU提供了相关的指令来以栈的方式访问内存空间,也就是说,基于8086CPU编程时,可将一段内存当做栈来使用,然后这时,ss:sp指示的就是栈顶的地址


栈中的一个单元大小是两个内存单元,就是16位,就是每次入栈,不管是8位还是16位,最后在栈中都占据16位大小也就是2个内存单元。


在8086CPU中,任意时刻,设ss中的内容为M,sp中的内容为N,每次入栈后,N=N-2(栈底在高地址,栈顶在低地址),每次出栈后,N=N+2。当使用push,pop指令时,CPU就会按照当前ss:sp指示的位置,进行入栈,出栈操作。

ds(es):idata(bx,bp,si,di):指示代码段地址

这里和前面的两对都不同,它们不用一一对应,而且除了用寄存器中的数据,还可以直接用立即数来作为偏移地址。它们用于对内存单元中的内容以数据形式读取。

使用格式为:ds(es):[idata(bx,bp,si,di)]

p.s.接下来用sa代表段地址,表示ds或者es均可

上面代表的是:段地址为ds中的内容,偏移地址为idata 的内存单元中的数据。

而且还有几点必须注意:

1.只有这4个寄存器可以作为偏移地址放进[]中,其余的寄存器如ax,cx...放进

去就是错误的指令

2.在[]中,这4个寄存器可以单个出现,或只能以4种组合出现:

sa:[bx]

sa:[bp]

sa:[si]

sa:[di]

sa:[bx+si]

sa:[bx+di]

sa:[bp+si]

sa:[bp+di]

3.两个内存单元之间不可以直接使用mov进行数据传输 最后来总结一下代码段

的合法寻址方式,也可以说是偏移地址中寄存器的组合方式:

-----------------------------------------------------

寻址方式名称

sa:[idata]直接寻址

sa:[bx]寄存器间接寻址

sa:[bp]寄存器间接寻址

sa:[si]寄存器间接寻址

sa:[di]寄存器间接寻址

sa:[bx+idata]寄存器相对寻址

sa:[bp+idata] 寄存器相对寻址

sa:[si+idata] 寄存器相对寻址

sa:[di+idata] 寄存器相对寻址

sa:[bx+si]基址变址寻址

sa:[bx+di] 基址变址寻址

sa:[bp+si] 基址变址寻址

sa:[bp+di] 基址变址寻址

sa:[bx+si+idata]相对基址变址寻址

sa:[bx+di+idata] 相对基址变址寻址

sa:[bp+si+idata] 相对基址变址寻址

sa:[bp+di+idata] 相对基址变址寻址

---------------------------------------------------

循环(loop)相关寄存器(cx)

cx表示的是循环的次数,设cx中的内容为N,每执行一次loop指令后,N=N-1,当N>0时,执行跳转(继续循环),否则跳出循环,执行下一条指令(内存上向邻的下一条)。

除法(div)相关寄存器(ax,dx)

除法指令:

div 寄存器

div 内存单元

div后面跟着的是除数,如果除数为8位,则被除数为16位,如果除数为16位,则被除数为32位;如果被除数为16位,则默认存放在ax,结果的商存放在al,余数存放ah;如果被除数为32位,则高16位存放在dx,低16位存放在ax;除得的商存放在ax,余数存放在dx

乘法(mul)相关寄存器(ax,dx)

乘法指令:
mul 寄存器
mul 内存单元
两个相乘的数必须位数一致,如果都为8位,一个默认放在al中,结果存放在ax中;如果都为16位,一个默认存放在ax中,结果的高位存放在dx中,低位存放在ax中。

转移指令(jcxz)相关寄存器(cx)

格式:jcxz label
如果cx中的内容为0,则转移到label处执行,否则执行下一条指令

复制指令(movsb,movsw)相关寄存器(ds,si,es,di)

格式:movsb
功能:把源地址的单元内容复制给目的地址的单元,在每次操作完后,根据df的值控制si、di的增减
源地址:ds:[si]
目的地址:es:[di]
df = 1:si、di递增
df = 0: si、di递减

标志寄存器(flag)

首先,flag和其它寄存器不一样,其它寄存器是整个寄存器具有一个含义(寄存器的16个位作为一个整体)。而flag是按位起作用的,也就是说,它的每一位都有专门的含义,记录特定的信息。
其中8086CPU的flag寄存器结构下:
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
OF DF IF TF SF ZF AF PF CF

上面空着的位表示没有使用,其中被使用的标志位的含义为:
CF:进位值\借位值
adc、abb指令与其密切相关
PF:1:为偶数;0:为奇数
AF:
ZF:1:为0;0:不为0
SF:1:为负;1:非负
TF:
IF:
DF:1:每次操作后si、di递减;0:每次操作后si、di递增
OF:1:溢出;0:没有溢出

使用标志寄存器的相关指令

cmp
分类 指令 转 移 条 件 说 明
(Ⅰ)
JZ/JE ZF=1 为零/相等, 则转移
JNZ/JNE ZF=0 不为零/不相等, 则转移
JS SF=1 为负, 则转移
JNS SF=0 为正, 则转移
JO OF=1 溢出, 则转移
JNO OF=0 不溢出, 则转移
JP PF=1 奇偶位为1, 则转移
JNP PF=0 奇偶位为0, 则转移
JC CF=1 进位位为1, 则转移
JNC CF=0 进位位为0, 则转移
(Ⅱ) JB/JNAE/JC CF=1 低于/不高于等于, 则转移
JNB/JAE/JNC CF=0 不低于/高于等于, 则转移
JBE/JNA (CF * ZF)=1 低于等于/不高于, 则转移
JNBE/JA (CF * ZF)=0 不低于等于/高于, 则转移
(Ⅲ) JL/JNGE (SF * OF)=1 小于/不大于等于, 则转移
JNL/JGE (SF * OF)=0 不小于/大于等于, 则转移
JLE/JNG ((SF * OF) * ZF)=1 小于等于/不大于, 则转移
JNLE/JG ((SF * OF) * ZF)=0 不小于等于/大于, 则转移

寄存器功能总结:

---------------------------------------------------------------------

ax:

特性:

 可被拆分为2个8位寄存器

功能:

(1)除法:1.被除数为16位,ax存放16位被除数,ax存放商。2.被除数为 32位,ax存放被除数的低16位,ax存放商

(2)乘法:1.相乘的数为8位,al存放一个相乘的数,且结果存放在ax中。2.相乘的数为16位,ax存放一个相乘的数

,且结果的低位存放在ax中

-----

bx:

特性:

可被拆分为2个8位寄存器

功能:

用作对内存单元的寻址中的偏移地址

-----

cx:

特性:

可被拆分为2个8位寄存器

功能:

1.用来存放loop、rep的循环次数

2.用jcxz的条件判断

-----

dx:

特性:

可被拆分为2个8位寄存器

功能:

(1)除法:1.被除数为16位,dx存放余数。2.被除数为32位,dx存放被除数的高16位,dx存放余数

(2)乘法:相乘的数为16位,相乘结果的高位存放在dx中

-----

cs:特性:

不能被mov修改,只有转移指令能修改

功能:

存放指令段地址

-----

es:

特性:

...

功能:

可用作ds的替补|有多个数据段的时候,用es顶上

-----

ip:

特性:

不能被mov修改,只有转移指令能修改

功能:

存放指令偏移地址

-----

bp:

特性:

...

功能:

存放内存单元的偏移地址

-----

sp:

特性:

...

功能:

存放栈顶的偏移地址

----

si:

特性:

...

功能:

存放内存单元的偏移地址

-----

di:

特性:

...

功能:

存放内存单元的偏移地址

---

psw:

...

-------------------------------------------------------------------------