x86汇编语言——中断处理
外部中断
外部硬件中断
从处理器外部来的中断信号,外部硬件中断通过两根信号线引入处理器内部,8086处理器的信号线为NMI和INTR

- 非屏蔽中断(NMI),不会被阻断和屏蔽的中断,intel处理器规定NMI中断信号从0跳变到1后必须维持4个以上时钟周期才有效。实模式下NMI的中断号为2
- 可屏蔽中断,intel处理器允许256个中断,8259芯片(中断控制器)提供其中15个
![在这里插入图片描述]()
在8259内部有中断屏蔽寄存器IMR,对应8个引脚,决定了该引脚能够将中断信号传给处理器。中断能否被处理还要看标志寄存器的IF位,通过cli和sti设置
中断向量表(实模式)
实模式下,中断处理程序的入口点在内存物理地址的0x00000到0x003ff,共1KB空间,即中断向量表
每个中断在中断向量表中占双字

处理中断的过程:
- 保护断点现场,将标志寄存器FLAGS压栈,清除IF(中断标志)和TF(陷阱标志),将CS和IP压栈
- 执行中断程序,将得到的终端号乘4在中断向量表中,取出中断程序的偏移地址和段地址,传送CS和IP。此时由于IF标志被清除,因此在中断处理过程中,处理器将不再响应中断
- 返回断点继续执行,中断处理程序的最后一条指令为iret,处理器依次出栈CS和IP,标志寄存区
中断随时可能会发生,因而中断向量表是BIOS在计算机启动时完成的
实时时钟
外围设备控制芯片ICH中,集成了实时时钟电路RTC,和静态存储器CMOS RAM
日期和时间信息是保存在 CMOS RAM 中的,通常有 128 字节,而日期和时间信息只占了一小部分容量,其他空间则用于保存整机的配置信息,比如各种硬件的类型和工作参数、开机密码和辅助存储设备的启动顺序等。
RTC 芯片由一个振荡频率为 32.768kHz 的石英晶体振荡器(晶振)驱动,经分频后,用于对CMOS RAM 进行每秒一次的时间刷新
CMOS RAM 的访问,需要通过两个端口来进行。 0x70 或者 0x74 是索引端口,用来指定 CMOS RAM 内的单元; 0x71 或者 0x75 是数据端口,用来读写相应单元里的内容
;读取星期
mov al,0x06
out 0x70,al
in al,ox71
从很早的时候开始,端口 0x70 的最高位(bit 7)是控制 NMI 中断的开关,为 0 时,允许 NMI 中断到达处理器,为 1 时,则阻断所有的 NMI 信号,其他 7 个比特,即 0~6 位,
则实际上用于指定 CMOS RAM 单元的索引号,尽管端口 0x70 的位 7 不是中断信号,但它能控制与非门的输出,决定真正的NMI 中断信号是否能到达处理器。
在早期的系统中,计算机的制造成本很高,为了最大化地利用硬件资源,导致出现这种稀奇古怪的做法,
CMOS RAM 中保存的日期和时间,通常是以二进制编码的十进制数(Binary Coded Decimal,BCD)
单元 0x0A~0x0D 不是普通的存储单元,而被定义成 4 个寄存器的索引号,也是通过 0x70 和0x71 这两个端口访问的。这 4 个寄存器用于设置实时时钟电路的参数和工作状态
寄存器 A 和 B 用于对 RTC 的功能进行整体性的设置,具体参考P156

