汇编语言 王爽 复习参考

第七章 更灵活的定位内存地址的方法

7.1 and和or指令

(1)and指令:逻辑与指令,按位进行与运算。

mov al,01100011B
and al,00111011B
执行后:al=001000011B

(2)or指令:逻辑或指令,按位进行或运算。

mov al,01100011B
and al,00111011B
执行后:al=01111011B

7.3 以字符形式给出的数据

在汇编程序中,对于字符的数据需要加单引号,示例如下:

datasg segment
    db 'BaSiC'
    db 'iNforMaTiOn'
datasg ends

7.4 大小写转换的问题

在不使用判断指令的前提下,将一个字符串转为大写或小写。
观察下面的ASCII码表,可以发现大写字母ASCII码的第5位为0,小写字母的第5位为1。于是就可以采用逻辑与或逻辑或的方式通过改变该位的值,而改变字符的大小写。

1

7.5 [bx+idata]

[bx+idata]表示一个内存单元,其中idata表示一个整数值。
该指令可以以以下三种格式表示:

eg1:
mov ax,[200+bx]

eg2:
mov ax,200[bx]

eg3:
mov ax,[bx].200

7.7 SI和DI

si和di是8086CPU中和bx功能相近的寄存器,si和di不能够分成两个8位寄存器使用。
使用示例如下:

eg1:
mov si,0
mov ax,[si]
mov ax,[si+123]

eg2:
mov di,0
mov ax,[di]
mov ax,[di+123]

7.8 [bx+si]和[bx+di]

[bx+si]和[bx+di]同样指的都是一个内存单元,使用示例如下:

eg1:
mov ax,[bx+si]

eg2:
mov ax,[bx][si]

7.9 [bx+si+idata]和[bx+di+idata]

[bx+si+idata]和[bx+di+idata]同样只得都是一个内存单元,使用示例如下:

eg1:
mov ax,[bx+200+si]

eg2:
mov ax,[200+bx+si]

eg3:
mov ax,200[bx][si]

eg4:
mov ax,[bx].200[si]

eg5:
mov ax,[bx][si].200

7.10 不同寻址方式的灵活应用

(1)[idata]用一个常量来表示地址,可用于直接定位一个内存单元;
(2)[bx]用一个变量来表示内存地址,可用于间接定位一个内存单元;
(3)[bx+idata]用一个变量和常量来表示地址,可在一个起始地址的基础上用常量间接定位一个内存单元;
(4)[bx+si]用两个变量表示地址;
(5)[bx+si+idata]用两个变量和一个常量表示地址。

第八章 数据处理的两个基本问题

本章所讨论的两个基本问题是:

  • 处理的数据在什么地方?
  • 要处理的数据有多长?

使用两个描述性的符号reg来表示一个寄存器,用sreg表示一个段寄存器。
reg的集合包括:ax、bx、cx、dx、ah、al、bh、bl、ch、cl、dh、dl、sp、bp、si、di;
sreg的集合包括:ds、ss、cs、es;

8.1 bx、si、di和bp

(1)在8086CPU中,只有bx、si、di和bp四个寄存器可以用在“[…]”中来进行内存单元的寻址。

比如下面的指令都是正确的:
mov ax,[bx]
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp]
mov ax,[bp+si]
mov ax,[bp+di]
而下面的指令是错误的:
mov ax,[cx]
mov ax,[ax]
mov ax,[dx]
mov ax,[ds]

(2)在[…]中,这4个寄存器可以单个出现,或只能以4种组合出现:bx和si、bx和di、bp和si、bp和di。

比如下面的指令是正确的:
mov ax,[bx]
mov ax,[si]
mov ax,[di]
mov ax,[bp]
mov ax,[bx+si]
mov ax,[bx+di]
mov ax,[bp+si]
mov ax,[bp+di]
mov ax,[bx+si+idata]
mov ax,[bx+di+idata]
mov ax,[bp+si+idata]
mov ax,[bp+di+idata]
下面的指令是错误的:
mov ax,[bx+bp]
mov ax,[si+di]

(3)只要在[…]中使用寄存器bp,而指令中没有显性地给出段地址,段地址就默认在ss中。比如下面的指令。

mov ax,[bp]				含义:(ax)=((ss)*16+(bp))
mov ax,[bp+idata]		含义:(ax)=((ss)*16+(bp)+idata)
mov ax,[bp+si]			含义:(ax)=((ss)*16+(bp)+(si))
mov ax,[bp+si+idata]	含义:(ax)=((ss)*16+(bp)+(si)+idata)

8.2 机器指令处理的数据在什么地方

对于绝大部分机器指令都是进行数据处理的指令,处理大致可分为3类:读取、写入、运算。在机器指令这一层来讲,并不关心数据的值是多少,而关心指令执行前一刻,它将要处理的数据所在的位置。指令执行前,所要处理的数据可以在3个地方:CPU内部、内存、端口,比如下表:
1

8.3 汇编语言中数据位置的表达

在汇编语言中用3个概念来表达数据的位置。
(1)立即数(idata)
对于直接包含在机器指令中的数据,在汇编语言中称为立即数。例如:

mov ax,1
add bx,2000h
or bx,00010000b
mov al,'a'

(2)寄存器
指令要处理的数据在寄存器中,在汇编指令中给出相应的寄存器名。例如:

mov ax,bx
mov ds,ax
push bx
mov ds:[0],bx

(3)段地址(SA)和偏移地址(EA)
指令要处理的数据在内存中,在汇编指令中可以用[X]的格式给出EA、SA在某个段寄存器中。
存放段地址的寄存器可以是默认的,比如:

段地址默认在ds中:
mov ax,[0]
mov ax,[di]
mov ax,[bx+8]
mov ax,[bx+si]

段地址默认在ss中:
mov ax,[bp]
mov ax,[bp+si]
mov ax,[bp+si+8]

存放短地址的寄存器也可以是显性给出的,比如:

mov ax,ds:[bp]
mov ax,es:[bx]
mov ax,ss:[bx+si]
mov ax,cs:[bx+si+8]

8.4 寻址方式

8086CPU的寻址方式,如下表所示:
1

8.5 指令要处理的数据有多长

8086CPU可以处理两种类型的数据:byte和word。
(1)通过寄存器名指明要处理的数据的尺寸。

下面的指令中,寄存器指明了指令进行的是字操作。
mov ax,1
mov bx,ds:[0]
inc ax
add ax,1000

下面的指令中,寄存器指明了指令进行的是字节操作。
mov al,1
mov al,ds:[0]
mov ds:[0],al
inc al
add al,100

(2)在没有寄存器名存在的情况下,用操作符X ptr指明内存单元的长度,X在汇编指令中可以为word或byte。

下面的指令中,用word ptr指明了指令访问的内存单元是一个字单元。
mov word ptr ds:[0], 1
inc word ptr [bx]
inc word ptr ds:[0]
add word ptr [bx],2

下面的指令中,用word ptr指明了指令访问的内存单元是一个字节单元。
mov byte ptr ds:[0], 1
inc byte ptr [bx]
inc byte ptr ds:[0]
add byte ptr [bx],2

8.7 div指令

