6 中断和外部设备操作

学习笔记
作者: MingXiao

6.1 位移指令

其中逻辑左移和算术左移是等价的

当移动位数大于1时,必须使用cl

mov al, 01010001B
mov cl, 3
shl al, cl

6.2 操作显存单元

显存总共32kB,占用了B8000HBFFFFH的内存空间,将每4kB分为1页,一共存8页

默认显示第0页,一页显示80*25个字符

每一列由两个字节组成,低字节是要显示的字符的ASCII码,高字节是显示的属性

6.3 描述内存单元的标号

什么是标号(label),就是表示一个数据区域的东西,如codesg, start

也可以这样写

code segment
	a: db 1, 2, 3, 4
	b: dw 0
start:
	mov si, offset a
	;xxxxx

这些都是地址标号

当标号去了冒号后,就成了数据标号,含有存储数据的位置类型(长度)

	a db 1, 2, 3
	b dw 0
s:	mov si, 0
	mov al, a[si]
	mov ah, 0
	add b, ax

不再需要byte ptrword ptr

同时数据标号可以在数据段和代码段使用,地址标号只能在代码段中使用

扩展用法

data segment
	a db 1, 2, 3, 4
	b dw 0
	c dw a, b
data ends

c dw a, b等价于c dw offset a, offset b

data segment
	a db 1, 2, 3, 4
	b dw 0
	c dd a, b
data ends

储存的是段地址加偏移地址,等价于

c dw offset a, seg a, offset b, seg b

seg可以获取标号的段地址

6.4 直接定址表

类似于哈希表,加快运算速度

函数也可以这么调用

assume cs:code, ds:data

data segment
	table dw sub1, sub2
data ends

code segment
start:
	mov ax, data
	mov al, 0
	mov ah, 2
	call func
	mov ax, 4c00H
	int 21
	
func:
	push bx
	cmp al, 1
	ja over					;当al>1时就结束
	mov bl, al
	mov bh, 0
	add bx, bx				;table是字型的,故要乘2
	call word ptr table[bx]

over:
	pop bx
	ret

sub1:
	;xxxx
	
sub2:
	;xxxx

这样写的函数便于补充和调用

6.5 中断

指CPU不再接着执行下面的任务,而是转移到别的地方执行

内中断:CPU内部的事件导致

外中断:外部设备导致,如键盘、鼠标的输入

每一种中断都有对应的中断服务程序(ISP,Interrupt Service Program),也就是中断例程

8086的内中断

  1. 除法溢出错误,0
  2. 单步执行指令(-t),1
  3. 执行into指令,4,是溢出时自动产生的(interrupt if overflow)
  4. 执行int n指令,n是中断类型码

在8086中有一个中断向量表,记录了不同类型的中断所需要跳转的CS:IP地址

ip = 0000:[N*4], cs = 0000:[N*4+2],N是中断类型

随后CPU执行CS:IP处的指令

执行过程

  1. CPU获取中断类型码N

  2. pushf

  3. 将标志位的TF = 0, IF = 0TF是trap flag,等于1时单步调试,IF是interrupt flag,等于1时能被中断

    整个意思就是程序将不能再被中断且连续执行

  4. push cs,push ip

  5. ip = 0000:[N*4], cs = 0000:[N*4+2]

6.6 编写一个中断指令

整个框架如下,doN表示中断类型码为N

assume cs:code

code segment
	
start:
	;doN 安装程序
	
	;设置中断向量表
	
	mov ax, 4c00H
	int 21H
	
	;doN主程序
	mov ax, 4c00H
	int 21H			;这是doN函数的返回
doNend:	nop			;程序结束标志

code ends
end start

安装程序

需要将主程序的内容复制到一段不会被使用的内存空间,在中断向量表的0000:0200之后的空间未被使用且不会被程序自动使用,可以储存

;doN安装程序
mov ax, cs
mov ds, ax
mov si, offset doN
mov ax, 0
mov es, ax
mov di, 200H
mov cx, offset doNend - offset doN		;长度就是doN的指令的长度
cld
rep movsb

设置中断向量表

想要发生N中断时自动调用,那么需要修改中断向量表中原来4N+2:4N处的内容

mov ax, 0
mov es, ax
mov word ptr es:[N*4], 200H		;保证中断后IP=200H
mov word ptr es:[N*4+2], 0		;保证中断后CS=0

0000:0200H是doN的入口地址,下面是doN的主程序

6.7 单步中断

-t指令到底干了些什么

  1. 首先产生中断
  2. TF = 1
  3. CPU单步执行指令,返回修改后的寄存器内容和下一条指令

如果一开始就是TF = 1,那么将一直重复-t指令的第一个指令,永远不能结束

故进入中断前CPU会自动设置TF = 0

单步中断不响应

