中断与外部设备操作

位移指令

示例一:

mov al,01001000b
shl al,1
图1:shl左移结果
图2:左移和右移与cf值

示例二:

SHL OPR,CNT, 将OPR 逻辑左移CNT 位

  1. 将寄存器或内存单元中的数据向左移位
  2. 将最后移出的移位写入CF 中
  3. 最低位用0 补充
mov al,01010001b
mov cl,3 ;移动位数大于1时必须用cl
shl al,cl

结果为(al)=10001000b cf 为0

惯常用法, 将x 逻辑左移一位, 相当于执行x=x*2, 右移一位, 相当于执行x=x/2

操作显存数据

显示的原理

图3:显示原理

显示缓冲区的结构

图4:dos显示结构
各行所需字节数 显示缓冲区地址范围
160(A0H) B800:0000~B800:009F
160(A0H) B800:00A0~B800:013F
160(A0H) B800:0140~B800:01DF
160(A0H) B800:0F00~B800:0F9F

示例三:

在屏幕中间, 白底蓝字, 显示welcome to masm!

assume cs:code,ds:data
data segment
  db 'wolcome to masm!'
data ends

code segment
start:
  ; 初始化寄存器
  mov ax,data
  mov ds,ax
  mov ax,0B800H
  mov es,ax
  mov si,0
  mov di,160*12+80-16
  ; 显示字符串
  mov cx,16
w:
  mov al,[si]
  mov es:[di],al
  inc di
  mov al,71H
  mov es:[di],al
  inc si
  inc di
  loop w

  mov ax,4c00h
  int 21h
code ends
end start

描述内存单元的标号

  • 代码段中的标号可以用来标记指令, 段的起始地址

  • 代码段中的数据也可以用标号

  • 数据标号不同于仅仅表示地址的地址标号, 数据标号存储数据单元的地址和长度

示例四:

assume cs:code

code segment
  a db 1,2,3,4,5,6,7,8
  b dw 0
start:
  mov si,0
  mov cx,8
s:
  mov al,a[si]
  mov ah,0
  add b,ax
  inc si
  loop s
  
  mov ax,4c00h
  int 21h
code ends
end start
  • 在code 段中使用标号a, b 后面没有冒号:, 它们同时描述内存地址和单元长度的标好
  • 标号a
    • 地址code:0
    • 以后内存单元都是字节
  • 标号b
    • 地址code:8
    • 以后内存单元都是字

示例五:

; a 代表地址为code:0, 长度为字节的内存
mov al,a[si]
==> mov al,cs:0[si]
====================
mov al,a[3]
==> mov al.cs:0[3]
====================
mov al,a[bx+si+3]
==> mov al,cs:0[bx+si+3]
====================
; b 代表地址为code:8, 长度为字的内存单元
mov ax,b
==> mov ax,cs:[8]
====================
mov b,2
==> mov word ptr cs:[8],2
====================
inc b
==> inc word ptr cs:[8]
====================
mov al,b
==> error!

更常见的方式: 数据段中的数据标号

示例六:

assume cs:code,ds:data
data segment
  a db 1,2,3,4,5,6,7,8
  b dw 0
data ends

code segment
start:
  mov ax,data
  mov ds,ax
  mov si,0
  mov cx,8
s:
  mov al,a[si]
  mov ah,0
  add b,ax
  inc si
  loop s
  mov ax,4c00h
  int 21h
code ends
end start

扩展用法: 将标号当做数据来定义

data segment
    a db 1,2,3,4,5,6,7,8
    b dw 0
    c dw a,b ; ==>> c dw offset a, offset b
data ends
====================
data segment
    a db 1,2,3,4,5,6,7,8
    b dw 0
    c dd a,b ; ==>> c dw offset a,seg a, offset b,seg b, seg 操作符为取段地址
data ends

定址表

数据的直接定址表

示例七:

16 进制的形式在屏幕中间显示给定的byte 型数据

建立一张表, 表中一次存储字符"0"~"F", 通过数值0~15 直接查找到对应的字符

assume cs:code

code segment
start:
  mov al,2Bh
  call showbyte
  mov ax,4c00h
  int 21h

  ; 子程序
showbyte:
  jmp short show
  table db '0123456789ABCDEF'
show:
  push bx
  push es
  push cx

  mov ah,al
  mov cl,4
  shr ah,cl ; 右移4位, ah 中得到高4 位的值
  and al,00001111b ; al 中为低4 位的值
  ; 用高4 位的值ah 作为相对于table 的偏移, 取得对应的字符并显示
  mov bl,ah
  mov bh,0
  mov ah,table[bx]

  mov bx,0b800h
  mov es,bx
  mov es:[160*12+40*2],ah
  ; 用低4 位的值al 作为相对于table 的偏移, 取得对应的字符
  mov bl,al
  mov bh,0
  mov al,table[bx]
  mov es:[160*12+40*2+2],al

  pop cx
  pop es
  pop bx
  ret
code ends
end start

直接定址表

思路:

  • 利用表, 在两个数据集合之间建立一种映射关系, 用查表的方法根据给出的数据得到其在另一集合中的对应数据
  • 优点:
    • 算法清晰简洁
    • 加快运算速度
    • 程序易于扩展

示例八:

编写程序, 计算sin(x), x ∈{0°, 30°, 60°, 90°, 120°, 150°, 180°}, 并在屏幕中间显示计算结果