div是除法指令,使用div做除法的时候应注意:
(1)除数:有8位和16位两种,在一个reg(寄存器)或内存单元中。
(2)被除数:默认放在AX或DX和AX中,如果除数为8位,被除数则为16位,默认在AX中存放;如果除数为16位,被除数则为32位,在DX和AX中存放,DX存放高16位,AX存放低16位。
(3)结果:如果除数为8位,则AL存储除法操作的商,AH存储除法操作的余数;如果除数为16位,则AX存储除法操作的商,DX存储除法操作的余数。
示例如下:

利用除法指令计算100001/100。

分析:
被除数100001大于65535,不能用ax寄存器存放,所以只能用dx和ax两个寄存器联合存放100001,也就是说要进行16位的除法。
除法100小于255,可以在一个8位寄存器中存放,但是,因为被除数是32位,除数应为16位,所以要用一个16位寄存器来存放除数100。
先将100001表示为16进制形式:186A1H。

程序如下:
mov dx,1
mov ax,86A1H
mov bx,100
div bx

程序执行后:
(ax)=03E8H,(dx)=1。

8.8 伪指令dd

db:字节型数据
dw:字型数据
dd:双字型数据
示例如下:

用div计算data段中第一个数据除以第二个数据后的结果,商存在第三个数据的存储单元中。
datasg segment
    dd 100001
    dw 100
    dw 0
datasg ends

解:
assume cs:codesg,ds:datasg

datasg segment
    dd 100001
    dw 100
    dw 0
datasg ends

codesg segment
    
    start:
    mov ax,datasg
    mov ds,ax
    
    mov ax,ds:[0]
    mov dx,ds:[2]
    
    div byte ptr ds:[4]
    
    mov ds:[6],ax
    
    mov ax,4c00h
    int 21h
codesg ends

end start

8.9 dup

dup的使用格式如下:

db 重复的次数 dup (重复的字节型数据)
dw 重复的次数 dup (重复的字型数据)
dd 重复的次数 dup (重复的双字型数据)

使用示例:

db 3 dup (0,1,2)
相当于:
db 0,1,2,0,1,2,0,1,2

第九章 转移指令的原理

可以修改IP,或同时修改CS和IP的指令统称为转移指令。
8086CPU的转移行为有以下几类:
-只修改IP时,称为段内转移,比如:jmp ax。
-同时修改CS和IP时,称为段间转移,比如:jmp 1000:0。
由于转移指令对IP的修改范围不同,段内转移又分为:短转移和近转移。

  • 短转移IP的修改范围为-128~127
  • 近转移IP的修改范围为-32768~32767
    8086CPU的转移指令分为一下几类:
  • 无条件转移指令(如:jmp)
  • 条件转移指令
  • 循环转移指令(如:loop)
  • 过程
  • 中断

9.1 操作符offset

操作符offset在汇编语言中是由编译器处理的符号,它的功能是取得标号的偏移地址。示例如下:

assume cs:codesg
codesg segment

    start:mov ax,offset start		;相当于mov ax,0
        s:mov ax,offset s           ;相当于mov ax,3

codesg ends
ends start

在上面的程序中,offset操作符取得了标号start和s的偏移地址0和3,所以指令:mov ax,offset start相当于指令mov ax,0,因为start是代码段中的标号,它所标记的指令是代码段中的第一条指令,偏移地址为0;
mov ax,offset s相当于指令mov ax,3,因为s是代码段中的标号,它所标记的指令是代码段中的第二条指令,第一条指令长度为3个字节,则s的偏移地址为3。

9.2 jmp指令

jmp为无条件转移指令,可以只修改IP,也可以同时修改CS和IP。
jmp指令要给出两种信息:

  • 转移的目的地址
  • 转移的距离(段间转移、段内短转移、段内近转移)

9.3 依据位移进行转移的jmp指令

jmp short 标号(转到标号处执行指令)
这种格式的jmp指令实现的是段内短转移,它对IP的修改范围为-128~127,即它向前转移时可以最多越过128个字节,向后转移可以最多越过127个字节。
转移的过程如下:
(1)8位位移=标号处的地址-jmp指令后的第一个字节的地址;
(2)short指明此处的位移为8位位移,进行的是段内短转移;
(3)8位位移的范围为-128~127,用补码表示;
(4)8位位移由编译程序在编译时算出。
示例程序如下:

assume cs:codesg
codesg segment

    start:mov ax,0
          jmp short s
          add ax,1
        s:inc ax

codesg ends
ends start

jmp near ptr 标号
该指令实现的是段内近转移,转移的过程如下:
(1)16位位移=标号处的地址-jmp指令后的第一个字节的地址;
(2)near ptr指明此处的位移为16位位移,进行的是段内近转移;
(3)16位位移的范围为-32768~32767,用补码表示;
(4)16位位移由编译程序在编译时算出。

9.4 转移的目的地址在指令中的jmp指令

jmp far ptr 标号
该指令实现的是段间转移,又称为远转移。
示例程序如下:

assume cs:codesg
codesg segment

    start:mov ax,0
          mov bx,0
          jmp far ptr s
          db 256 dup(0)
        s:add ax,1
          inc ax

codesg ends
ends start

9.5 转移地址在寄存器中的jmp指令

指令格式:jmp 16位reg
功能:(IP)=(16位reg)

assume cs:codesg
codesg segment

    start:mov ax,0
          mov bx,s
          jmp bx
          db 256 dup(0)
        s:add ax,1
          inc ax
          
          mov ax,4c00h
          int 21h

codesg ends
ends start

9.6 转移地址在内存中的jmp指令

转移地址在内存中的jmp指令有两种格式:
(1)jmp word ptr 内存单元地址(段内转移)
功能:该内存单元地址处存放着一个字,该字是转移的目的地址。
示例程序如下:

mov ax,0123h
mov ds:[0],ax
jmp word ptr ds:[0]
执行后,(IP)=0123h

mov ax,0123h
mov [bx],ax
jmp word ptr [bx]
执行后,(IP)=0123h

(2)jmp dword ptr 内存单元地址(段间转移)
功能:从内存单元地址开始处存放着两个字,高地址的字是转移的目的段地址,低地址处是转移的目的偏移地址。
(CS)=(内存单元地址+2)
(IP)=(内存单元地址)
示例程序如下:

mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2],0
jmp dword ptr ds:[0]

9.7 jcxz指令

jcxz指令为有条件转移指令,所有的有条件转移指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围为:-128~127。
指令格式:jcxz 标号(如果(cx)=0,转移到标号处执行。)
操作:
当(cx)=0时,(IP)=(IP)+8位位移;
(1)8位位移=标号处的地址-jcxz指令后的第一个字节的地址;
(3)8位位移的范围为-128~127,用补码表示;
(4)8位位移由编译程序在编译时算出。
当(cx)≠0时,不做跳转(程序继续向下执行)。

示例程序如下:

利用jcxz指令,实现在内存2000H段中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中。

assume cs:codesg
codesg segment

    start:mov ax,2000h
          mov ds,ax
          mov bx,0
        s:mov cx,ds:[bx]
          jcxz ok
          inc bx
          jmp short s
          
       ok:mov dx,bx
          
          mov ax,4c00h
          int 21h

