ITEEDU

10.2 标志寄存器

 标志寄存器在前面几章里已有所讨论,不过有几个稍微特殊的位还要作些更详细的研究。

(1)PF(Parity Flag)位

这是奇偶标志位,当运算结果之中"1"的个数为偶数时此位将置位,当运算结果中"1"的个数是奇数时此位复位。请看下面的实验:

 C:\ASM\>debug[Enter] 
    -a[Enter] 
    0F6A:0100 mov al,36 
    0F6A:0102 or al,81 
    0F6A:0104 and al,7f 
    0F6A:0106 add al,80 
    0F6A:0108 shl al,1
   0F6A:010A [Enter] 
-r
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000

DS=0F6A ES=0F6A SS=0F6A CS=0F6A IP=0100 NV UP EI PL NZ NA PO NC

0F6A:0100 B036 MOV AL,36

36H转化成二进制为00110110B,其中1的个数为4。

-t
AX=0036 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0F6A ES=0F6A SS=0F6A CS=0F6A IP=0102 NV UP EI PL NZ NA PO NC
0F6A:0102 0C81O R AL,81

  81H转化成二进制数为10000001B,其中包含有两个1。

-t
AX=00B7 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0F6A ES=0F6A SS=0F6A CS=0F6A IP=0104 NV UP EI NG NZ NA PE NC
0F6A:0104 247F AND AL,7F

36H OR 81H=B7H=10110111B,结果包含6个1,为偶数,所以PF=1(PE)。

-t
AX=0037 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0F6A ES=0F6A SS=0F6A CS=0F6A IP=0106 NV UP EI PL NZ NA PO NC
0F6A:0106 0480 ADD AL,80

B7H AND 7FH=37H=00110111B,结果包含5个1,为奇数,所以PF=0(PO)。

-t
AX=00B7 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0F6A ES=0F6A SS=0F6A CS=0F6A IP=0108 NV UP EI NG NZ NA PE NC
0F6A:0108 D0E0 SHL AL,1

37H+80H=B7H=10110111B,结果包含6个1,为偶数,所以PF=1(PE)。

-t
AX=006E BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0F6A ES=0F6A SS=0F6A CS=0F6A IP=010A NV UP EI PL NZ AC PO CY
0F6A:010A C3 RET

 B7 SHL 1=6EH=01101110B,结果包含5个1,为奇数,所以PF=0(PO)。
由此实验可以看出,无论是逻辑操作指令还是算术运算指令,或者是移位指令都能影响PF标志。

(2)OF(Overflow Flag)

这是溢出标志位,这一位表示了运算结果是否超出了计算机所能表示的数的范围。溢出的概念容易让人范迷糊,举两个例子,分别计算0FFH+1与68H+21H,哪次运算会溢出?

初看起来计算0FFH+1将产生9位的结果,而68H+21H=89H,仍然是8位,好象计算0FFH+1应该导致溢出。结果真是这样吗?让我们向"DEBUG"请教一下:

C:\ASM\>DEBUG[Enter] 
  -a[Enter] 
  0F6A:0100 mov al,ff 
  0F6A:0102 add al,1 
  0F6A:0104 mov al,68 
  0F6A:0106 add al,21 
  0F6A:0108 [Enter] 
-g=100 104[Enter]
AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0F6A ES=0F6A SS=0F6A CS=0F6A IP=0104 NV UP EI PL ZR AC PE CY
0F6A:0104 B068 MOV AL,68


-g=104 108[Enter]
AX=0089 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000
DS=0F6A ES=0F6A SS=0F6A CS=0F6A IP=0108 OV UP EI NG NZ NA PO NC
0F6A:0108 5F POP DI

 通过这个实验可以看出,实际计算结果与我们推测的正好相反。0FFH+1没有造成溢出,而68H+21H反而溢出了。

为什么会有这样的现象?如果我们把"溢出"的概念放到"带符号数"的条件下考虑就会明白这其中的道理了。所谓"超出数的表示范围"指得是超出"带符号数"的范围,毫无疑问68H+21H是等于89H,但是89H实际是一个负数。

究竟什么情况下会导致溢出?数据在进行计算时各数位之间难免会有进位,以8位数据计算为例,与溢出有关的进位操作是Bit6至Bit7之间的进位与Bit7至CF之间的进位。具体来说,当Bit6至Bit7的进位与Bit7至CF的进位同时发生或同时不发生的情况下就没有产生溢出。反之,如果只发生了一次进位,那么就导致了溢出。

 拿刚才所举的例子来说,0FFH+1符合两次进位的规则,所以无溢出发生;而68H+21H只有Bit6至Bit7的进位,没有Bit7至CF的进位,所以有溢出发生。