有些时候,-t指令的单步中断不会被识别,如

mov ax, 12H
mov ss, ax
mov sp, 21H

在执行完mov ss, ax后会自动执行下一条,因为规范就是ss, sp连续定义

ss下一条必须是sp的赋值

6.8 由int Nh引发的中断

执行int n时,自动执行以下操作

pushf
push cs
push ip

所以在中断调用的函数(中断例程)中需要在结束前加上一句iret,依次执行

pop ip
pop cs
popf

共弹出6个元素

6.9 BIOS和DOS中断处理

BIOS中断

BIOS:Basic Input Output System,在主板的ROM(read only memory)存放的一套程序

对于8086来说,这个程序长8kB,从FE000H开始,到FFFFFH结束

主要内容:硬件系统的检测和初始化程序;外部和内部中断的中断例程;对外部设备进行I/O中断

DOS中断

比BIOS更高层,是系统层面的中断

和硬件有关的中断,一般都是调用的BIOS的中断例程

int 21H

这个中断使用ah中的值作为功能号,返回结果保存在al

4cH表示程序返回,结果保存在al

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

  1. CPU供电,初始化使得CS:IP = FFFF:0000,执行这个位置的程序,跳转到BIOS的硬件检测和初始化
  2. 建立BIOS中断的中断向量表
  3. 调用int 19H,引导操作系统,交管控制权到OS(这里是DOS)
  4. DOS建立自己的中断向量表和中断例程

6.10 端口的读写

CPU的邻居

CPU可以直接读写三个地方的数据

  1. 内部寄存器
  2. 内存单元
  3. 端口(连接到主板的外部设备和CPU的交流通道)

端口有64K的地址空间

读写指令

in用于读端口

in al, 60h表示从60h这个端口号读入一个字节,赋值给al

执行过程:

  1. CPU通过地址总线将60H发出,通过控制总线发出in(读)命令
  2. 60H端口将内容通过数据总线送入CPU

out用于写端口

out 21h, al表示将al的内容写入到21h端口

in, out指令中,只能使用ax, al来进行读写,前者用于16bit,后者8bit

6.11 CMOS RAM芯片

包含一个实时钟和一个128个单元的RAM存储器,该芯片依靠电池工作,主板断电后内容不会丢

有两个端口,70h, 71h,前者存放读取的内存地址,后者存放选定的内存里的值

提取CMOS RAM中的时间

已知地址08存放月份,使用BCD码存放,如何提取?

assume cs:code

code segment
	mov al, 8					;访问8号地址
	out 70h, al					;写入8
	
	in al, 71h					;读出月份
	;下面是将BCD转换为ASCII
	;先将高四位和第四位分开,再直接+30h
	;就得到月份的十位和个位的ASCII码
	
	mov ah, al
	mov cl, 4			
	shr ah, cl					;逻辑右移4次得到高四位,最高位补0
	and al, 00001111B			;做and运算
	
	add al, 30H					;变成ASCII码
	add ah, 30H
	;打印
code ends

end

6.12 外设连接与中断

外中断分为不可/可屏蔽中断,前者是CPU必须响应的中断,后者可以不响应

可屏蔽:看标志位寄存器的IFIF = 0,则不响应这次中断;IF = 1,则在执行完当前指令后响应;几乎所有外设的中断都是可屏蔽中断;信息来自于CPU外部,通过数据总线送入CPU

不可屏蔽:CPU必须在执行完当前指令后响应,对于8086来说,这个中断的中断码固定为2N = 2, int 2

6.13 PC机键盘处理过程

键盘按键

  1. 按下键盘,得到一个扫描码,表示按下的键的位置,称为通码
  2. 通码送入寄存器的60H端口
  3. 松开按键,得到一个扫描码,称为断码
  4. 断码送入60H

断码的第七位为1,通码的第七位为0,有通码 + 80H = 断码,这都不是ASCII码

引发9号中断

  1. 键盘输入达到60H端口,芯片向CPU发出N = 9的可屏蔽中断

  2. IF = 1,响应中断,否则不管

  3. 接受的输入信息存放在BIOS键盘缓冲区16B的空间),这个区域可以存放15个键盘输入,一个输入占1B

    高位是扫描码,低位是对于的ASCII码

    如果输入的是控制键/切换键(shift,ctrl等),则会改变键盘状态字节,也是一个FLAG,按位生效,存放在0040:0017

执行9号中断

  1. 读出60H的扫描码
  2. 如果是字符,将扫描码和对于的ASCII码送入键盘缓冲区;如果是控制键/切换键,将其转变为状态字节,送入FLAG的单元
  3. 向键盘芯片发出回应

6.14 操作外部设备

以键盘操作为例

按下键盘时产生的int 9中断,如果响应了,那么跳转到BIOS的键盘中断处理程序int 16H