常规解法

  • 利用麦克劳林公式计算
  • 将角度x 换位弧度y=x/180*3.1415926

计算sin(x) 需要进行多次乘法和除法, 乘除都是比较昂贵的操作

另一种解法

空间换时间

  • 将所要计算的sin(x) 的结果都存储到一张表中, 然后用角度值来查表, 找到对应的sin(x) 的值
  • 方法
    • 用ax 向子程序传递角度
    • 角度值/30 为table 表中的偏移, 找到对应的字符串的首地址
assume cs:code

code segment
start:
  mov al,60
  call showsin
  mov ax,4c00h
  int 21h

showsin:
  jmp short show
  ; 字符串偏移地址表
  table dw ag0,ag30,ag60,ag90,ag120,ag150,ag180
  ag0 db'0',0 ; sin(0) 对应的字符串'0
  ag30 db'0.5',0
  ag60 db'0.866',0
  ag90 db'1',0
  ag120 db'0.866',0
  ag150 db'0.5',0
  ag180 db'0',0 ; sin(180) 对应字符串'0'
show:
  push bx
  push es
  push si
  mov bx,0b800h
  mov es,bx
  ; 用角度/30 作为相对于table 的偏移量, 取得对应的字符串的偏移地址, 放在bx 中
  mov ah,0
  mov bl,30
  div bl
  mov bl,al
  mov bh,0
  add bx,bx
  mov bx,table[bx]
  ; 显示sin(x) 对应的字符串
  mov si,160*12+40*2
shows:
  mov ah,cs:[bx]
  cmp ah,0
  je showret
  mov es:[si],ah
  inc bx
  add si,2
  jmp shows
showret:
  pop si
  pop es
  pop bx
  ret

code ends
end start

代码的直接定址表

直接定址表法

  • 用查表的方式, 通过依据数据, 直接计算出所要找的元素位置

直接定址表分类

  • 数据段额直接定址表
  • 代码的直接诶定址表

示例九:

实现一个子程序setscreen, 为显示输出提供如下功能

  1. 清屏
  2. 设置前景色
  3. 设置背景色
  4. 向上滚动一行

方案:

  • 将4 个功能写成4 个子程序
  • 这些功能子程序入口地址存储在一个表中, 他们在表中位置和功能号相对应
  • 对应关系, 功能号*2=对应功能子程序在地址表中的偏移
assume cs:code
code segment
start:
  ; 主程序
  mov ah,3
  mov al,2 ; 传递颜色
  call setscreen
  mov ax,4c00h
  int 21h
  ; 子程序
setscreen:
  jmp short set
  table dw sub1,sub2,sub3,sub4
set:
  push bx
  cmp ah,3; 判断是否大于3
  ja sret
  mov bl,ah
  mov bh,0
  add bx,bx
  call word ptr table[bx]
sret:
 pop bx
 ret
; 各功能实现
sub1:
  push bx
  push cx
  push es

  mov bx,0b800h
  mov es,bx
  mov bx,0
  mov cx,2000
sub1s:
  mov byte ptr es:[bx],' '
  add bx,2
  loop sub1s

  pop es
  pop cx
  pop bx
  ret
;sub1 ends

sub2:
  push bx
  push cx
  push es

  mov bx,0b800h
  mov es,bx
  mov bx,1
  mov cx,2000
sub2s:
  and byte ptr es:[bx],11111000b
  or es:[bx],al
  add bx,2
  loop sub2s

  pop es
  pop cx
  pop bx
  ret
;sub2 ends

sub3:
  push bx
  push cx
  push es
  mov cl,4
  shl al,cl
  mov bx,0b800h
  mov es,bx
  mov bx,1
  mov cx,2000
sub3s:
  and byte ptr es:[bx],10001111b
  or es:[bx],al
  add bx,2
  loop sub3s
  pop es
  pop cx
  pop bx
  ret
;sub3 ends

sub4:
  push cx
  push si
  push di
  push es
  push ds
  
  mov si,0b800h
  mov es,si
  mov ds,si
  mov si,160
  mov di,0
  cld
  mov cx,24
sub4s:
  push cx
  mov cx,160
  rep movsb
  pop cx
  loop sub4s

  mov cx,80
  mov si,0
sub4s1:
  mov byte ptr es:[160*24+si],' '
  add si,2
  loop sub4s1
  
  pop ds
  pop es
  pop di
  pop si
  pop cx
  ret
;sub4 ends
code ends
end start

中断及其处理

概念:

  • 中断: cpu 不再接着(刚执行完的指令) 向下执行, 而是专区处理中断信息
  • 内中断: 由cpu 内部发生的事件而引起的中断
  • 外中断: 由外部设备发生的事件引起的中断
图5:中断及其处理

8086的内中断

  • 除法错误, 比如执行div 指令产生的除法溢出
  • 单步执行
  • 执行into 指令
  • 执行int 指令

8086 中断类型码:

  1. 除法错误: 0
  2. 单步执行: 1
  3. 执行into 指令: 4
  4. 执行int n 指令, 立即数n wei中断类型码

示例十:

利用中断在屏幕上显示一行字符串

assume cs:code,ss:stack,ds:data
stack segment
  db 200h dup(0)
stack ends
data segment
  szmsg db 13,10,'hello world!',13,10,'$'
data ends
code segment
start:
  mov ax,data
  mov ds,ax
  lea dx,szmsg
  mov ah,9
  int 21h
  
  mov ax,4c00h
  int 21h