每次当中断实际发生时,可以在程序(中断处理过程)中读寄存器 C 的内容来检查中断的原因。该寄存器还有一个特点,就是每次读取它后,所有内容自动清零。而且,如果不读取它的话(换句话说,相应的位没有清零),同样的中断将不再产生。
程序目标:RTC芯片定时发出中断,执行代码读取CMOS RAM信息
用户程序
注意:处理器在设计的时候就规定,当遇到修改段寄存器 SS 的指令时,在这条指令和下一条指令执行完毕期间,禁止中断,以此来保护堆栈
计算机启动后, RTC 芯片的中断号默认是 0x70
;代码清单9-1
;文件名:c09_1.asm
;文件说明:用户程序
;创建日期:2011-4-16 22:03
;===============================================================================
SECTION header vstart=0 ;定义用户程序头部段
program_length dd program_end ;程序总长度[0x00]
;用户程序入口点
code_entry dw start ;偏移地址[0x04]
dd section.code.start ;段地址[0x06]
realloc_tbl_len dw (header_end-realloc_begin)/4
;段重定位表项个数[0x0a]
realloc_begin:
;段重定位表
code_segment dd section.code.start ;[0x0c]
data_segment dd section.data.start ;[0x14]
stack_segment dd section.stack.start ;[0x1c]
header_end:
;===============================================================================
SECTION code align=16 vstart=0 ;定义代码段(16字节对齐)
new_int_0x70:
push ax
push bx
push cx
push dx
push es
.w0:
mov al,0x0a ;阻断NMI。当然,通常是不必要的
or al,0x80
out 0x70,al
in al,0x71 ;读寄存器A
test al,0x80 ;测试第7位UIP
jnz .w0 ;以上代码对于更新周期结束中断来说
;是不必要的
xor al,al
or al,0x80
out 0x70,al
in al,0x71 ;读RTC当前时间(秒)
push ax
mov al,2
or al,0x80
out 0x70,al
in al,0x71 ;读RTC当前时间(分)
push ax
mov al,4
or al,0x80
out 0x70,al
in al,0x71 ;读RTC当前时间(时)
push ax
mov al,0x0c ;寄存器C的索引。且开放NMI
out 0x70,al
in al,0x71 ;读一下RTC的寄存器C,否则只发生一次中断
;此处不考虑闹钟和周期性中断的情况
mov ax,0xb800
mov es,ax
pop ax
call bcd_to_ascii
mov bx,12*160 + 36*2 ;从屏幕上的12行36列开始显示
mov [es:bx],ah
mov [es:bx+2],al ;显示两位小时数字
mov al,':'
mov [es:bx+4],al ;显示分隔符':'
not byte [es:bx+5] ;反转显示属性
pop ax
call bcd_to_ascii
mov [es:bx+6],ah
mov [es:bx+8],al ;显示两位分钟数字
mov al,':'
mov [es:bx+10],al ;显示分隔符':'
not byte [es:bx+11] ;反转显示属性
pop ax
call bcd_to_ascii
mov [es:bx+12],ah
mov [es:bx+14],al ;显示两位小时数字
mov al,0x20 ;中断结束命令EOI
out 0xa0,al ;向从片发送
out 0x20,al ;向主片发送
pop es
pop dx
pop cx
pop bx
pop ax
iret
;-------------------------------------------------------------------------------
bcd_to_ascii: ;BCD码转ASCII
;输入:AL=bcd码
;输出:AX=ascii
mov ah,al ;分拆成两个数字
and al,0x0f ;仅保留低4位
add al,0x30 ;转换成ASCII
shr ah,4 ;逻辑右移4位
and ah,0x0f
add ah,0x30
ret
;-------------------------------------------------------------------------------
start:
mov ax,[stack_segment]
mov ss,ax
mov sp,ss_pointer
mov ax,[data_segment]
mov ds,ax
mov bx,init_msg ;显示初始信息
call put_string
mov bx,inst_msg ;显示安装信息
call put_string
mov al,0x70
mov bl,4
mul bl ;计算0x70号中断在IVT中的偏移
mov bx,ax
cli ;防止改动期间发生新的0x70号中断
push es
mov ax,0x0000
mov es,ax
mov word [es:bx],new_int_0x70 ;偏移地址。
;将中断处理程序的地址写入中断向量表中
mov word [es:bx+2],cs ;段地址
pop es
mov al,0x0b ;RTC寄存器B
or al,0x80 ;阻断NMI
out 0x70,al
mov al,0x12 ;设置寄存器B,禁止周期性中断,开放更
out 0x71,al ;新结束后中断,BCD码,24小时制
mov al,0x0c
out 0x70,al
in al,0x71 ;读RTC寄存器C,复位未决的中断状态
;RTC 芯片设置完毕后,8259 是不会允许 RTC中断,需要修改它内部的中断屏蔽寄存器 IMR。
;IMR 是一个 8 位寄存器,位 0 对应着中断输入引脚 IR0,位 7 对应着引脚 IR7,相应的位是 0 时,允许中断,为 1 时,关掉中断
in al,0xa1 ;读8259从片的IMR寄存器
and al,0xfe ;清除bit 0(此位连接RTC)
out 0xa1,al ;写回此寄存器
sti ;重新开放中断
mov bx,done_msg ;显示安装完成信息
call put_string
mov bx,tips_msg ;显示提示信息
call put_string
mov cx,0xb800
mov ds,cx
mov byte [12*160 + 33*2],'@' ;屏幕第12行,35列
.idle:
hlt ;使CPU进入低功耗状态,直到用中断唤醒
not byte [12*160 + 33*2+1] ;反转显示属性
jmp .idle
;-------------------------------------------------------------------------------
put_string: ;显示串(0结尾)。
;输入:DS:BX=串地址
mov cl,[bx]
or cl,cl ;cl=0 ?
jz .exit ;是的,返回主程序
call put_char
inc bx ;下一个字符
jmp put_string
.exit:
ret
;-------------------------------------------------------------------------------
put_char: ;显示一个字符
;输入:cl=字符ascii
push ax
push bx
push cx
push dx
push ds
push es
;以下取当前光标位置
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
in al,dx ;高8位
mov ah,al
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
in al,dx ;低8位
mov bx,ax ;BX=代表光标位置的16位数
cmp cl,0x0d ;回车符?
jnz .put_0a ;不是。看看是不是换行等字符
mov ax,bx ;
mov bl,80
div bl
mul bl
mov bx,ax
jmp .set_cursor
.put_0a:
cmp cl,0x0a ;换行符?
jnz .put_other ;不是,那就正常显示字符
add bx,80
jmp .roll_screen
.put_other: ;正常显示字符
mov ax,0xb800
mov es,ax
shl bx,1
mov [es:bx],cl
;以下将光标位置推进一个字符
shr bx,1
add bx,1
.roll_screen:
cmp bx,2000 ;光标超出屏幕?滚屏
jl .set_cursor
mov ax,0xb800
mov ds,ax
mov es,ax
cld
mov si,0xa0
mov di,0x00
mov cx,1920
rep movsw
mov bx,3840 ;清除屏幕最底一行
mov cx,80
.cls:
mov word[es:bx],0x0720
add bx,2
loop .cls
mov bx,1920
.set_cursor:
mov dx,0x3d4
mov al,0x0e
out dx,al
mov dx,0x3d5
mov al,bh
out dx,al
mov dx,0x3d4
mov al,0x0f
out dx,al
mov dx,0x3d5
mov al,bl
out dx,al
pop es
pop ds
pop dx
pop cx
pop bx
pop ax
ret
;===============================================================================
SECTION data align=16 vstart=0
init_msg db 'Starting...',0x0d,0x0a,0
inst_msg db 'Installing a new interrupt 70H...',0
done_msg db 'Done.',0x0d,0x0a,0
tips_msg db 'Clock is now working.',0
;===============================================================================
SECTION stack align=16 vstart=0
resb 256
ss_pointer:
;===============================================================================
SECTION program_trail
program_end:
内部中断
内部中断发生在处理器内部,是由执行的指令引起的。 div 或者 idiv 指令的除数为零时,或者除法的结果溢出时,将产生中断 0(0 号中断),这就是除法错中断
内部中断不受标志寄存器 IF 位的影响,也不需要中断识别总线周期,它们的中断类型是固定的,可以立即转入相应的处理过程。
软中断
软中断是由 int 指令引起的中断处理
int3
int imm8
into
int3 是断点中断指令
int3 和 int 3 不是一回事
into 是溢出中断指令,当处理器执行这条指令时,如果标志寄存器的 OF 位是 1,那么,将产生 4 号中断。否则什么也不做
BIOS中断
BIOS 中断,是因为这些中断功能是在计算机加电后, BIOS 程序执行期间建立起来的
BIOS 可能会为一些简单的外围设备提供初始化代码和功能调用代码,并填写中断向量表,但也有一些 BIOS 中断是由外部设备接口自己建立的。
- 每个外部设备接口,包括各种板卡,如网卡、显卡、键盘接口电路、硬件控制器等,都有自己的只读存储器(Read Only Memory, ROM),类似于 BIOS 芯片,这些 ROM 中提供了它自己的功能调用例程,以及本设备的初始化代码。按照规范,前两个单元的内容是 0x55 和 0xAA,第三个单元是本 ROM 中以 512 字节为单位的代码长度;从第四个单元开始,就是实际的 ROM 代码。
- 从内存物理地址 A0000 开始,到 FFFFF 结束,有相当一部分空间是留给外围设备的。如果设备存在,那么,它自带的 ROM 会映射到分配给它的地址范围内
- BIOS 程序会以 2KB 为单位搜索内存地址 C0000~E0000 之间的区域。当发现某个区域的头两个字节是 0x55 和 0xAA 时,意味着该区域有 ROM 代码存在。接着,它对该区域做累加和检查,看结果是否和第三个单元相符。如果相符,就从第四个单元进入。这时,处理器执行的是硬件自带的程序指令,这些指令初始化外部设备的相关寄存器和工作状态,最后,填写相关的中断向量表,使它们指向自带的中断处理过程。
用户程序
;===============================================================================
SECTION header vstart=0 ;定义用户程序头部段
program_length dd program_end ;程序总长度[0x00]
;用户程序入口点
code_entry dw start ;偏移地址[0x04]
dd section.code.start ;段地址[0x06]
realloc_tbl_len dw (header_end-realloc_begin)/4
;段重定位表项个数[0x0a]
realloc_begin:
;段重定位表
code_segment dd section.code.start ;[0x0c]
data_segment dd section.data.start ;[0x14]
stack_segment dd section.stack.start ;[0x1c]
header_end:
;===============================================================================
SECTION code align=16 vstart=0 ;定义代码段(16字节对齐)
start:
mov ax,[stack_segment]
mov ss,ax
mov sp,ss_pointer
mov ax,[data_segment]
mov ds,ax
mov cx,msg_end-message
mov bx,message
.putc:
mov ah,0x0e
mov al,[bx]
int 0x10
inc bx
loop .putc
.reps:
mov ah,0x00
int 0x16
mov ah,0x0e
mov bl,0x07
int 0x10
jmp .reps
;===============================================================================
SECTION data align=16 vstart=0
message db 'Hello, friend!',0x0d,0x0a
db 'This simple procedure used to demonstrate '
db 'the BIOS interrupt.',0x0d,0x0a
db 'Please press the keys on the keyboard ->'
msg_end:
;===============================================================================
SECTION stack align=16 vstart=0
resb 256
ss_pointer:
;===============================================================================
SECTION program_trail
program_end:



浙公网安备 33010602011771号