位移指令
示例一:
mov al,01001000b
shl al,1
图1:shl左移结果
图2:左移和右移与cf值
示例二:
SHL OPR,CNT
, 将OPR 逻辑左移CNT 位
- 将寄存器或内存单元中的数据向左移位
- 将最后移出的移位写入CF 中
- 最低位用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, 为显示输出提供如下功能
- 清屏
- 设置前景色
- 设置背景色
- 向上滚动一行
方案:
- 将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 内部发生的事件而引起的中断
- 外中断: 由外部设备发生的事件引起的中断
8086的内中断
- 除法错误, 比如执行div 指令产生的除法溢出
- 单步执行
- 执行into 指令
- 执行int 指令
8086 中断类型码:
- 除法错误: 0
- 单步执行: 1
- 执行into 指令: 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 根据中断信息可以找到要执行的处理程序
中断向量表
- 由中断类型码, 查表得到中断处理程序入口地址, 从而定位中断处理程序
示例十一:
图7:系统中的0 号中断中断过程
是由cpu 的硬件自动完成, 用中断类型码找到中断向量, 并用它设置cs 和ip
8086 cpu 的中断过程
- 从中断信息中取得中断类型码
- 标志寄存器的值入栈-- 中断过程中药改变标志寄存器的值, 需要现行保护
- 设置标志寄存器的第8 位TF 和第9 位IF 的值为0
- cs 的内容入栈
- ip 内容入栈
- 从终端向量表读取中断处理程序入口地址, 设置ip 和cs
- 取得中断类型码N
- pushf
- TF=0, IF=0
- push cs
- push ip
- (ip)=(n*4), (cs)=(n*4+2)
编制中断处理程序
- cpu 随时都可能检测到中断信息, 所以中断处理程序须常驻内存
- 中断处理程序的入口地址, 即中断向量, 必须存储在对应的中断向量表中(0000:0000-0000:03ff)
如何编制中断处理程序呢?
对0 号中断, 即出发错误的中断处理
编写一个0 号中断处理程序, 功能为在屏幕中间显示"overflow!" 后, 返回到操作系统
图9:0号中断,屏幕中间显示字符串do0:
- 相关处理
- 向现实缓冲区送字符串"overflow!"
- 返回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
- 标识寄出去入栈, TF, IF 设置为0
- cs, ip 入栈
- (ip)=(1*4), cs=(1*4+2)
中断处理程序也由一条条指令组成的
如果在执行中断处理程序之前, TF=1, 则CPU 在执行完中断处理程序的第一条指令后, 又要产生单步中断, 转去执行单步中断的中断处理程序的第一条指令……
上面的过程将陷入一个永远不能结束的循环, CPU永远执行单步中断处理程序的第一条指令
所以, 在进入中断处理程序之前, 设置TF=0
中断不响应情况
有些情况下, cpu 执行完当前指令后, 即便发生中断, 也不会响应
例如, 执行完向ss 寄出去传输数据指令后, 即便发送中断, cpu 也不会响应
原因, ss:sp 联合指向栈顶, 它们的设置应该连续完成, 一次保证对栈的正确操作
由int指令引发的中断
格式: int n, n为中断类型码
执行过程
- 取中断类型码n
- 标志寄存器入栈, IF = 0, TF = 0
- CS, IP入栈
- (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 开始
其中的主要内容:
- 硬件系统的检测和初始化程序
- 外部中断和内部中断的中断例程
- 用于对硬件设备进行I/O操作的中断例程
- 其他和硬件系统相关的中断例程
示例十五:
在屏幕的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 中断例程的安装过程
- CPU 一加电,初始化(CS)=0FFFFH, (IP)=0, 自动从FFFF:0单元开始执行程序. FFFF:0处有一条转跳指令, CPU执行该指令后, 转去执行BIOS中的硬件系统检和初始化程序
- 初始化程序将建立BIOS 所支持的中断向量, 即将BIOS提供的中断例程的入口地址登记在中断向量表中
- 硬件系统检测和初始化完成后, 调用int 19h进行操作系统的引导. 从此将计算机交由操作系统控制
- DOS 启动后, 除完成其它工作外, 还将它所提供的中断例程装入内存, 并建立相应的中断向量
端口的读写
图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 个地方的数据
- cpu 内部寄存器
- 内存单元
- 端口
- 各种接口卡, 网卡, 显卡
- 主板上的接口芯片
- 其他芯片
读写内存与寄存器的指令, mov, add, push...
读写端口的指令
- in, cpu 从端口读取数据
- out: cpu 往端口写入数据
端口的读写
访问端口的方法
in al,60h
, 从60h 端口读入一个字节
执行时与总线相关的操作
- CPU通过地址线将地址信息60h发出
- CPU通过控制线发出端口读命令, 选中端口所在的芯片, 并通知要从中读取数据
- 端口所在的芯片将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芯片
- 包含一个实时钟和一个有128个存储单元的RAM存储器
- 128 个字节的 RAM 中存储: 内部实时钟, 系统配置信息, 相关的程序(用于开机时配置系统信息)
- CMOS RAM 芯片靠电池供电, 关机后其内部的实时钟仍可正常工作, RAM 中的信息不丢失
- 该芯片内部有两个端口, 端口地址为70h和71h, CPU 通过两个端口读写CMOS RAM
- 70h地址端口, 存放要访问的CMOS RAM单元的地址
- 71h数据端口, 存放从选定的单元中读取的数据, 或要写入到其中的数据
- 读取CMOS RAM的两个步骤
- 将要读取的单元地址送入70h 地址端口
- 从数据端口71h 读出指定单元的内容
示例十八:
在屏幕中间显示当前的月份
事实上, 在CMOS RAM中的时间信息含有月份
图14:在cmos中包含的时间屏幕中间显示当前月份
- 从CMOS RAM的8号单元读出当前月份的BCD码
- 首先要向地址端口70h写入要访问的单元的地址-月份8
- 再从数据端口71h中取得指定单元中的数据-月份值
- 将用BCD码表示的月份以十进制的形式显示到屏幕上
- 将读到的数据前4位和后4位分离出来
- 由低4位为BCD码的“数字”转为ASCII码
- BCD 码值=十进制数码值
- 十进制数对应的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
外中断处理过程
可屏蔽中断所引发的中断过程
- 取中断类型码n
- 标志寄存器入栈, IF=0, TF=0
- CS , IP 入栈
- (IP)=(n*4), (CS)=(n*4+2)
可屏蔽中断信息来自于CPU外部, 中断类型码是通过数据总线送入CPU
(对比内中断: 中断类型码是在CPU内部产生的)
将IF置0的原因: 进入中断处理程序后, 禁止其他的可屏蔽中断
如果在中断处理程序中需要处理可屏蔽中断, 可以用指令将IF 置1
不可屏蔽中断的中断过程
- 标志寄存器入栈, IF=0, TF=0
- CS, IP入栈
- (IP)=(8), (CS)=(0AH)
8086 cpu 提供的设置if 指令, sti, 用于设置if=1, cli用于设置if=0
PC机键盘的处理过程
1. 键盘输入
- 键盘上的每一个键相当于一个开关, 键盘中有一个芯片对键盘上的每一个键的开关状态进行扫描
- 按下一个键时的操作
- 开关接通, 该芯片就产生一个扫描码, 扫描码说明了按下的键在键盘上的位置
- 扫描码被送入主板上的相关接口芯片的寄存器中, 该寄存器的端口地址为60H
- 松开按下的键时的操作
- 产生一个扫描码, 扫描码说明了松开的键在键盘上的位置
- 松开按键时产生的扫描码也被送入60H 端口中
- 扫描码--长度为一个字节的编码
- 按下一个键时产生的扫描码--通码, 通码的第7 位为 0
- 松开一个键时产生的扫描码--断码, 断码的第7位为 1
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中断例程的工作
-
读出60H 端口中的扫描码
-
根据扫描码分情况对待
-
如果是字符键的扫描码,将该扫描码和它所对应的字符码(
即 ASCII码) 送入内存中的BIOS 键盘缓冲区
-
如果是控制键(比如 Ctrl ) 和切换键(比如 CapsLock) 的扫描码, 则将其转变为状态字节(用二进制位记录控制键和切换键状态的字节)写入内存中存储状态字节的单元
-
-
对键盘系统进行相关的控制, 如向相关芯片发出应答信息
定制键盘输入处理(int 9 中断例程)
处理过程
- 键盘产生扫描码
- 扫描码送入60h 端口
- 引发9 号中断
========> 以上由硬件系统完成
- CPU执行int 9中断例程, 处理键盘输入
- DOS系统提供int 9中断例程
- 按照开发需求定制处理键盘的输入
示例十九:
编程任务
- 在屏幕中间依次显示 '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键后改变当前屏幕的显示颜色, 其他的键照常处理
解决的问题
-
改变屏幕的显示颜色
改变从B800开始的4000个字节中的所有奇地址单元中的内容, 当前屏幕的显示颜色即发生改变
-
F1改变功能,其他键照常
可以调用原int 9中断处理程序, 来处理其他的键盘输入
-
原int 9中断例程入口地址的保存
要保存原int 9中断例程的入口地址原因: 在新int 9中断例程中要调用原int 9中断例程
保存在哪里? 我们将地址保存在0:200单元处
-
新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 步
- 读取缓冲区第一个字单元中的键盘输入
- 将读取的扫描码送入ah, ASCII 码送入al
- 将己读取的键盘输入从缓冲区中删除
事实:
- 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
字符串的输入
示例二十二:
问题: 设计一个最基本的字符串输入程序,需要具备下面的功能
- 在输入的同时需要显示这个字符串
- 一般在输入回车符后, 字符串输入结束
- 能够删除已经输入的字符--用退格键
解决:
-
在输入的同时需要显示这个字符串
需要用栈的方式来管理字符串的存储空间
-
在输入回车符后,字符串输入结束
输入回车符后 , 在字符串中加入0, 表示字符串结束
-
在输入的同时需要显示这个字符串
每次有新的字符输入和删除一个字符的时候, 都应该重新显示字符串, 即从字符栈的栈底到栈顶, 显示所有的字符
过程
- 调用int 16h读取键盘输入
- 如果不是字符: ①如果是退格键, 从字符栈中弹出一个字符, 显示字符栈中的所有字符,继续执行1; ②如果是Enter 键, 向字符栈中压入0, 返回
- 如果是字符键: 字符入栈; 显示字符栈中的所有字符; 继续执行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
网友评论