code ends
end start

中断处理程序

cpu 接到中断信息时, 执行中断处理程序

中断处理程序位置

  • 中断信息和其处理程序的入口地址之间有某种联系, cpu 根据中断信息可以找到要执行的处理程序

中断向量表

  • 由中断类型码, 查表得到中断处理程序入口地址, 从而定位中断处理程序
图6:8086cpu中的中断向量表

示例十一:

图7:系统中的0 号中断

中断过程

是由cpu 的硬件自动完成, 用中断类型码找到中断向量, 并用它设置cs 和ip

8086 cpu 的中断过程

  1. 从中断信息中取得中断类型码
  2. 标志寄存器的值入栈-- 中断过程中药改变标志寄存器的值, 需要现行保护
  3. 设置标志寄存器的第8 位TF 和第9 位IF 的值为0
  4. cs 的内容入栈
  5. ip 内容入栈
  6. 从终端向量表读取中断处理程序入口地址, 设置ip 和cs
图8:中断过程
  1. 取得中断类型码N
  2. pushf
  3. TF=0, IF=0
  4. push cs
  5. push ip
  6. (ip)=(n*4), (cs)=(n*4+2)

编制中断处理程序

  • cpu 随时都可能检测到中断信息, 所以中断处理程序须常驻内存
  • 中断处理程序的入口地址, 即中断向量, 必须存储在对应的中断向量表中(0000:0000-0000:03ff)

如何编制中断处理程序呢?

对0 号中断, 即出发错误的中断处理

编写一个0 号中断处理程序, 功能为在屏幕中间显示"overflow!" 后, 返回到操作系统

图9:0号中断,屏幕中间显示字符串

do0:

  1. 相关处理
  2. 向现实缓冲区送字符串"overflow!"
  3. 返回dos

子程序存放位置

  • 子程序应该存放在内存的确定位置, 不能破坏系统
  • 绕过操作系统, 直接找到一块别的程序不会用到的内存区, 将do0 传送到其中
  • 内存0000:0000~0000:03ff, 大小为1kb 的空间是系统存放终端向量表, dos 系统和其他应用程序都不会随便使用这段空间, 8086 支持256 个中断, 但是实际系统重要处理的中断事件远没有达到256 个
  • 利用中断向量表中的空闲低钠盐存放我们的程序, 使用0000:0200~0000:02ff 空间

示例十二:

assume cs:code
code segment
start:
  ; do0安装程序
  mov ax,cs
  mov ds,ax
  mov si,offset do0
  mov ax,0
  mov es,ax
  mov di,200h
  mov cx,offset do0end-offset do0
  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:
  mov ax,cs
  mov ds,ax
  mov si,202h
  mov ax,0b800h
  mov es,ax
  mov di,12*160+36*2
  mov cx,9
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

单步中断

debug 提供了单步中断的中断处理程序, wei显示所有寄存器的内容后等待输入命令

debug 利用cpu 提供的单步中断功能, 使用t 命令时, debug 将tf 标识设为1, 使cpu 工作在单步中断方式

单步中断过程与处理

两个和中断相关的寄存器标志位

  • TF-陷阱标识(Trap Flag), 用于调试时单步操作, 当TF=1, 每条指令执行后产生陷阱, 由系统控制计算机, TF=0, cpu 正常工作, 不产生陷阱
  • IF-中断标志(Interrupt Flag), IF=1, 允许cpu 响应可屏蔽中断请求, IF=0, 关闭中断

中断过程:

  1. 取得中断类型码1
  2. 标识寄出去入栈, TF, IF 设置为0
  3. cs, ip 入栈
  4. (ip)=(1*4), cs=(1*4+2)
  • 中断处理程序也由一条条指令组成的

  • 如果在执行中断处理程序之前, TF=1, 则CPU 在执行完中断处理程序的第一条指令后, 又要产生单步中断, 转去执行单步中断的中断处理程序的第一条指令……

  • 上面的过程将陷入一个永远不能结束的循环, CPU永远执行单步中断处理程序的第一条指令

  • 所以, 在进入中断处理程序之前, 设置TF=0

中断不响应情况

有些情况下, cpu 执行完当前指令后, 即便发生中断, 也不会响应

例如, 执行完向ss 寄出去传输数据指令后, 即便发送中断, cpu 也不会响应

原因, ss:sp 联合指向栈顶, 它们的设置应该连续完成, 一次保证对栈的正确操作

由int指令引发的中断

格式: int n, n为中断类型码

执行过程

  1. 取中断类型码n
  2. 标志寄存器入栈, IF = 0, TF = 0
  3. CS, IP入栈
  4. (IP) = (n*4),(CS) = (n*4+2)
  • int 指令的最终功能和call指令相似, 都是调用一段程序
  • 一般情况下, 系统将一些具有一定功能的子程序, 以中断处理程序的方式提供给应用程序调用

可以自定义中断例程实现特定功能

中断7ch 的中断例程的编写和安装

7ch 中断例程, 求一个word 型数据平方, 参数(ax)=要计算的数据, 返回值dx,ax 存放高低16 位

示例十三:

求2*3456^2

assume cs:code
code segment
start:
  mov ax,cx
  mov ds,ax
  mov si,offset sqr
  mov ax,0
  mov es,ax
  mov di,200h
  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
code ends
end start

示例十四:

将以0 结尾的字符串转化为大写, 参数ds:si 执行字符串首地址