codesg ends
ends start 

9.8 loop指令

loop指令为循环指令,所有的循环指令都是短转移,在对应的机器码中包含转移的位移,而不是目的地址。对IP的修改范围为:-128~127。
指令格式:loop 标号((cx)=(cx)-1,如果(cx)≠0,转移到标号处执行。)
操作:
(1)(cx)=(cx)-1;
(2)如果(cx)≠0,(IP)=(IP)+8位位移。
8位位移=标号处的地址-loop指令后的第一个字节的地址;
8位位移的范围为-128~127,用补码表示;
8位位移由编译程序在编译时算出。
如果(cx)=0,不再循环(程序继续向下执行)。

示例程序如下:

利用loop指令,实现在内存2000H段中查找第一个值为0的字节,找到后,将它的偏移地址存储在dx中。

assume cs:codesg
codesg segment

    start:mov ax,2000h
          mov ds,ax
          mov bx,0
        s:mov cl,[bx]
          mov ch,0
          inc cx
          inc bx
          loop s
          
       ok:dec bx
          mov dx,bx
          
          mov ax,4c00h
          int 21h

codesg ends
ends start     

第十章 CALL和RET指令

10.1 ret和retf

ret指令用栈中的数据,修改IP的内容,从而实现近转移;
retf指令用栈中的数据,修改CS和IP的内容,从而实现远转移。
CPU执行ret指令时,进行下面两步操作:

(1) (IP)=((ss)*16+(sp))
(2) (sp)=(sp)+2

CPU执行retf指令时,进行下面4步操作:

(1) (IP)=((ss)*16+(sp))
(2) (sp)=(sp)+2
(3) (CS)=((ss)*16+(sp))
(4) (sp)=(sp)+2

10.2 call指令

CPU执行call指令时,进行两步操作:

(1)将当前的IP或CS和IP压入栈中;
(2)转移。

10.3 依据位移进行转移的call指令

call标号(将当前的IP压栈后,转到标号处执行指令)
CPU执行此种格式的call指令时,进行如下操作:

(1) (sp)=(sp)-2
    ((ss)*16+(sp))=(IP)
(2) (IP)=(IP)+16位位移

其中16位位移:
16位位移=标号处的地址-call指令后的第一个字节的地址;
16位位移的范围为-32768~32767,用补码表示;
16位位移由编译程序在编译时算出。

10.4 转移的目的地址在指令中的call指令

“call far ptr 标号”实现的是段间转移。
CPU执行此种格式的call指令时,进行如下操作:

(1) (sp)=(sp)-2
    ((ss)*16+(sp))=(CS)
    (sp)=(sp)-2
    ((ss)*16+(sp))=(IP)  
(2) (CS)=标号所在段的段地址
    (IP) =标号在段中的偏移地址

10.5 转移地址在寄存器中的call指令

指令格式:call 16位reg
功能:

(sp)=(sp)-2
((ss)*16+(sp))=(IP)
(IP)=(16位reg)

使用示例:
在这里插入图片描述

10.6 转移地址在内存中的call指令

转移地址在内存中的call指令有两种格式:
(1)call word ptr 内存单元地址
示例程序:

mov sp,10h
mov ax,0123h
mov ds:[0],ax
call word ptr ds:[0]

执行后,(IP)=0123h,(sp)=0Eh。
即,将当前IP的内容压入栈中,ds:[0]

(2)call dword ptr 内存单元地址
示例程序:

mov sp,10h
mov ax,0123h
mov ds:[0],ax
mov word ptr ds:[2],0
call dword ptr ds:[0]

执行后,(CS)=0,(IP)=0123h,(sp)=0Ch。
即,将当前IP和CS的内容压入栈中,并跳转至ds:[0]

10.8 mul指令

mul指令是乘法指令,须注意以下两点:
(1)两个相乘的数:两个相乘的数,要么都是8位,要么都是16位。如果是8位,一个默认放在AL中,另一个放在8位reg或内存字节单元中;如果是16位,一个默认在AX中,另一个放在16位reg或内存字单元中。
(2)结果:如果是8位乘法,结果默认放在AX中;如果时16位乘法,结果高位默认在DX中存放,低位在AX中放。

格式如下:
mul reg
mul 内存单元

例如:
mul byte ptr ds:[0]
结果:(ax)=(al)*((ds)*16+0)
mul word ptr [bx+si+8]
结果:(ax)=(ax)*((ds)*16+(bx)+(si)+8)结果的低16位
     (ax)=(ax)*((ds)*16+(bx)+(si)+8)结果的高16位

示例:
(1)计算100*10000
100小于255,但10000大于255,所以必须做16位乘法,程序如下:
mov ax,100
mov bx,10000
mul bx
结果:(ax)=4240h,(dx)=000Fh

参数和结果传递的问题

先看一个示例程序:
编程计算data段中第一组数据的3次方,结果保留在后面一组dword单元中。

assume cs:code

data segment
    dw 1,2,3,4,5,6,7,8
    dd 8 dup (8)
data ends

code segment
    start:
    mov ax,data
    mov ds,ax
    mov si,0                 ;ds:si指向第一组word单元
    mov di,16                ;ds:di指向第二组dword单元
    
    mov cx,8
  s:mov bx,ds:[si]
    call cube
    mov [di],ax
    mov [di].2,dx
    add si,2
    add di,4
    loop s
      
    mov ax,4c00h
    int 21h   
        
    cube:
    mov ax,bx
    mul bx
    mul bx
    ret
code ends

end start

但该程序存在缺陷,对于有多个参数的需要传递的情况,寄存器就不够。
对于这种情况,可以将批量数据放到内存中,然后将它们所在内存空间的首地址放在寄存器中,传递给需要的子程序。对于具有批量数据的返回结果,也可以用同样的方法。
示例程序如下:
设计一个子程序,功能:将一个全是字母的字符串转化为大写。
分析:这个子程序需要知道两件事,字符串的内容和字符串的长度。因为字符串中的字母可能很多,所以不便将整个字符串的所有字母都直接传递给子程序。但是,可以将字符串在内存中的首地址放在寄存器中传递给子程序。因为子程序中要用到循环,可以使用loop指令,而循环的次数就是字符串的长度。

    capital:
    and byte ptr [si],11011111b        ;将ds:si所指向单元中的字母转化为大写
    inc si                             ;ds:si指向下一单元
    loop capital
    ret

编程,将data段中的字符串转化为大写。

assume cs:code

data segment
    db 'conversation'
data ends

code segment
    
    start:
    mov ax,data
    mov ds,ax
    mov si,0           ;ds:si指向字符串(批量数据)所在空间的首地址
    mov cx,12          ;cx存放字符串的长度
    call capital
    
    mov ax,4c00h
    int 21h
    
    capital:
    and byte ptr [si],11011111b        ;将ds:si所指向单元中的字母转化为大写
    inc si                             ;ds:si指向下一单元
    loop capital
    ret
    
code ends

end start

10.12 寄存器冲突的问题

设计一个子程序,功能:将多个全是字母,以0结尾的字符串,转化为大写。
示例程序如下:

assume cs:code,ds:data

data segment
    db 'word',0
    db 'unix',0
    db 'wind',0
    db 'good',0
data ends

code segment
    
    start:
    mov ax,data
    mov ds,ax
    mov bx,0
    
    mov cx,4
  s:mov si,bx
    call capital
    add bx,5
    loop s
    
    mov ax,4c00h
    int 21
    
    capital:
    mov cl,[si]
    mov ch,0
    jcxz ok
    and byte ptr [si],11011111b
    inc si
    jmp short capital
    
code ends

ends start

上述程序在思想上正确,但在细节上却有些错误。
分析:
问题在于cx的使用,主程序要使用cx记录循环次数,可是子程序中也使用了cx,在执行子程序的时候,cx中保存的循环计数值被改变,使得主程序的循环出错。

问题一般化: 子程序中使用的寄存器,很可能在主程序中也要使用,造成了寄存器使用上的冲突。

问题解决:
我们希望:

  • 编写调用子程序的程序的时候,不必关心子程序到底使用了哪些寄存器;
  • 编写子程序的时候不必关心调用者使用了哪些寄存器;
  • 不会发生寄存器冲突。

解决这个问题的简捷方法是,在子程序的开始将子程序中所有用到的寄存器中的内容保存起来,在子程序返回前再恢复。可以用栈来保存寄存器中的内容。
以后,编写子程序的标准框架如下:

子程序开始: 子程序中使用的寄存器入栈
           
           子程序内容
           
           子程序中使用的寄存器出栈
           
           返回(ret、retf)

由此,改进一下子程序capital的设计:

capital: push cx
         push si
         
 change: mov cl,[si]
         mov ch,0
         jcxz ok
         and byte ptr [si],11011111b
         inc si
         jmp short change
         
     ok: pop si
         pop cx
         ret

注:寄存器入栈和出栈的顺序须对应。

第十一章 标志寄存器

CPU内部的寄存器中,有一种特殊的寄存器,具有以下三种作用。
(1)用来存储相关指令的某些执行结果;
(2)用来为CPU指向相关指令提供行为依据;
(3)用来控制CPU的相关工作方式。
这种特殊的寄存器在8086CPU中,被称为标志寄存器。
8086CPU的标志寄存器有16位,其中存储的信息通常被称为程序状态字(PSW)。
8086CPU的标志寄存器(flag)的结构如下图所示:
在这里插入图片描述
flag的1,3,5,12,13,14,15位在8086CPU中没有使用,不具有任何含义。

11.1 ZF标志

flag的第6位是ZF,零标志位。它记录相关指令执行后,其结果是否为0。如果结果为0,那么zf=1;否则,zf=0。
示例如下:

eg1:
mov ax,1
sub ax,1
执行后,结果为0,则zf=1。

eg2:
mov ax,1
and ax,0
执行后,结果为0,则zf=1,表示“结果是0”。

eg3:
mov ax,1
or ax,0
执行后,结果不为0,则zf=0,表示“结果非0”。

注意,在8086CPU的指令集中,有的指令的执行是影响标志寄存器的,比如,add、sub、mul、div、inc、or、and等,它们大都是运算指令;有的指令的执行对标志寄存器没有影响,比如,mov、push、pop等,它们大都是传送指令。

平时在使用一条指令的时候,要注意这条指令的全部功能,包括执行结果对标志寄存器的哪些标志位造成影响。

11.2 PF标志

flag的第2位是PF,奇偶标志位。它记录相关指令执行后,其结果的所有bit位中1的个数是否为偶数。如果1的个数为偶数,pf=1;否则,pf=0。
示例如下:

eg1:
mov al,1
add al,10
执行后,结果为00001011B,其中有3(奇数)个1,则pf=0。

eg2:
mov al,1
or al,2
执行后,结果为00000011B,其中有2(偶数)个1,则pf=1。

11.3 SF标志

flag的第7位是SF,符号标志位。它记录相关指令执行后,其结果是否为负。如果结果为负,sf=1;如果非负,sf=0。
计算机中通常用补码来表示有符号数据。计算机中的一个数据可以看作是有符号数,也可以看作是无符号数。比如:

00000001B,可以看作为无符号数1,或有符号数+1;
10000001B,可以看作为无符号数129,也可以看作有符号数-127。

也就是说,对于同一个二进制数,计算机可以将它当作无符号数据来运算,也可以当作有符号数据来运算。比如:

mov al,10000001B
add al,1
结果:(al)=10000010B
将上述编码视为无符号数的运算,add指令相当于计算129+1,则结果为130(10000010B);
将上述编码视为有符号数的运算,add指令相当于计算-127+1,则结果为-126(10000010B)。

总之,CPU在执行add等指令时,是必然要影响到SF标志位的值的,至于需不需要这种影响,就取决于我们如何看待指令所要进行的运算了。
示例如下:

eg1:
mov al,10000001B
add al,1
执行后,结果为10000010B,sf=1,表示:如果指令进行的是有符号数运算,那么结果为负:

eg2:
mov al,10000001B
add al,01111111B
执行后,结果为0,sf=0,表示:如果指令进行的是有符号数运算,那么结果为非负。

11.4 CF标志

flag的第0位是CF,进位标志位。一般情况下,在进行无符号数运算的时候,它记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。
对于位数为N的无符号数来说,其对应的二进制信息的最高位,即第N-1位,就是它的最高有效位,而假想存在的第N位,就是相对于最高有效位的更高位,如下图所示:
1
当两个数据相加时,有可能产生从最高有效位向更高位的进位。比如,两个8位数据:98H+98H,将产生进位。由于这个进位值在8位数中无法保存,这个进位将被记录在一个特殊的寄存器的某一位上。8086CPU就用CF位来记录这个进位值。
示例如下:

mov al,98h
add al,al      ;执行后:(al)=30H,CF=1,CF记录了从最高有效位向更高位的进位值
add al,al      ;执行后:(al)=60H,CF=0,CF记录了从最高有效位向更高位的进位值

而当两个数据做减法时,有可能向更高位借位。比如,两个8位数据:97H-98H,将产生借位,借位后,相当于计算197H-98H。而flag的CF位也可以用来记录这个借位值。
示例如下:

mov al,97h
sub al,98h     ;执行后,(al)=FFH,CF=1,CF记录了向更高位的借位值
sub al,al      ;执行后,(al)=0,CF=0,CF记录了向更高位的借位值

11.5 OF标志

flag的第11位是OF,溢出标志位。一般情况下,OF记录了有符号数运算的结果是否发生了溢出。如果发生溢出,OF=1;否则,OF=0。

一定要注意CF和OF的区别:CF是对无符号数运算有意义的标志位,而OF是对有符号数运算有意义的标志位。比如:
mov al,98
add al,99
add指令执行后:CF=0,OF=1。CPU在执行add等指令的时候,就包含了两种含义:无符号数运算和有符号数运算。对于无符号数运算,CPU用CF位来记录是否产生了进位;对于有符号数运算,CPU用OF位来记录是否产生了溢出,当然,还要用SF位来记录结果的符号。对于无符号数运算,98+99没有进位,CF=0;对于有符号数运算,98+99发生溢出,OF=1。

