1.写在前面
我前面介绍了汇编的一些东西,但是这里我打算重新介绍一下中断,同时通过一个简单的汇编的程序来带你了解中断,同时也会简单的介绍这个的程序怎么一步步的写出来的,这儿我会从8086的CMOS中读取对应的事件,同时也会写一个除法溢出的内中断,来帮助的大家来理解的中断。
2.本篇博客的概述
3.中断是什么?
中断是指计算机运行过程中,出现某些意外情况需主机干预时,机器能自动停止正在运行的程序并转入处理新情况的程序,处理完毕后又返回原被暂停的程序继续运行。这里的意外的情况,就是中断信息。这就好比我们在打字的时候,屏幕上会显示我们的打的字,如果这儿没有中断的,那么我们的打的字,不会立即出现在屏幕上的。还有就是现在比较火的自动驾驶,一辆自动驾驶的车,在路上遇到了特殊的情况,这个时候这辆自动驾驶的车应该立马刹车,这就是中断,不管CPU这个时候在干嘛!都会被打断,去执行刹车的逻辑。但是现在市面上很多自动驾驶的车都做不到,比较拉胯。
4.中断的分类
中断的分类有内中断和外中断,那么怎么理解内中断和外中断?
内中断:顾名思义就是中断信息来自CPU的内部。
外中断:顾名思义就是中断信息来自CPU的外部,响应一些外设的中断。
区别的方式就是中断的信息来自内部还是外部。
5.中断向量表
我们既然已经知道了中断的现象,我们的作为程序员,是不是要根据这个需求来想想这个如何实现的。
我们要确定的中断信息中内容是什么?怎么根据这个中断信息获取到对应的信息,然后根据这个中断信息来获取对应的中断程序,然后进行执行。中断同样也分成可屏蔽中断,不可屏蔽中断,因为CPU执行的任务也会分成紧急不紧急,如果CPU在执行某个很紧急的中断的时候,就好比刹车的中断,这个中断的优先级应该是最高的,这个时候如果来了一个中断,这个时候刹车的中断是不是不能打断,这个时候刹车的中断就是不可屏蔽中断,而能打断的中断就是可屏蔽中断。
我这儿大概的设计了一下中断的过程,上面提到的一个很重要的信息,就是根据中断信息获取到中断程序,这个映射关系有一个东西叫做中断向量表。这个中断向量表中存的有点像java中的map,键就是中断类型码,值就是中断程序的地址。
6.如何书写一个中断
6.1内中断
既然我们上面知道了一个简单的中断过程,就是CPU接收一个中断信息,然后根据这个中断信息,去中断向量表中找到对应的中断程序,然后执行,执行完中断程序后,然后回到原来程序继续执行。
这儿我们第一步就是书写我们的中断程序。具体的汇编代码如下:
assume cs:code,ds:data,ss:stack
data segment
db 128 dup(0)
data ends
stack segment stack
db 128 dup(0)
sta ends
code segment
;===========初始化栈=================================
start: mov ax,stack
mov ss,ax
mov sp,128
;===========初始化栈=================================
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
show_string:mov al,[si]
mov es:[di],al
inc si
add di,2
loop show_string
;=============循环显示字符串===========================
mov ax,4C00H
int 21H
do0end:nop
;===========除法溢出的中断程序=========================
code ends
end start
上面的汇编的代码主要书写对应的中断的程序,这儿选择用汇编程序来讲的中断的的意思,就是汇编更容易点介绍中断的程序,虽然单片机的也是可以演示对应的中断,但是单片机我没有那么多的硬件,就先用汇编吧,正好最近也是在学汇编。
我们的第一步也是完成了,但是没有设置对应的中断向量表,这个时候就算发生了除法的中断,上面的字符串也不会显示。下面的程序就是设置我们的中断向量表,具体的代码如下:
assume cs:code,ds:data,ss:stack
data segment
db 128 dup(0)
data ends
stack segment stack
db 128 dup(0)
stack ends
code segment
;===========初始化栈=================================
start: mov ax,stack
mov ss,ax
mov sp,128
;===========初始化栈=================================
;===========复制中断程序==============================
mov ax,cs
mov ds,ax
mov si,offset do0;设置ds:si指向源地址
mov ax,0
mov es,ax
mov di,200H;设置es:di指向目的地址
mov cx,offset do0end-offset do0;设置cx为传输长度
cld ;设置传输方向为正
rep movsb
;===========复制中断程序==============================
;===========设置中断向量表============================
cli
mov ax,0
mov es,ax
mov word ptr es:[0*4],200h
mov word ptr es:[0*4+2],0
sti
;===========设置中断向量表============================
;===========测试除法溢出=============================
mov ax,1000h
mov bh,1
div bh
;===========测试除法溢出=============================
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
因为8086的CPU的中断向量表在0~200H中,所以这儿我将对应的中断向量改成自己写的中断的程序,这样就实现对应的除法溢出的中断程序,具体的运行的结果如下:
可以看到的我们一个简单的除法的溢出的中断就实现了。
6.2外中断
我接下来要写的程序就是读取对应的CMOS中的时间,然后通过按键F1来改变显示的颜色,我们先来看下如何显示对应的时间,具体的代码如下:
assume cs:code,ds:data,ss:stack
data segment
db 256 dup(0)
data ends
stack segment stack
db 128 dup(0)
stack ends
code segment
start: mov ax,stack
mov ss,ax
mov sp,128
call cpy_boot
call sav_old_int9
mov bx,0
push bx
mov bx,7E00H
push bx
retf
mov ax, 4c00H
int 21H
Boot:
jmp BOOT_START
TIME_CMOS db 9,8,7,4,2,0
TIME_STYLE db 'YY/MM/DD HH:MM:SS',0
BOOT_START:
call init_reg
call clear_screen
call show_clock
mov ax,4c00H
int 21H
show_clock: call show_style
call set_new_int9
mov bx,OFFSET TIME_CMOS - OFFSET Boot + 7E00H
showTime: mov si,bx
mov di,160*20
mov cx,6
showDate: mov al,ds:[si]
out 70H,al
in al,71H
mov ah,al
shr ah,1
shr ah,1
shr ah,1
shr ah,1
and al,00001111B
add ah,30H
add al,30H
mov es:[di],ah
mov es:[di+2],al
add di,6
inc si
loop showDate
jmp showTime
showtimeRet: call set_old_int9
ret
set_old_int9: push bx
push es
mov bx,0
mov es,bx
cli
push es:[200H]
pop es:[9*4]
push es:[202H]
pop es:[9*4+2]
sti
pop es
pop bx
ret
set_new_int9:
push bx
push es
mov bx,0
mov es,bx
cli
mov word ptr es:[9*4],OFFSET new_int9 - OFFSET Boot + 7E00H
mov word ptr es:[9*4+2],0
sti
pop es
pop bx
ret
new_int9: push ax
call clear_buff
in al,60H
pushf
call dword ptr cs:[200H]
cmp al,3BH ;F1
jne int9Ret
call change_time_color
int9Ret: pop ax
iret
change_time_color:
push bx
push cx
push es
mov bx,0B800H
mov es,bx
mov bx,160*20 + 1
mov cx,17
changeTimeColor:inc byte ptr es:[bx]
add bx,2
loop changeTimeColor
pop es
pop cx
pop bx
ret
show_style: mov si,OFFSET TIME_STYLE - OFFSET Boot + 7E00H
mov di,160*20
call show_string
ret
clear_buff: mov ah,1
int 16H
jz clearBuffRet
mov ah,0
int 16H
jmp clear_buff
clearBuffRet: ret
show_string:
push dx
push ds
push es
push si
push di
showString: mov dl,ds:[si]
cmp dl,0
je showStringRet
mov es:[di],dl
add di,2
inc si
jmp showString
showStringRet:
pop di
pop si
pop es
pop ds
pop dx
ret
clear_screen:
mov bx,0
mov dx,0700H
mov cx,2000
clearScreen: mov es:[bx],dx
add bx,2
loop clearScreen
ret
init_reg:
mov bx,0B800H
mov es,bx
mov bx,0
mov ds,bx
ret
Boot_end: nop
sav_old_int9:
mov bx,0
mov es,bx
push es:[9*4]
pop es:[200H]
push es:[9*4+2]
pop es:[202H]
ret
cpy_boot:
mov bx,cs
mov ds,bx
mov si,OFFSET Boot
mov bx,0
mov es,bx
mov di,OFFSET 7E00H
mov cx,OFFSET Boot_end - OFFSET Boot
cld
rep movsb
ret
code ends
end start
上面的代码就是实现一个简单的功能,就是读取CMOS中的时钟的信息,然后按F1按键就可以改变时钟的颜色,现在我来为大家解释没一段代码,首先我们先看start方法中的代码,call cpy_boot
,具体的代码如下:
;===============将Boot到Boot_end的指令复制到0000:7E00H处======
cpy_boot:
mov bx,cs
mov ds,bx
mov si,OFFSET Boot
mov bx,0
mov es,bx
mov di,OFFSET 7E00H
mov cx,OFFSET Boot_end - OFFSET Boot
cld
rep movsb
ret
然后我们再看call sav_old_int9
,具体的代码如下:
;============将原来的中断保存起来==================
sav_old_int9:
mov bx,0
mov es,bx
push es:[9*4]
pop es:[200H]
push es:[9*4+2]
pop es:[202H]
ret
这个时候我们需要看剩下的start中的代码,具体的如下:
mov bx,0
push bx
mov bx,7E00H
push bx
retf
这个时候CPU会将CS:IP变成0000:7E00H这个时候就会执行我们刚刚复制的代码,我们继续往下看
;跳转到 BOOT_START处执行。
jmp BOOT_START
我们继续看BOOT_START中的代码,具体的如下:
call init_reg
call clear_screen
call show_clock
我们先看init_reg
方法中的内容具体的如下:
;=======初始化显存的位置==========
init_reg:
mov bx,0B800H
mov es,bx
mov bx,0
mov ds,bx
ret
继续看clear_screen
方法中内容具体的如下:
;===========清屏===============
clear_screen:
mov bx,0
mov dx,0700H
mov cx,2000 ;循环的次数
clearScreen: mov es:[bx],dx
add bx,2
loop clearScreen
ret
最后我们再看show_clock
方法中内容具体的如下:
;显示对应的时间
show_clock: call show_style ;按对应的格式显示对应的时间
call set_new_int9 ;设置对应新的int9中断程序
mov bx,OFFSET TIME_CMOS - OFFSET Boot + 7E00H ;设置要取的位置
showTime: mov si,bx
mov di,160*20
mov cx,6
showDate: mov al,ds:[si] ;读取对应的端口位置
out 70H,al
in al,71H
;将BCD码转成对应十进制
mov ah,al
shr ah,1
shr ah,1
shr ah,1
shr ah,1
and al,00001111B
add ah,30H
add al,30H
mov es:[di],ah
mov es:[di+2],al
add di,6
inc si
loop showDate
jmp showTime
showtimeRet: call set_old_int9 ;将原来的int9程序复原
ret
上面的程序就是从CMOS中读取对应的时间,然后设置显示屏幕上,同时我们也设置新的int9中断,当程序执行完了我们将原来的中断复原,我们无法做到完整的写出int9的中断,我们只能借助原来的int9的中断,进行一些对应的调整。我们来看下set_new_int9
方法,设置新的中断的程序,具体的代码如下:
set_new_int9:
push bx
push es
mov bx,0
mov es,bx
;设置新的中断
cli
mov word ptr es:[9*4],OFFSET new_int9 - OFFSET Boot + 7E00H
mov word ptr es:[9*4+2],0
sti
pop es
pop bx
ret
new_int9: push ax
call clear_buff
in al,60H
pushf
call dword ptr cs:[200H] ;调用老的中断程序
cmp al,3BH ;F1
jne int9Ret
call change_time_color ;是F1直接改变时钟的颜色
int9Ret: pop ax
iret
上面的代码设置的我们的新中断,如果按了F1,就直接修改时钟的显示颜色。至此整个程序就讲的差不多了。
6.3中断的流程
7.写在最后
本篇博客的重点就是中断的执行的流程,至于我写的程序不太重要,重要的是中断的执行流程,我们从中进行对应修改,以达到我们写的CPU能响应我们写的中断,这个时候就需要我们修改对应中断向量表了。