16位、32位数据运算时也是判断高2位之间的进位与最高位的进位情况。

(3)IF(Interrupt Flag):

中断标志,这一位控制着CPU对中断的响应,当这一位为1时CPU将响应发生的中断,此位为0时CPU即使收到中断信号也不执行中断服务程序。

(4)TF(Trap Flag):

陷井标志,此位置1时会使CPU进入单步运行方式,即CPU每执行一条指令之后就会产生一次单步中断,中断号为01。DEBUG提供的T命令就是依靠TF标志实现的。前面我们讨论中断时曾经提到过PC电脑具有三种中断源,硬件中断、软件中断与处理机中断。单步中断就属于处理机中断,它是由CPU自己产生的。类似的还有除法错误中断,这是在进行除法运算时除数为0或者商过大时由CPU的,其中断号为0。

至此标志寄存器的各个位本书都作了简单的介绍,与标志寄存器有关的还有一些指令,下面做一些简单的说明。首先看一看标志位设置指令:

·CLC(Clear CF)与STC(Set CF):这两条指令用于将CF标志清零和置1。
·CMC(Complement CF):这条指令用于将CF标志求反。
·CLI(Clear IF)与STI(Set IF):这两条指令用于将中断标志IF清零和置1,以控制CPU对中断的响应。
·CLD(Clear DF)与STD(Set DF):前文已有所述。

标志寄存器与累加器之间还可以相互交换数据,相应的指令是:

·LAHF(Load AH with Flag):此指令用于将标志寄存器的低8位传送至AH寄存器中。
·SAHF(Store AH into Flag):此指令用于将AH寄存器中的数据置入标志寄存器的低8位中。
上面两条指令只能传送设置标志寄存器的低8位,如果需要处理完整的标志寄存器则需要借助于堆栈:
·PUSHF(Push the Flag):标志寄存器入栈。
·POPF(Pop the Flag):标志寄存器出栈。

利用这两条指令,我们就能够处理标志寄存器的高8位,DEBUG就是通过这样的途径将TF位置成1使CPU进入单步工作状态的。下面给出的这个程序也是利用此方法演示了单步中断的应用:

TRAP.ASM
        code  segment
              assume    cs:code,ds:code
              org       100h
      begin:
              jmp       start	;跳过数据区
     counter  dw        ?	;定义一个字的计数器
new01:			;新编的01H中断服务程序
              push      ax	;暂存AX寄存器
              inc       word ptr cs:[counter]	;计数器加1
              jnz       exit	;若计数器未满65536则退出服务程序
              mov       ax,0e07h	;计数器计满65536后响一次铃
              int       10h
       exit:
              pop       ax	;恢复AX寄存器
              iret      ;中断返回
      start:
              mov       ax,0	;初始化DS寄存器指向中断向量表
              mov       ds,ax
              mov       bx,4	;BX寄存器指向01H中断向量
			
              mov       dx,offset new01	;将中断服务程序的入口地址
              mov       word ptr ds:[bx],dx	;CS:OFFSET NEW01填入中断向量表
              mov       word ptr ds:[bx+2],cs
			
              pushf     ;将标志寄存器压入堆栈
              pop       ax	;标志寄存器弹出至AX寄存器
              or        ah,1	;将TF标志位置1
              push      ax	;将修改后的标志寄存器压入堆栈
              popf      ;而后将其送回标志寄存器
      loop1:
              jmp       loop1	;陷入死循环
        code  ends
              end       begin

 这个程序还演示了如何自行编制中断服务程序,其实编制中断服务程序与一般的程序设计方法一样,只是要注意在中断向量表中的相应位置填入正确的程序入口。这个程序运行之后就不能返回系统了,它将TF标志设为1,使CPU进入单步运行状态,而后进入了死循环。由于工作于单步状态下,所以CPU每次执行一个指令后就会产生01中断。而后CPU开始执行01中断服务程序。

NEW01就是中断服务程序的入口,这一小段程序设了一个计数器COUNTER,每当计数器计满65536之后就响一次铃,所以计算机会发出连续的蜂鸣音。
最后要提醒大家的是当CPU响应中断时IF标志会自动清零,所以CPU在执行中断服务程序时是连续执行的,不再产生单步中断。