assume cs:code

code segment
start:
  mov ax,cs
  mov ds,ax
  mov si,offset capital
  mov ax,0
  mov es,ax
  mov di,200h
  mov cx,offset capitalend-offset capital
  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
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
  iret
capitalend: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
  int 7ch
  mov ax,4c00h
  int 21h
code ends
end start

BIOS和DOS中断处理

bios 基本输入输出系统, 在系统板的rom 中存放着一套程序, 容量8kb, 地址从FE000H 开始

其中的主要内容:

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

示例十五:

在屏幕的5 行12 列显示3 个红底高亮闪烁绿色的'a'

使用bios 的10h 中断

  • (ah)=2, 调用10h 汇总单例程的2 号子程序, 设置光标位置
  • (ah)=9, 调用10h 中断例程的9 号子程序, 在光标位置显示字符
assume cs:code
code segment
  mov ah,2 ; 设置光标位置
  mov bh,0 ; 第0 页
  mov dh,5 ; dh 中放行号
  mov dl,12 ; dl 中放列号
  int 10h
  
  mov ah,9 ; 显示字符功能
  mov al,'a' ; 字符
  mov bl,11001010b ; 颜色设置
  mov bh,0 ; 第0 页
  mov cx,3 ; 字符重复个数
  mov ax,4c00h
  int 21h
code ends
end

其他bios 中断

图10:其他bios中断

int 21h dos 中断例程应用

4ch 号功能, 程序返回, 功能号在ah, 返回结果在al

09h 号功能, 在光标位置显示字符串, ds:dx 指向要显示的字符串, 用'$' 结束

示例十六:

在屏幕5 行12 列 显示字符串"welcome to masm!"

assume cs:code,ds:data
data segment
  db 'welcome to masm!','$'
data ends
code segment
start:
  mov ah,2 ; 光标
  mov bh,0 ; 第0页
  mov dh,5 ; dh 行号
  mov dl,12 ; dl 列号
  int 10h

  mov ax,data
  mov ds,ax
  mov dx,0 ; ds:dx 指向字符串的首地址data:0
  mov ah,9
  int 21h

  mov ax,4c00h
  int 21h
code ends
end start

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

  1. CPU 一加电,初始化(CS)=0FFFFH, (IP)=0, 自动从FFFF:0单元开始执行程序. FFFF:0处有一条转跳指令, CPU执行该指令后, 转去执行BIOS中的硬件系统检和初始化程序
  2. 初始化程序将建立BIOS 所支持的中断向量, 即将BIOS提供的中断例程的入口地址登记在中断向量表中
  3. 硬件系统检测和初始化完成后, 调用int 19h进行操作系统的引导. 从此将计算机交由操作系统控制
  4. DOS 启动后, 除完成其它工作外, 还将它所提供的中断例程装入内存, 并建立相应的中断向量
图11:中断例程安装过程

端口的读写

图12:端口的读写

示例十七:

用端口访问外设, 发声

assume cs:code
code segment
start:
  mov al,08h ; 设置剩余频率
  out 42h,al
  out 42h,al
  in al,61h ; 读设备控制器端口原值
  mov ah,al ; 保存原值
  or al,3 ; 打开扬声器和定时器
  out 61h,al ; 接通扬声器, 发声
  mov cx,60000 ; 延时
delay:
  nop
  loop delay
  mov al,ah ; 恢复端口值
  out 61h,al
  mov ax,4c00h
  int 21h
code ends
end start

cpu 的邻居

cpu 可以直接读写3 个地方的数据

  1. cpu 内部寄存器
  2. 内存单元
  3. 端口
    1. 各种接口卡, 网卡, 显卡
    2. 主板上的接口芯片
    3. 其他芯片

读写内存与寄存器的指令, mov, add, push...

读写端口的指令

  • in, cpu 从端口读取数据
  • out: cpu 往端口写入数据

端口的读写

访问端口的方法

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

执行时与总线相关的操作

  1. CPU通过地址线将地址信息60h发出
  2. CPU通过控制线发出端口读命令, 选中端口所在的芯片, 并通知要从中读取数据
  3. 端口所在的芯片将60h端口中的数据通过数据总线送入CPU

I/O 端口分配

图13:I/O端口分配

端口读写指令示例

0~255 以内的端口读写, 端口号用立即数给出

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

out 21h,al ; 往21h 端口写入一个字节

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

mov dx,3f8h ; 将端口号3f8送入dx

in al,dx ; 从3f8h端口读入一个字节

out dx,al ; 向3f8h端口写入一个字节

在in和out 指令中,只能使用ax 或al 来存放从端口中读入的数据或要发送到端口中的数据

访问8 位端口时用 al , 访问 16 位端口时用ax

操作CMOS RAM芯片

  1. 包含一个实时钟和一个有128个存储单元的RAM存储器
  2. 128 个字节的 RAM 中存储: 内部实时钟, 系统配置信息, 相关的程序(用于开机时配置系统信息)
  3. CMOS RAM 芯片靠电池供电, 关机后其内部的实时钟仍可正常工作, RAM 中的信息不丢失
  4. 该芯片内部有两个端口, 端口地址为70h和71h, CPU 通过两个端口读写CMOS RAM
    1. 70h地址端口, 存放要访问的CMOS RAM单元的地址
    2. 71h数据端口, 存放从选定的单元中读取的数据, 或要写入到其中的数据
  5. 读取CMOS RAM的两个步骤
    1. 将要读取的单元地址送入70h 地址端口
    2. 从数据端口71h 读出指定单元的内容