11.6 adc指令

adc是带进位加法指令,它利用了CF位上记录的进位值。
指令格式:adc 操作对象1,操作对象2
功能:操作对象1=操作对象1+操作对象2+CF
示例如下:

eg1:
mov ax,2
mov bx,1
sub bx,ax
adc ax,1
执行后,(ax)=4。adc执行时,相当于计算:(ax)+1+CF=2+1+1=4。

eg2:
mov al,1
add ax,ax
adc ax,3
执行后,(ax)=5。adc执行时,相当于计算:(ax)+3+CF=2+3+0=5

adc指令和add指令相配合就可以对更大的数据进行加法运算。例如:
编程,计算1EF000H+201000H,结果放在ax(高16位)和bx(低16位)中。
因为两个数据的位数都大于16,用add指令无法进行计算。我们将计算分成两步进行,先将低16位相加,然后将高16位和进位值相加。

mov ax,001eh
mov bx,0f000h
add bx,1000h
adc ax,0020h

编程,计算1EF0001000H+2010001EF0H,结果放在ax(最高16位),bx(次高16位),cx(低16位)中。

mov ax,001eh
mov bx,0f000h
mov cx,1000h
add cx,1ef0h
adc bx,1000h
adc ax,0020h

下面编写一个子程序,对两个128位数据进行相加。
名称:add128
功能:两个128位数据进行相加。
参数:ds:si指向存储单元第一个数的内存空间,因数据为128位,所以需要8个字单元,由低地址单元到高地址单元依次存放128位数据由低到高的各个字。运算结果存储在第一个数的存储空间中。ds:di指向存储第二个数的内存地址。
程序如下:

add128:
push ax
push cx
push si
push di

sub ax,ax     ;将CF设置为0

mov cx,8
s:
mov ax,[si]
adc ax,[di]
mov [si],ax
inc si
inc si
inc di
inc di
loop s

pop di
pop si
pop cx
pop ax
ret 

inc和loop指令不影响CF位

11.7 sbb指令

sbb是带借位减法指令,它利用了CF位上记录的借位值。
指令格式:sbb 操作对象1,操作对象2
功能:操作对象1=操作对象1-操作对象2-CF
利用sbb指令可以对任意大的数据进行减法运算。比如,计算003E1000H-00202000H,结果放在ax,bx中,程序如下:

mov bx,1000h
mov ax,003eh
sub bx,2000h
sbb ax,0020h

11.8 cmp指令

cmp是比较指令,cmp的功能相当于减法指令,只是不保存结果。
指令格式:cmp 操作对象1,操作对象2
功能:计算操作对象1-操作对象2,但并不保存结果,仅仅根据计算结果对标志寄存器进行设置。
比如,指令cmp ax,ax,做(ax)-(ax)的运算,结果为0,但并不在ax中保存,仅影响flag的相关各位。指令执行后,zf=1,pf=1,sf=0,cf=0,of=0。
示例如下:

eg1:
mov ax,8
mov bx,3
cmp ax,bx
执行后,(ax)=8,zf=0,pf=1,sf=0,cf=0,of=0。

利用cmp指令对无符号数进行比较时,相关标志位对比较结果的记录。

cmp ax,bx
如果(ax)=(bx),则(ax)-(bx)=0,所以:zf=1;
如果(ax)≠(bx),则(ax)-(bx)≠0,所以:zf=0;
如果(ax)<(bx),则(ax)-(bx)将产生借位,所以:cf=1;
如果(ax)≥(bx),则(ax)-(bx)不必借位,所以:cf=0;
如果(ax)>(bx),则(ax)-(bx)既不必借位,结果又不为0,所以:cf=0并且zf=0;
如果(ax)≤(bx),则(ax)-(bx)既可能借位,结果可能为0,所以:cf=1或zf=1。

利用cmp指令对有符号数进行比较时,相关标志位对比较结果的记录。
所得到的相应结果的正负,并不能说明,运算所应该得到的结果的正负。这是因为在运算的过程中可能发生溢出。如果有这样的情况发生,那么,仅根据sf的值就不能说明任何问题。比如:

mov ah,22h
mov bh,0a0h
sub ah,bh

结果sf=1,运算实际得到的结果是(ah)=82h,但是在逻辑上,运算所应该得到的结果是:34-(-96)=130。就是因为130这个结果作为一个有符号数超出了-128~127这个范围,在ah中不能表示,而ah中的结果被CPU当作有符号数解释为-126。而sf被用来记录这个ah寄存器中的实际结果的正负,所以sf=1。但仅sf=1不能说明在逻辑上,运算所得的正确结果的正负。
又比如:

mov ah,08ah
mov bh,070h
cmp ah,bh

结果sf=0,运算(ah)-(bh)实际得到的结果是1AH,但是在逻辑上,运算所应得到的结果是:(-118)-112=-230。sf记录ah寄存器中的正负,所以sf=0。但sf=0不能说明在逻辑上,运算所得到的正确结果。

从上面的分析中可以看出,实际结果的正负,之所以不能说明逻辑上真正结果的正负,关键的原因在于发生了溢出。如果没有溢出发生的话,那么,实际结果的正负和逻辑上真正结果的正负就一致了。
所以,我们应该同时根据sf(实际结果的正负)和of(是否溢出)来判断逻辑上真正结果的正负。
以cmp ah,bh为例,总结如下:

(1) 如果sf=1,而of=0
of=0,说明没有溢出,则逻辑上真正结果的正负=实际结果的正负;
因sf=1,实际结果为负,所以逻辑上真正的结果为负,所以(ah)<(bh)。

(2) 如果sf=1,而of=1
of=1,说明有溢出,逻辑上真正的结果的正负≠实际结果的正负;
因sf=1,实际结果为负。
实际结果为负,而又有溢出,说明由于溢出导致了实际结果为负,如果因为溢出导致了实际结果为负,
那么逻辑上真正的结果必然为正。
这样,sf=1,of=1,说明了(ah)>(bh)。

(3) 如果sf=0,而of=1
of=1,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;
因sf=0,实际结果非负。而of=1说明有溢出,则结果非负,所以,实际结果为正。
实际结果为正,而又有溢出,说明由于溢出导致了实际结果非负,如果因为溢出导致了实际结果为正,
那么逻辑上真正的结果必然为负。
这样,sf=0,of=1,说明了(ah)<(bh)。

(4) 如果sf=0,而of=0
of=0,说明没有溢出,则逻辑上真正结果的正负=实际结果的正负;
因sf=0,实际结果非负,所以逻辑上真正的结果非负,所以(ah)≥(bh)。

11.9 检测比较结果的条件转移指令

“转移”指的是它能修改IP,而“条件”指的是它可以根据某种条件,决定是否修改IP。
因为cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种,即根据无符号数的比较结果进行转移的条件转移指令(它们检测zf、cf的值)和根据有符号数的比较结果进行转移的条件转移指令(它们检测sf、of和zf的值)。
下面是常用的根据无符号数的比较结果进行转移的条件转移指令:
1
示例程序如下:
编程实现,如果(ah)=(bh),则(ah)=(ah)+(ah);否则(ah)=(ah)+(bh)。