int 16H中断

ah = 0时,从键盘缓冲区读取1B,并删除这个字节,返回值为ah = 扫描码, al = ASCII码

ASCII码会根据键盘状态字而改变,如果shift按下,caps为1,那么按键A扫描码不会改变,ASCII码变为61H(小写a)

int 16H的0号中断例程具体实现过程

  1. 检查缓冲区有无数据
  2. 没有数据,重复1;有数据,向下走
  3. 读取第一个字单元的键盘输入
  4. 将扫描码送入ah,ASCII码送入al
  5. 将读取到的输入从缓冲区删除

int 9int 16H是一对配合的中断例程,前者在键盘按下/松开时写数据,后者在被调用时读数据

6.15 定制和改写键盘中断例程

例:现在想在屏幕上依次显示a~z,且人看得见;显示过程中,按下ESC后改变字体颜色,要求原先的int 9中断例程仍然能调用

解:自动变化字母,人能看清,那么得延迟,延迟函数有两个

  1. 调用中断例程

    delay:
        push ax
        push cx
        push dx
        mov cx, 000FH
        mov dx, 4240H
        mov ax, 8600H
        int 15H
        pop dx
        pop cx
        pop ax
        iret
    

    int 15Hah=86H时,会延迟cx:dx微秒的延迟,1s = 10e6 us = F4240H us

  2. CPU空循环

    delay:
        push ax
        push dx
        mov dx, 10H
        mov ax, 0
    s1:    sub ax, 1
        sbb dx, 0        ;使用sbb,带借位的减法
        cmp ax, 0
        jne s1
        cmp dx, 0
        jne s1
        pop dx
        pop ax
        ret
    

    8086的时钟频率为5MHz,一个周期200ns,4个周期构成一个总线周期(存/取1B的时间)

    对于sub指令,占4B,需要4个总线周期,执行这条指令也需要1个总线周期,共5个,时间为\(200\times 4\times 5 = 4\mathsf{us}\),为了实现秒级的延迟,16B的寄存器不够,需要dx:ax配合使用

​ 按下ESC后改变颜色,得写新的int 9中断例程

int 9:
	push ax
	push bx
	push es
	
	in al, 60H		;从60H端口读入键盘的输入
	pushf			;标志位寄存器压栈
	
	pushf			;为了手动设置IF,TF标志位为0而压栈,可以认为上面是准备工作
	pop bx			;和上一句一起,将标志位赋给bx
	and bh, 11111100B
	push bx
	popf 			;和上一句一起改变标志位寄存器
	
	;执行原来的int 9中断,取键盘输入,这里留空后面补上
	
	cmp ah, 1h		;ESC的扫描码为1H
	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			;中断例程的返回

要求原先的int 9中断例程忍让能用,那么得存放原先的入口

data segment
	dw 0, 0
data ends			;datasg 用来保存原来的入口

;下面是改变int 9的入口到自己定义的
mov ax, 0
mov es, ax

;通过栈来实现存原先的入口
push es:[9*4]
pop ds:[0]
push es:[9*4+2]
pop ds:[2]

;重定向int 9的入口
mov word ptr es:[9*4], offset int9
mov es:[9*4+2], cs

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

这样原来的中断指令的地址就在ds:[0], ds:[2]中,通过call dword ptr ds:[0]就能实现原先的中断例程(取键盘输入)

完整的指令如下

assume cs:code
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
;通过入栈出栈的方式将原始int9中断例程的入口地址放到定义好的数据段
    push es:[9*4]
    pop ds:[0]
    push es:[9*4+2]
    pop ds:[2]
;将中断向量表的9号例程指向自定义的例程程序入口,即cs:offset int9处
    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 cx
    push dx
    mov cx,000fh
    mov dx,4240h
    mov ah,86h
    mov al,0
    int 15h
    pop dx
    pop cx
    pop ax
    ret

;新的int9中断例程
int9: 
;子程序中用到的寄存器压栈
    push ax
    push bx
    push es
;从60h 从端口读入键盘的输入
	in al,60h
;标志位寄存器压栈
	pushf
;手动设置IF、TF标志位为零
    pushf
    pop bx ;先push再pop,将标志位寄存器的值放到通用寄存器中
    and bh,11111100b
    push bx ;压栈改变后的标志位寄存器的值
    popf ;设置IF、TF标志位为零
    call dword ptr ds:[0] ;执行原来的int9中断例程,取键盘输入
    cmp al,1h ; 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

注意到int9函数不用写在主程序中,这是在键盘输入时自动跳转的

cli指令可以屏蔽外部中断,保证代码运行,当保护的代码结束时,sti指令可以恢复外部中断的中断性

6.16 磁盘读写

BIOS提供int 13H作为磁盘读写中断



Comments