示例十八:

在屏幕中间显示当前的月份

事实上, 在CMOS RAM中的时间信息含有月份

图14:在cmos中包含的时间

屏幕中间显示当前月份

  1. 从CMOS RAM的8号单元读出当前月份的BCD码
    1. 首先要向地址端口70h写入要访问的单元的地址-月份8
    2. 再从数据端口71h中取得指定单元中的数据-月份值
  2. 将用BCD码表示的月份以十进制的形式显示到屏幕上
    1. 将读到的数据前4位和后4位分离出来
    2. 由低4位为BCD码的“数字”转为ASCII码
      1. BCD 码值=十进制数码值
      2. 十进制数对应的ASCII码 = BCD码值+30h
assume cs:code
code segment
start:
  mov al,8
  out 70h,al ; 取得月份
  in al,71h

  mov ah,al
  mov cl,4 ; 分离月份的十, 个位
  shr ah,cl
  and al,00001111b

  add ah,30h ; 转换为ASCII 码
  add al,30h

  mov bx,0b800h
  mov es,bx
  mov byte ptr es:[160*12+40*2],ah
  mov byte ptr es:[160*12+40*2+2],al

  mov ax,4c00h
  int 21h
code ends
end start

外设连接与中断

cpu 通过端口与外部设备"连接"

cpu 在执行指令过程中, 可以检测到发送过来的中断信息, 引发中断过程, 处理外设的输入

外中断

由外部设备发生的时间引起的中断

可屏蔽中断

  • 可屏蔽中断是CPU 可以不响应的外中断
  • CPU 是否响应可屏蔽中断, 要看标志寄存器的IF 位的设置
  • 当CPU 检测到可屏蔽中断信息时:
    • 如果IF=1, 则CPU 在执行完当前指令后响应中断,引发中断过程
    • 如果IF=0, 则不响应可屏蔽中断

不可屏蔽中断

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

外中断处理过程

可屏蔽中断所引发的中断过程

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

可屏蔽中断信息来自于CPU外部, 中断类型码是通过数据总线送入CPU

(对比内中断: 中断类型码是在CPU内部产生的)

将IF置0的原因: 进入中断处理程序后, 禁止其他的可屏蔽中断

如果在中断处理程序中需要处理可屏蔽中断, 可以用指令将IF 置1

不可屏蔽中断的中断过程

  1. 标志寄存器入栈, IF=0, TF=0
  2. CS, IP入栈
  3. (IP)=(8), (CS)=(0AH)

8086 cpu 提供的设置if 指令, sti, 用于设置if=1, cli用于设置if=0

PC机键盘的处理过程

1. 键盘输入

  • 键盘上的每一个键相当于一个开关, 键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描
  • 按下一个键时的操作
    • 开关接通, 该芯片就产生一个扫描码, 扫描码说明了按下的键在键盘上的位置
    • 扫描码被送入主板上的相关接口芯片的寄存器中, 该寄存器的端口地址为60H
  • 松开按下的键时的操作
    • 产生一个扫描码, 扫描码说明了松开的键在键盘上的位置
    • 松开按键时产生的扫描码也被送入60H 端口中
  • 扫描码--长度为一个字节的编码
    • 按下一个键时产生的扫描码--通码, 通码的第7 位为 0
    • 松开一个键时产生的扫描码--断码, 断码的第7位为 1
图15:键盘上键的扫描码(通码)

2. 引发9 号中断

  • 键盘的输入到达60H 端口时, 相关的芯片就会向CPU 发出中断类型码为 9 的可屏蔽中断信息
  • CPU检测到该中断信息后, 如果IF=1, 则响应中断, 引发中断过程, 转去执行int 9中断例程

输入的字符键盘值如何保存

  • 有BIOS键盘缓冲区
  • BIOS键盘缓冲区: 是系统启动后, BIOS用于存放int 9 中断例程所接收的键盘输入的内存区
  • BIOS键盘缓冲区: 可以存储15 个键盘输入, 一个键盘输入用一个字单元存放, 高位字节存放扫描码, 低位字节存放字符码
7 6 5 4 3 2 1 0
Insert CapsLock NumLock ScrollLock Alt Ctrl 左Shift 右Shift

执行int 9 中断例程

BIOS 中提供的处理键盘输入的int 9中断例程的工作

  1. 读出60H 端口中的扫描码

  2. 根据扫描码分情况对待

    1. 如果是字符键的扫描码,将该扫描码和它所对应的字符码(

      即 ASCII码) 送入内存中的BIOS 键盘缓冲区

    2. 如果是控制键(比如 Ctrl ) 和切换键(比如 CapsLock) 的扫描码, 则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元

  3. 对键盘系统进行相关的控制, 如向相关芯片发出应答信息

定制键盘输入处理(int 9 中断例程)

处理过程

  1. 键盘产生扫描码
  2. 扫描码送入60h 端口
  3. 引发9 号中断

========> 以上由硬件系统完成

  1. CPU执行int 9中断例程, 处理键盘输入
    1. DOS系统提供int 9中断例程
    2. 按照开发需求定制处理键盘的输入

示例十九:

编程任务

  • 在屏幕中间依次显示 'a'~'z', 并可以让人看清
  • 在显示的过程中, 按下Esc键后, 改变显示的颜色