cmp ah,bh
je s
add ah,bh
jmp short ok
s:add ah,ah
ok:...

data段中的8个字节如下:

data segment
    db 8,11,8,1,8,5,63,38
data ends  

编程,统计data段中数值为8的字节的个数,用ax保存统计结果。

assume cs:code
         
data segment
    db 8,11,8,1,8,5,63,38
data ends         
         
code segment
    
    start:
    mov ax,data
    mov ds,ax
    mov si,0
    
    mov ax,0
    mov cx,8
    s:
    cmp byte ptr ds:[si],8
    jne k
    inc ax
    k:
    inc si
    loop s
    
    mov ax,4c00h
    int 21

code ends

end start

11.10 DF标志和串传送指令

flag的第10位是DF,方向标志位。在串处理指令中,控制每次操作后si、di的增减。
df=0 每次操作后si、di递增
df=1 每次操作后si、di递减

格式:movsb
功能:将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器df位的值,将
si和di递增2或递减2。
格式:movsw
功能:将ds:si指向的内存字单元中的字送入es:di中,然后根据标志寄存器df位的值,将
si和di递增2或递减2。

一般来说,movsb和movsw都和rep配合使用,格式如下:

rep movsb
用汇编语法来描述rep movsb的功能就是:
s:movsb
  loop s
可见,rep的作用是根据cx的值,重复执行后面的串传送指令。由于每执行一次movsb指令
si和di都会递增或递减指向后一个单元或前一个单元,则rep movsb就可以循环实现(cx)
个字符的传送。
同理,也有rep movsw

8086CPU提供下面两条指令对df位进行设置。

cld指令:将标志寄存器的df位置0
std指令:将标志寄存器的df位置1

示例如下:
编程,用串传送指令,将data段中的第一个字符串复制到它后面的空间中。

assume cs:code
         
data segment
    db 'Welcome to masm!'
    db 16 dup (0)
data ends         
         
code segment
    
    start:
    mov ax,data
    mov ds,ax
    mov si,0
    
    mov es,ax
    mov di,16
    
    mov cx,16
    cld
    rep movsb
    
    mov ax,4c00h
    int 21

code ends

end start

注:
使用串传送指令进行数据的传送,需要一下必要的信息:
① 传送的原始位置:ds:si;
② 传送的目的位置:es:di;
③ 传送的长度:cx;
④ 传送的方向:df。

11.11 pushf和popf

pushf的功能是将标志寄存器的值压栈,而popf是从栈中弹出数据,送入标志寄存器中。
示例程序如下:
下面程序执行后:(ax)=?

mov ax,0
push ax
popf                 ;popf执行后,将标志寄存器的值全置为0
mov ax,0fff0h        
add ax,0010h         ;该句执行后,ax的值为零,但标志寄存器的值从cf至df为11001000
pushf                ;pushf执行后,将标志寄存器的值全压入栈中
pop ax               ;该句执行后,ax为47h
and al,11000101B
and ah,00001000B

11.12 标志寄存器在Debug中的表示

在Debug中,标志寄存器是按照有意义的各个标志位单独表示的。
1
下面列出Debug对我们已知的标志位的表示。
2

第12章 内中断

12.1 内中断的产生

对于8086CPU,当CPU内部有下面的情况发生的时候,将产生相应的中断信息。

(1)除法错误,比如,执行div指令产生的除法溢出;
(2)单步执行;
(3)执行into指令;
(4)执行int指令。

CPU要进行不同的处理,首先要知道所接收到的中断信息的来源。所以中断信息中必须包含识别来源的编码。8086CPU用称为中断类型码的数据来标识中断信息的来源。中断类型码为一个字节型数据,可以表示256中中断信息的来源。我们将产生中断信息的事件,即中断信息的来源,简称为中断源,上述4种中断源在8086CPU中的中断类型码如下:

(1)除法错误:0
(2)单步执行:1
(3)执行into指令:4
(4)执行int指令,该指令的格式为int n,指令中的n为字节型立即数,是提供给CPU的中断类型码。

12.3 中断向量表

CPU用8位的中断类型码通过中断向量表找到相应的中断处理程序的入口地址。
中断向量表就是中断向量的列表。
中断向量就是中断处理程序的入口地址。
总之,中断向量表就是中断处理程序入口地址的列表。
1
中断向量表在内存中存放,对于8086PC机,中断向量表指定放在内存地址0处。从内存0000:0000到0000:03FF的1024个单元中存放着中断向量表。
并且,在8086PC机中该中断向量表必须放在此处。
一个表项存放一个中断向量,也就是一个中断处理程序的入口地址,对于8086CPU,这个入口地址包括段地址和偏移地址,所以一个表项占两个字,高地址字存放段地址,低地址字存放偏移地址。

12.4 中断过程

下面是8086CPU在收到中断信息后,所引发的中断过程。

(1)从中断信息中取得中断类型码;
(2)标志寄存器的值入栈;
(3)设置标志寄存器的第8位TF和第9位IF的值;
(4)CS的内容入栈;
(5)IP的内容入栈;
(6)从内存地址为中断类型码*4和中断类型码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS。

CPU在收到中断信息后,如果处理该中断信息,就完成一个由硬件自动执行的中断过程(程序员无法改变该过程)。

12.5 中断处理程序和iret指令

中断处理程序的编写方法和子程序的比较相似,如下:

(1)保存用到的寄存器;
(2)处理中断;
(3)恢复用到的寄存器;
(4)用iret指令返回。

iret指令的功能用汇编语法描述为:
pop IP
pop CS
popf

iret通常和硬件自动完成的中断过程配合使用。

12.7 编写处理0号中断

现在我们考虑改变一下0号中断处理程序的功能,即重新编写一个0号中断处理程序,它的功能是在屏幕中间显示“overflow!”,然后返回到操作系统,如下图所示:
1
当CPU执行div bh后,发生了除法溢出错误,产生0号中断信息,引发中断过程,CPU执行我们编写的0号中断处理程序。在屏幕中间显示提示信息“overflow!”后,返回到操作系统。

编程,当发生除法溢出时,在屏幕中间显示“overflow!”,返回DOS。
分析:
(1)当发生除法溢出时,产生0号中断信息,从而引发中断过程。
此时,CPU将进行以下工作。
① 取得中断类型码0;
② 标志寄存器入栈,TF、IF设置为0;
③ CS、IP入栈;
④ (IP)=(04),(CS)=(04+2)。
(2)当中断0发生时,CPU转去执行中断处理程序。
① 相关处理;
② 向显示缓冲区送字符串“overflow!”;
③ 返回DOS。

一般情况下,从0000:0200至0000:02FF的256个字节的空间所对应的中断向量表项都是空的,操作系统和其他应用程序都不占用。

总之,编写该中断程序需要完成以下内容:
(1)编写可以显示“overflow!”的中断处理程序:do0;
(2)将do0送入内存0000:0200处;
(3)将do0的入口地址0000:0200存储在中断向量表0号表项中。
程序如下:

assume cs:code