assume cs:code,ds:data,ss:stack
stack segment
  db 128 dup(0)
stack ends
data segment
  dw 0,0
data ends

code segment
  ; 代码段
start:
  mov ax,stack
  mov ss,ax
  mov sp,128
  mov ax,data
  mov ds,ax

  ; 改中断例程入口地址
  mov ax,0
  mov es,ax
  push es:[9*4]
  pop ds:[0]
  push es:[9*4+2]
  pop ds:[2]
  mov word ptr es:[9*4],offset int9
  mov es:[9*4+2],cs

  ; 显示a~z
  mov ax,0b800h
  mov es,ax
  mov ah,'a'
s:
  mov es:[160*12+40*2],ah
  call delay
  inc ah
  cmp ah,'z'
  jna s
  mov ax,0
  mov es,ax

  ; 恢复原来地址
  push ds:[0]
  pop es:[9*4]
  push ds:[2]
  pop es:[9*4+2]

  mov ax,4c00h
  int 21h

  ; 定义延迟程序
delay:
  push ax
  push dx
  mov dx,10h
  mov ax,0
s1:
  sub ax,1
  sbb dx,0
  cmp ax,0
  jne s1
  cmp dx,0
  jne s1
  pop dx
  pop ax
  ret

  ; 定义中断例程
int9:
  push ax
  push bx
  push es
  in al,60h
  pushf
  pushf
  pop bx
  and bh,11111100b
  push bx
  popf
  call dword ptr ds:[0]

  cmp al,1 ; esc 扫描码是1
  jne int9ret
  ; 改变颜色
  mov ax,0b800h
  mov es,ax
  inc byte ptr es:[160*12+40*2+1]
int9ret:
  pop es
  pop bx
  pop ax
  iret
code ends
end start

改写中断例程的方法

示例二十:

任务: 安装一个新的int 9中断例程

功能: 在DOS下, 按F1键后改变当前屏幕的显示颜色, 其他的键照常处理

解决的问题

  1. 改变屏幕的显示颜色

    改变从B800开始的4000个字节中的所有奇地址单元中的内容, 当前屏幕的显示颜色即发生改变

  2. F1改变功能,其他键照常

    可以调用原int 9中断处理程序, 来处理其他的键盘输入

  3. 原int 9中断例程入口地址的保存

    要保存原int 9中断例程的入口地址原因: 在新int 9中断例程中要调用原int 9中断例程

    保存在哪里? 我们将地址保存在0:200单元处

  4. 新int 9中断例程的安装

    我们可将新的int 9中断例程安装在0:204 处

assume cs:code
stack segment
  db 128 dup(0)
stack ends
code segment
  ; 设置各段地址
start:
  mov ax,stack
  mov ss,ax
  mov sp,128
  push cs ;ds 和cs 相同
  pop ds
  mov ax,0
  mov es,ax
  ; 安装新程序
  mov si,offset int9
  mov di,204h
  mov cx,offset int9end-offset int9
  cld
  rep movsb
  ; 将原来中断地址保存在0:200 单元
  push es:[9*4]
  pop es:[200h]
  push es:[9*4+2]
  pop es:[202h]
  ; 改变后中断的入口地址
  cli
  mov word ptr es:[9*4],204h
  mov word ptr es:[9*4+2],0
  sti
  mov ax,4c00h
  int 21h
  ; 定义新中断例程
int9:
  push ax
  push bx
  push cx
  push es
  in al,60h
  pushf
  ; 调用中断例程
  call dword ptr cs:[200h]
  ; F1 键处理
  cmp al,3bh
  jne int9ret
  mov ax,0b800h
  mov es,ax
  mov bx,1
  mov cx,2000
s:
  inc byte ptr es:[bx]
  add bx,2
  loop s
int9ret:
  pop es
  pop cx
  pop bx
  pop ax
  iret
int9end:
  nop
code ends
end start

用中断响应外设

如何操作外部设备

硬件中断int 9h BIOS中断int 16h DOS中断int 21h
由键盘上按下或松开一个键时,
如果中断是允许的, 就会产生int 9h中断, 并转到BIOS的键盘中断处理程序
BIOS中断提供基本的键盘操作,
功能号(AH)=
00H, 10H - 从键盘读入字符
01H, 11H - 读取键盘状态
02H, 12H - 读取键盘标志
03H - 设置重复率
04H - 设置键盘点击
05H - 字符及其扫描码进栈
在使用功能键和变换键的程序中很重要
DOS中断提供丰富, 便捷的功能调用功能号(AH)=
01H - 从键盘输入一个字符并回
06H - 读键盘字符
07H - 从键盘输入一个字符不回显
08H - 从键盘输入一个字符, 不回显, 检测CTRL-Break
0AH - 输入字符到指定地址的缓冲区
0BH - 读键盘状态
0CH - 清除键盘缓冲区, 并调用一种键盘功能

对键盘输入的处理的int 9h中断和int 16h中断

  • int 9h将键盘输入存入缓冲或改变状态字
  • 键盘输入将引发9 号中断, BIOS 提供了int 9 中断例程
  • int 9中断例程从60h 端口读出扫描码, 并将其转化为相应的ASCII 码或状态信息, 存储在内存的指定空间(键盘缓冲区或状态字节)中
  • 键盘缓冲区中有16 个字单元, 可以存储15个按键的扫描码和对应的入ASCII 码
  • BIOS提供了int 16h 中断例程供程序员调用, 以完成键盘的各种操作
  • 例: 当(AH)=0时, 读取键盘缓冲区
  • 功能: 从键盘缓冲区中读取一个键盘输入, 并且将其从缓冲区中删除

mov ah,0

int 16h

结果: (ah)=扫描码, (al)=ASCII码

调用int 16h 从键盘缓冲区中读取键盘的输入

综前所述: int 16h 中断例程 0 号功能的实现过程

  1. 检测键盘缓冲区中是否有数据
  2. 没有则继续做第1 步
  3. 读取缓冲区第一个字单元中的键盘输入
  4. 将读取的扫描码送入ah, ASCII 码送入al
  5. 将己读取的键盘输入从缓冲区中删除

事实:

  • B1OS 的int 9 中断例程和int 16h 中断例程是一对相互配合的程序, int 9 中断例程向键盘缓冲区中写入int 16h 中断例程从缓冲区中读出
  • 它们写入和读出的时机不同, int 9 中断例程在有键按下的时候向键盘缓冲区中写入数据, 而int 16h 中断例程是在应用程序对其进行调用的时候, 将数据从键盘缓冲区中读出

示例二十一:

要求:

  • 输入"r", 将屏幕上的字符设置为红色
  • 输入"g" 将屏幕上的字符设置为绿色
  • 输入"b" 将屏幕上的字符设置为蓝色
assume cs:code
code segment
start:
  ; 调用中断,等待输入
  mov ah,0
  int 16h
  ; 识别按键
  mov ah,1
  cmp al,'r'
  je red
  cmp al,'g'
  je green
  cmp al,'b'
  je blue
  jmp short sret

  ; 设置屏幕颜色
red:
  shl ah,1
green:
  shl ah,1
blue:
  mov bx,0b800h
  mov es,bx
  mov bx,1
  mov cx,2000
s:
  and byte ptr es:[bx],11111000b
  or es:[bx],ah
  add bx,2
  loop s
sret:
  mov ax,4c00h
  int 21h
code ends
end start

字符串的输入

示例二十二:

问题: 设计一个最基本的字符串输入程序,需要具备下面的功能

  1. 在输入的同时需要显示这个字符串
  2. 一般在输入回车符后, 字符串输入结束
  3. 能够删除已经输入的字符--用退格键

解决:

  1. 在输入的同时需要显示这个字符串

    需要用栈的方式来管理字符串的存储空间

  2. 在输入回车符后,字符串输入结束

    输入回车符后 , 在字符串中加入0, 表示字符串结束

  3. 在输入的同时需要显示这个字符串

    每次有新的字符输入和删除一个字符的时候, 都应该重新显示字符串, 即从字符栈的栈底到栈顶, 显示所有的字符

过程

  1. 调用int 16h读取键盘输入
  2. 如果不是字符: ①如果是退格键, 从字符栈中弹出一个字符, 显示字符栈中的所有字符,继续执行1; ②如果是Enter 键, 向字符栈中压入0, 返回
  3. 如果是字符键: 字符入栈; 显示字符栈中的所有字符; 继续执行1
assume cs:code,ds:data
data segment
  db 32 dup(?)
data ends
code segment
start:
  mov ax,data
  mov ds,ax
  mov si,0 ; 设置字符串的存储空间
  mov dh,12
  mov dl,20 ; 显示位置
  call getstr
return:
  mov ax,4c00h
  int 21h

getstr:
  push ax
getstrs:
  mov ah,0
  int 16h
  cmp al,20h
  jb nochar ; 小于20h 为非字符
  mov ah,0
  ; 字符入栈
  call charstack
  mov ah,2
  ; 显示栈中字符
  call charstack
  jmp getstrs

nochar: ; 处理非字符
  cmp ah,0eh
  je backspace
  cmp ah,1ch ; 回车键的扫描
  je enter
  jmp getstrs

  ; 对退格键, 回车键处理
backspace: ; 退格
  mov ah,1
  ; 字符出栈
  call charstack
  ; 显示栈中的字符
  mov ah,2
  call charstack
  jmp getstrs
enter: ; 回车
  mov al,0
  mov ah,0
  ; 0 字符入栈
  call charstack
  mov ah,2
  ; 显示栈中的字符
  call charstack
  pop ax
  ret ; getstr 结束
; 子程序实现
charstack:
  jmp short charstart
  table dw charpush,charpop,charshow
  top dw 0 ; 栈顶
charstart:
  push bx
  push dx
  push di
  push es
  ; 实现各功能
  cmp ah,2
  ja sret
  mov bl,ah
  mov bh,0
  add bx,bx
  jmp word ptr table[bx]

charpush:
  mov bx,top
  mov [si][bx],al
  inc top
  mov bx,top
  mov al,[si][bx]
  jmp sret

charpop:
  cmp top,0
  je sret
  dec top
  mov bx,top
  mov al,[si][bx]
  jmp sret

charshow:
  mov bx,0b800h
  mov es,bx
  mov al,160
  mov ah,0
  mul dh
  mov di,ax
  add dl,dl
  mov dh,0
  add di,dx

  mov bx,0

charshows:
  cmp bx,top
  jne noempty
  mov byte ptr es:[di],' '
  jmp sret
noempty:
  mov al,[si][bx]
  mov es:[di],al
  mov byte ptr es:[di+2],' '
  inc bx
  add di,2
  jmp charshows