code segment
    
    start:
    ;do0安装程序
    mov ax,0                      ;设置es:di指向目的地址
    mov es,ax
    mov di,200h
    
    mov ax,cs                     ;设置ds:si指向源地址
    mov ds,ax
    mov si,offset do0
    
    mov cx,offset do0end-offset do0   ;设置cx为传输长度    
    cld                           ;设置传输方向为正    
    rep movsb
    
    ;设置中断向量表
    mov ax,0
    mov es,ax
    mov word ptr es:[0*4],200h
    mov word ptr es:[0*4+2],0    
    
    mov ax,4c00h
    int 21h
    
    do0:
    jmp short do0start            ;该条指令占两个字节
    db "overflow!"
    
    do0start:    
    ;设置ds:si指向字符串
    mov ax,cs
    mov ds,ax
    mov si,202h    
    
    mov ax,0b800h
    mov es,ax
    mov di,12*160+36*2            ;设置es:di指向显存空间的中间位置
    
    mov cx,9                      ;设置cx为字符串长度
    s:
    mov al,[si]
    mov es:[di],al
    inc si
    add di,2
    loop s
    
    mov ax,4c00h
    int 21h
    
    do0end:
    nop
    
code ends

end start

第13章 int指令

13.1 int指令

int指令的格式为:int n,n为中断类型码,它的功能是引发中断过程。
CPU执行int n指令,相当于引发一个n号中断的中断过程,执行过程如下:

(1)取中断类型码n;
(2)标志寄存器入栈,IF=0,TF=0;
(3)CS、IP入栈;
(4)(IP)=(n*4),(CS)=(n*4+2)。

可以在程序中使用int指令调用任何一个中断的中断处理程序。示例程序如下:

assume cs:code

code segment
    
    start:
    mov ax,0b800h
    mov es,ax
    mov byte ptr es:[12*160+40*2],'!'
    int 0
    
code ends

end start

13.2 编写供应用程序调用的中断例程

编写、安装中断7ch的中断例程。
功能:求一word型数据的平方。
参数:(ax)=要计算的数据。
返回值:dx、ax中存放结果的高16位和低16位。
应用举例:求2*3456^2
7ch中断安装程序:

assume cs:code

code segment
    
    start:
    mov ax,0
    mov es,ax
    mov di,200h
    
    mov ax,cs
    mov ds,ax
    mov si,offset sqr
    
    mov cx,offset sqrend-offset sqr
    cld
    rep movsb
    
    ;设置中断向量表
    mov ax,0
    mov es,ax
    mov word ptr es:[7ch*4],200h
    mov word ptr es:[7ch*4+2],0
    
    mov ax,4c00h
    int 21h   
    
    sqr:
    mul ax
    iret
    
    sqrend:
    nop
    
code ends

end start

测试程序:

assume cs:code

code segment
    
    start:
    mov ax,0b800h
    mov es,ax
    mov di,160*12
    
    mov bx,0ffset s-offset se
    mov cx,80
    s:
    mov byte ptr es:[di],'!'
    add di,2
    int 7ch
    se:
    nop
    
    mov ax,4c00h
    int 21h
    
code ends

end start

注意,在中断例程sqr的最后,要使用iret指令。用汇编语法描述,iret指令的功能为:
pop IP
pop CS
popf

13.3 对int、iret和栈的深入理解

问题:用7ch中断例程完成loop指令的功能。
loop s的执行需要两个信息,循环次数和到s的位移,所以,7ch中断例程要完成loop指令的功能,也需要这两个信息作为参数。用cx存放循环次数,bx存放位移。
应用举例:在屏幕中间显示80个"!"。
7ch中断安装程序:

assume cs:code

code segment
    
    start:
    ;安装中断例程
    mov ax,0
    mov es,ax
    mov di,200h
        
    mov ax,cs
    mov ds,ax
    mov si,offset jmp_near_ptr
    
    mov cx,offset jmp_near_ptr_end-offset jmp_near_ptr  
    cld
    rep movsb
    
    ;设置中断向量表
    mov ax,0
    mov es,ax
    mov word ptr es:[7ch*4],200h
    mov word ptr es:[7ch*4+2],0
    
    mov ax,4c00h
    int 21h
    
    jmp_near_ptr:
    push bp
    mov bp,sp
    add ss:[bp+2],bx    
    jmp_near_ptr_ret:
    pop bp
    iret
    
    jmp_near_ptr_end:
    nop
    
code ends

end start

测试程序:

assume cs:code

data segment
    db 'conversation',0
data ends

code segment
    
    start:
    mov ax,data
    mov ds,ax
    mov si,0
    
    mov ax,0b800h
    mov es,ax
    mov di,12*160
    
    s:
    cmp byte ptr [si],0
    je ok
    mov ah,02h
    mov al,[si]
    mov word ptr es:[di],ax
    inc si
    add di,2
    mov bx,offset s-offset ok
    int 7ch
    
    ok:
    mov ax,4c00h
    int 21h
    
code ends

end start

13.4 BIOS和DOS所提供的中断例程

在系统版的ROM中存放着一套程序,称为BIOS(基本输入输出系统),BIOS中主要包含以下几部分内容。

(1)硬件系统的检测和初始化程序;
(2)外部中断和内部中断的中断例程;
(3)用于对硬件设备进行IO操作的中断例程;
(4)其他和硬件系统相关的中断例程。

操作系统DOS也提供了中断例程,从操作系统的角度看,DOS的中断例程就是操作系统向程序员提供的编程资源。

13.5 BIOS和DOS中断例程的安装过程

在前面,我们都是自己编写中断例程,将它们放到安装程序中,然后运行安装程序,将它们安装到指定的内存区中。
而BIOS和DOS提供的中断例程是如何安装到内存中的呢?
(1)开机后,CPU一加电,初始化(CS)=0FFFFH,(IP)=0,自动从FFFF:0单元开始执行程序。FFFF:0处有一条跳转指令,CPU执行该指令后,转去执行BIOS中的硬件系统检测和初始化程序。
(2)初始化程序将建立BIOS所支持的中断向量,即将BIOS提供的中断例程的入口地址登记在中断向量表中。注意,对于BIOS所提供的中断例程,只需将入口地址登记在中断向量表中即可,因为它们是固化到ROM中的程序,一直在内存中存在。
(3)硬件系统检测和初始化完成后,调用int 19h进行操作系统的引导。从此将计算机交由操作系统控制。
(4)DOS启动后,除完成其他工作外,还将它所提供的中断例程装入内存,并建立相应的中断向量。

13.6 BIOS中断例程应用

举例如下:
int 10h 中断例程是BIOS提供的中断例程,其中个包含了多个和屏幕输出相关的子程序。
一般来说,一个供程序员调用的中断例程往往包含多个子程序,中断例程内部用来传递进来的参数来决定执行哪个子程序。BIOS和DOS提供的中断例程,都用ah来传递内部子程序的编号。
int 10h 中断例程的设置光标位置功能:

mov ah,2       ;置光标
mov bh,0       ;第0页
mov dh,5       ;dh中放行号
mov dl,12      ;dl中放列号
int 10h

(ah)=2表示调用第10h号中断例程的2号子程序,功能为设置光标位置。
int 10h 中断例程的在光标位置显示字符功能:

mov ah,9          ;在光标位置显示字符
mov al,'a'        ;字符
mov bl,7          ;颜色属性
mov bh,0          ;第0页
mov cx,3          ;字符重复个数
int 10h

(ah)=9表示调用第10h号中断例程的9号子程序,功能为在光标位置显示字符。
示例程序如下:
编程,在屏幕的5行12列显示3个红底高亮闪烁绿色的‘a’。

assume cs:code

code segment
    
    mov ah,2
    mov bh,0
    mov dh,5
    mov dl,12
    int 10h
    
    mov ah,9
    mov al,'a'
    mov bl,11001010b
    mov bh,0
    mov cx,3
    int 10h
    
    mov ax,4c00h
    int 21h
    
code ends

ends start

int 21h 中断例程是DOS提供的中断例程,其中包含了DOS提供给程序员在编程时调用的子程序。
我们前面一直使用的是int 21h中断例程的4ch号功能,即程序返回功能,如下:

mov ah,4ch ;程序返回
mov al,0   ;返回值
int 21h

(ah)=4ch表示调用第21号中断例程的4ch号子程序,功能为程序返回,可以提供返回值作为参数。
前面使用这个功能时经常写做:

mov ax,4c00h
int 21h

int 21h 中断例程在光标位置显示字符串的功能:

ds:dx指向字符串      ;要显示的字符串用"$"作为结束符
mov ah,9            ;功能号9,表示在光标位置显示字符串
int 21h

(ah)=9表示调用第21h号中断例程的9号子程序,功能为在光标位置显示字符串。
示例程序如下:
编程,在屏幕的5行12列显示字符串“Welcome to masm!”。

assume cs:code

data segment
    db 'Welcome to masm','$'
data ends    
    
code segment
    
    start:
    mov ah,2
    mov bh,0
    mov dh,5
    mov dl,12
    int 10h
    
    mov ax,data
    mov ds,ax
    mov dx,0
    mov ah,9
    int 21h
    
    mov ax,4c00h
    int 21h
    
code ends

end start

第14章 端口

14.1 端口的读写

在访问端口时,CPU端口地址来定位端口。因为端口所在的芯片和CPU通过总线相连,所以,端口地址和内存地址一样,通过地址总线来传送。在PC系统中,CPU最多可以定位64KB个不同的端口。则端口地址的范围为0~65535。
我们来看一下CPU执行内存访问指令和端口访问指令时,总线上的信息:
(1)访问内存:

mov ax,ds:[8]      ;假设执行前(ds)=0

执行时与总线相关的操作如下:

① CPU通过地址线将地址信息8读出;
② CPU通过控制线发出内存读命令,选中存储器芯片,并通知它,将要从中
读取数据;
③ 存储器将8号单元中的数据通过数据线送入CPU。

(2)访问端口:

in al,60h      ;从60h号端口读入一个字节

执行时与总线相关的操作如下:

① CPU通过地址线将地址信息60h发出;
② CPU通过地址线发出端口读命令,选中端口所在的芯片,并通知它,将要
从中读取数据;
③ 端口所在的芯片将60h端口中的数据通过数据线送入CPU。

注意,在in和out指令中,只能使用ax或al来存放从端口中读入的数据或要发送到端口中的数据。访问8位端口时用al,访问16位端口时用ax。
对0~255以内的端口进行读写时:

in al,20h         ;从20h端口读入一个字节
out 20h,al        ;从20h端口写入一个字节

对256~65535的端口进行读写时,端口号放在dx中:

mov dx,3f8h       ;将端口3f8h送入dx
in al,dx          ;从3f8h端口读入一个字节
out dx,al         ;从3f8h端口写入一个字节

14.3 shl和shr指令

shl和shr是逻辑移位指令。
shl是逻辑左移指令,功能为:

(1)将一个寄存器或内存单元中的数据向左移位;
(2)将最后移出的一位写入CF中;
(3)最低位用0补充。

示例程序如下:

mov al,01001000b
shl al,1                        ;将al中的数据左移一位
执行后(al)=10010000b,CF=0

注:如果移动位数大于1时,必须将移动位数放在cl中。

shr是逻辑右移指令,它和shl所进行的操作刚好相反。

(1)将一个寄存器或内存单元中的数据向右移位;
(2)将最后移出的一位写入CF中;
(3)最高位用0补充。

14.4 CMOS RAM中存储的时间信息

在CMOS RAM中,存放着当前的时间:年、月、日、时、分、秒。这6个信息的长度都为1个字节,存放单元为:
秒:0 分:2 时:4 日:7 月:8 年:9
这些数据以BCD码的方式存放。(此处采用的是压缩BCD码,即以4位二进制数表示一位十进制数)
在CMOS RAM存储时间信息的单元中,存储了用两个BCD码表示的两位十进制数,高4位的BCD码表示十位,低4位的BCD码表示个位。比如,00010100b表示14。
示例程序如下:
编程,从CMOS RAM的8号单元读取当前月份的BCD码。

要读取CMOS RAM的信息,首先要向地址端口70h写入要访问的单元的地址;
mov al,8
out 70h,al
然后从数据端口71h中取得指定单元中的数据;
in al,71h

第15章 外中断

在计算机系统中,CPU除了能够执行指令,进行运算以外,还应该能够对外部设备进行控制,接收它们的输入,向它们进行输出。
要及时处理外设的输入,显然需要解决两个问题:①外设的输入随时可能发生,CPU如何得知?②CPU从何处得到外设的输入?

15.1 接口芯片和端口

第14章我们讲过,PC系统的接口卡和主板上,装有各种接口芯片。这些外设接口芯片的内部有若干寄存器,CPU将这些寄存器当作端口来访问。
外设的输入不直接送入内存和CPU,而是送入相关的接口芯片的端口;CPU向外设的输出也不是直接送入外设,而是先送入端口中,再由相关的芯片送到外设。
可见,CPU通过端口和外部设备进行联系。

15.2 外中断信息

外设的输入随时可能发生,CPU如何得知?
CPU提供中断机制来满足这种需要。前面讲过,当CPU的内部有需要处理的事情发生的时候,将产生中断信息,引发中断过程。这种中断信息来自CPU的内部。
另一种中断信息来自于CPU外部,即外中断。外中断源一共有以下两类:
1、可屏蔽中断
可屏蔽中断是CPU可以不响应的外中断。CPU是否响应可屏蔽中断,要根据标志寄存器的IF位。如果IF=1,则CPU可响应该中断;否则,不响应该中断。
8086CPU提供的设置IF的指令如下:

sti,设置IF=1;
cli,设置IF=0。

2、不可屏蔽中断
不可屏蔽中断是CPU必须响应的外中断。当CPU检测到不可屏蔽的中断信息时,则在执行完当前指令后,立即响应,引发中断过程。
对于8086CPU,不可屏蔽中断的中断类型码固定为2,所以中断过程中,不需要取中断类型码。则不可屏蔽中断的中断过程为:

(1)标志寄存器入栈,IF=0,TF=0;
(2)CS、IP入栈;
(3)(IP)=(8),(CS)=(0AH)。
  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值