sret:
  pop es
  pop di
  pop dx
  pop bx
  ret
code ends
end start

读写磁盘

图16:bios提供的磁盘直接服务int 13

用BIOS int 13h 对磁盘进行读操作

入口参数:

  • (ah)=2 (2表示读扇区)
  • (al)=读取的扇区数
  • (ch)=磁道号, (cl)=扇区号
  • (dh)=磁头号(对于软盘即面号, 一个面用一个磁头来读写)
  • (dl)=驱动器号: 软驱从0开始, 0: 软驱A, 1: 软驱B; 硬盘从80h 开始, 80h: 硬盘C, 81h: 硬盘D
  • es:bx 指向接收从扇区读入数据的内存区

返回参数:

  • 操作成功: (ah)=0, (al)=读入的扇区数

  • 操作失败: (ah)=出错代码

mov ax,0
mov es,ax
mov bx,200h ; 读入0:200h
mov al,1 ; 1 个扇区
mov ch,0 ; 0 磁道
mov cl,1 ; 1 扇区
mov dl,80h ; c 盘
mov dh,0 ; 0 面
mov ah,2 ; 读扇区
int 13h

用BIOS int 13h 对磁盘进行写操作

入口参数:

  • (ah)=3 (3表示写扇区)
  • (al)=写入的扇区数
  • (ch)=磁道号, (cl)=扇区号
  • (dh)=磁头号(对于软盘即面号)
  • (dl)=驱动器号: 软驱从0开始, 0: 软驱A, 1: 软驱B, 硬盘从80h 开始, 80h: 硬盘C, 81h: 硬盘D
  • es:bx 指向将写入磁盘的数据

返回参数:

  • 操作成功: (ah)=0, (al)=写入的扇区数
  • 操作失败: (ah)=出错代码
mov ax,0
mov es,ax
mov bx,200h ; 写0:200h
mov al,1 ; 写1 个扇区
mov ch,0 ; 0 磁道
mov cl,1 ; 1 扇区
mov dl,80h ; c 盘
mov dh,0 ; 0 面
mov ah,3 ; 写入扇区
int 13h
图17:dos中断对磁盘恩建额支持int 21h

计算机"唱歌"

图18:有关的硬件及控制
; 8253 芯片(定时/计数器)的设置
mov al,0b6h ;8253初始化
out 43h,al ;43H是8253芯片控制口的端口地址
mov dx,12h
mov ax,34dch
div word ptr [si] ;计算分频值,赋给ax, [si]中存放声音的频率值。
out 42h, al ;先送低8位到计数器,42h是8253芯片通道2的端口地址
mov al, ah
out 42h, al ;后送高8位计数器

;设置8255芯片(并行I/O), 控制扬声器的开/关
in al,61h ;读取8255 B端口原值
mov ah,al ;保存原值
or al,3 ;使低两位置1,以便打开开关
out 61h,al ;开扬声器, 发声
... ;延时,保持时间
... mov al, ah
out 61h, al;恢复扬声器端口原值

示例二十三:

演奏程序

assume cs:codeseg, ds:dataseg, ss:stackseg
dataseg segment
mus_freq dw 262,262,262,196
         dw 330,330,330,262
         dw 262,330,392,392
         dw 349,330,294
         dw 294,330,349,349
         dw 330,294,330,262
         dw 262,330,294,196
         dw 247,294,262,-1
mus_time dw 3 dup(12,12,25,25),12,12,50
         dw 3 dup(12,12,25,25),12,12,50
dataseg ends

stackseg segment
   db 100h dup (0)
stackseg ends

codeseg segment
start:
    mov ax, stackseg
    mov ss, ax
    mov sp, 100h

    mov ax, dataseg
    mov ds, ax

    lea si, mus_freq
    lea di, mus_time

play:
    mov dx, [si]
    cmp dx, -1
    je end_play
    call sound
    add si, 2
    add di, 2
    jmp play

end_play:
    mov ax, 4c00h
    int 21h

;演奏一个音符
;入口参数:si - 要演奏的音符的频率的地址
;          di - 要演奏的音符的音长的地址
sound:
    push ax
    push dx
    push cx

    ;8253 芯片(定时/计数器)的设置
    mov al,0b6h    ;8253初始化
    out 43h,al     ;43H是8253芯片控制口的端口地址
    mov dx,12h
    mov ax,34dch
    div word ptr [si] ;计算分频值,赋给ax。[si]中存放声音的频率值。
    out 42h, al       ;先送低8位到计数器,42h是8253芯片通道2的端口地址
    mov al, ah
    out 42h, al       ;后送高8位计数器


    ;设置8255芯片, 控制扬声器的开/关
    in al,61h   ;读取8255 B端口原值
    mov ah,al   ;保存原值
    or al,3     ;使低两位置1,以便打开开关
    out 61h,al  ;开扬声器, 发声

    mov dx, [di]       ;保持[di]时长
wait1:
    mov cx, 28000
delay:
    nop
    loop delay
    dec dx
    jnz wait1

    mov al, ah         ;恢复扬声器端口原值
    out 61h, al

    pop cx
    pop dx
    pop ax
    ret

codeseg ends
end start
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,907评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,987评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,298评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,586评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,633评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,488评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,275评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,176评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,619评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,819评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,932评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,655评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,265评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,871评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,994评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,095评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,884评论 2 354

推荐阅读更多精彩内容