文章目录[隐藏]
上一节:19、硬盘和显卡的访问与控制
下一节:21、32位x86处理器编程架构
01、中断和硬件中断
非屏蔽中断NMI
、可屏蔽中断INTR
。
02、中断控制器简介
使用8259A中断控制器芯片
,其有自己的端口号,可以设置中断的优先级、屏蔽某些中断信号等等,中断引脚的优先级从高到低
依次为主片IR0~~IR1
、从片IR0~~IR7
、主片的IR3~~IR7
。
8259A
内部有一个中断屏蔽寄存器,对应引脚输入0表示允许
中断进入、1表示禁止
中断进入。
除了中断屏蔽寄存器,还有处理器内部的FLAGS
标志寄存器中的IF
标志位:IF
为0表示忽略
中断信号、IF
为1表示接收
中断信号,使用CLI
和STI
设置标志位IF
。
03、中断号、中断处理过程、中断向量表
默认中断号为:
中断处理过程:在实模式下,保护现场,处理器知道了中断号,使用中断号访问中断向量表,从中取出中断处理程序的逻辑段地址和偏移地址,转到那里去执行中断处理程序,执行完之后返回。
中断向量表IVT
:是BIOS
在开机时创建。
使用Bochs
虚拟机调式:中断向量表1000
个字节,512
个字,从地址0
开始。
设置断点:b 0x7c00
查看IVT
:xp /512xh 0
04、实时时钟、CMOS RAM和BCD编码
实时时钟:提供基准时间,存储在CMOS RAM
芯片中。
CMOS RAM
在64~256
字节之间,日期和时间信息占据一小部分,剩余的用来保存整机的配置信息(硬件类型、工作参数、开机密码、辅助存储设备的启动顺序等等)。
索引端口0x70
、0x74
,指定偏移地址(索引号);
数据端口0x71
、0x75
,用来读取偏移地址处的内容。
二进制形式的十进制编码(BCD:Binary Coded Decimal
)
左边25的十进制,右边上为纯二进制编码,右边下为BCD编码。
05、实时时钟芯片的中断信号
RTC
可产生3种
中断信号:
1、PF(Periodic Interrupt)
可调节,最慢500ms
发生一次、最快30.517us
发生一次,使用下图修改。
分频器修改:这是基于32.768KHz
时的设置。
其他频率参考MC146818
芯片手册。
PF
中断是否允许发生由寄存器B
的位6
控制,0
为不允许、1
为允许。
若寄存器A位3~0
选择了0000
,则寄存器B的位6
自动置0
。
2、UI(Update-ended Interrupt)
每隔一秒,RTC
将更新CMOS RAM
中的时间和日期,更新操作包括读取并增加日期和时间、检查数据是否超出范围并溢出、检查是否到了闹钟时间,设置相关寄存器的状态,最后更新之后的数据写回原来的位置。
这些步骤和过程叫做更新周期,在每个更新周期结束时,若允许,RTC
将发出一个中断信号表示本次周期更新结束,就叫做更新周期结束中断。
更新周期时候会进行由寄存器B的位7控制:为0表示每秒都会产生、为1表示暂停更新周期并且此后不再产生更新周期。
更新周期结束中断时候产生取决于寄存器B的位4:为0表示不产生、为1比爱是允许在每个更新周期结束时产生中断。
更新周期是否开始由寄存器A的位7决定:此位是只读的,为0表示更新周期至少在488us内不会启动,即此时访问CMOS RAM中的时间是安全的;为1表示正处于更新周期,或者更新周期马上就要启动。
如果寄存器B的位7(SET位)为0,在分频电路正确配置的情况下,更新周期每秒发生一次,更新周期至少在寄存器B的位7(UIP位)置1之后的488us内开始,而且整个周期的完成时间不会多余1984us。
在更新周期进行的时候,和日期时间有关的存储单元0x00~0x09
的这一部分会和外部总线脱离,为了防止因外部数据访问导致的数据冲突和破坏;
因此要安全的访问0x00~0x09
这一部分数据,要避开更新周期,有两个可以选择的时机:
- 1、当检测到更新结束中断发生时,有
999ms
时间用于读取有效的日期时间和数据。 - 2、若检测到寄存器A的位7(
UIP
位)是0,意味着在更新周期开始之前滑油488us
的时间用于读取有效的日期时间和数据。
3、AI(Alarm Interrupt)
寄存器B的位5(AIE
位)控制闹钟中断信号是否会产生:为0表示不产生、为1表示产生。
中断类型的判断:读取寄存器C
相关位进行判断。
- 位7:为1表示有中断发生、为0表示没有;
- 位7为1时再根据位4、5、6进一步判断是哪种中断发生;
- 位6:为0表示没发生、为1表示发生;
- 位5:为0表示没发生、为1表示发生;
- 位4:为0表示没发生、为1表示发生;
- 其中低4位始终保持为0,
寄存器C是只读的,读操作将会导致所有位被清零
。
06、安装0x70号中断的处理过程
加载器程序使用c08_mbr.asm
,加载器是一样的可通用,具体代码查看c09_1.asm
。
当处理器执行任何一条改变栈段寄存器SS
的指令时,会在下一条指令执行前禁止中断,所以修改SP
的指令需要紧跟在修改SS
的指令后面。
访问中断向量表:因为IVT
保存在物理地址为0
处,所以程序中将地址0x0000
当作段地址传给ES
。
07、启用更新周期结束中断
此小节代码如下,具体代码查看c09_1.asm
。
...
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,利用这个机会打开非屏蔽中断NMI
in al,0xa1 ;读端口0xA1(默认)读8259从片的IMR寄存器
and al,0xfe ;清除bit 0(此位连接RTC)
out 0xa1,al ;写回此寄存器
sti ;重新开放中断
...
阻断NMI
信号原理:
寄存器B的各个位介含义:
代码中设置寄存器B的值为0x12
,含义参考上图即可。
其中小时模式为12小时制时,其中时所在寄存器位7位0表示上午、为1表示下午。
读8259
从片的IMR
寄存器,来设置中断屏蔽寄存器的相应位为0
已开启以开启中断。
08、用TEST指令等待更新周期结束
此小节代码如下,具体代码查看c09_1.asm
。
...
.w0:
mov al,0x0a ;阻断NMI。当然,通常是不必要的
or al,0x80
out 0x70,al
in al,0x71 ;读寄存器A
test al,0x80 ;测试第7位UIP
jnz .w0 ;以上代码对于更新周期结束中断来说
;是不必要的
....
判断RTC
是否处于更新周期,根据寄存器A的位7判断:0表示未开始,此时访问CMOS RAM
中的日期时间是安全的、1表示正处于更新周期或者更新周期马上就要启动。
使用test
指令判断某一位是0还是1,test
指令的and
指令类似,但是test
指令执行之后不保存运算结果,影响标志位ZF
、SF
、PF
,AF
未定义,OF=CF=0
。
RTC
更新周期时序:
09、读取BCD码的时间并显示在屏幕上
此小节代码如下,具体代码查看c09_1.asm
。
...
mov ax,0xb800
mov es,ax
pop ax
call bcd_to_ascii
mov bx,12*160 + 36*2 ;从屏幕上的12行36列开始显示
;一个字符占2个字节
mov [es:bx],ah
mov [es:bx+2],al ;显示两位小时数字
....
时间的时分秒分别在CMOS RAM
的0、2、4
位上:
10、用NOT指令反转时间分隔符的颜色
此小节代码如下,具体代码查看c09_1.asm
。
...
mov al,':'
mov [es:bx+4],al ;显示分隔符':' mov byte [es:bx+4], ':'
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 ;显示两位小时数字
...
not
指令:这里程序运行时这个冒号(':'
)将会一秒改变一次。
11、用IRET指令从中断过程返回
此小节代码如下,具体代码查看c09_1.asm
。
...
mov al,0x20 ;中断结束命令EOI(end of interrupt)
out 0xa0,al ;向从片发送
out 0x20,al ;向主片发送
pop es
pop dx
pop cx
pop bx
pop ax
iret
...
中断服务寄存器ISR
:位为1表示正在响应对应的中断,一旦响应了该中断,8259A
中断控制器芯片无法知道该中断何时结束,若不清除相应位,则下一次从该引脚来的中断将会得不到处理。
中断从主片来的则EOI
指令只需要发给主片,要是从片来的则EOI
指令。片和从片都需要发送。
IRET
指令从中断处理程序返回。
12、用停机指令HLT使处理器进入低功耗状态
此小节代码如下,具体代码查看c09_1.asm
。
执行剩余程序:
...
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
...
在虚拟机中运行:冒号一秒翻转一次、@为处理器停机之后被中断唤醒一次翻转一次
Virtual Box
虚拟机设置:
Bochs
虚拟机设置:
13、内部中断和软中断
内部中断:在处理执行指令时发生了错误或者故障引起的。
- 比如除法指令除数为0、除法的结果溢出时产生0号中断(除法错中断)。
- 比如遇到非法指令(指令的操作码未定义,或者指令超过了规定的长度)时产生6号中断。操作码未定义表示无法译码或执行。
- 内部中断不受标志寄存器
FLAGS
的中断标志位IF
的影响,处理器转到内一个中断的中断号,可以直接转到对应中断处理程序执行。
软中断:使用指令产生中断
int3
:断点中断指令,供调试器使用故意设置的陷阱,又叫做陷阱终端,机器码是16进制的CC
。
调时程序可以单步执行指令,也允许设置断点,当程序执行到断电时停下,此时可以方便查看寄存器等内容。
断点就是某一条指令的起始地址,比如断点选在xor di,di
,机器码是31FF
处,当我们设置断点是,调试器将此条指令的机器码改为CC
。
当处理器执行到此条指令时,发现是机器码是CC
即断点指令int3
,从而转去执行断点中断处理程序,进入中断前依次压入标志寄存器FLAGS
、CS
、IP
。
中断处理程序是调试期提供的,任务是将断点处的处理器状态保存起来,包括标志寄存器、段寄存器以及通用寄存器的数值。同时中断处理过程会提供一个交互的界面,允许使用调试命令查看断点前的及机器状态。
在调试过程中接到继续执行的命令时,将恢复进入中断前指令的机器码,即将CC
改回为31
,然后修改栈中的返回地址,让此地址指向原来的那条指令即31FF
。
最后中断处理过程指向中断返回指令iret
,重新返回到被中断的那一条指令继续向下执行。
注意:int3 != int 3
。
溢出中断指令into
:
处理器执行这条into
指令时,若标志寄存器中的溢出标志OF
为1,产生4号中断,否则不做任何操作。
14、BIOS中断控制概述
外部硬件中断是否能够被处理:
- 要看中断控制器
8259A
是否允许中断信号到达处理器; - 即使中断信号到达处理器还要看标志寄存器中的中断标志位
IF
的状态; - 处理器响应中断的过程,包括向
8259A
发送中断响应信号; - 然后要求
8259A
芯片送来一个中断号。
内部中断、软中断不受IF
位的影响。
处理器最主要的就是根据中断号找到中断处理过程,并执行这个过程。
8086
系统中断可处理256
种中断:
内部中断2%
、外部硬件中断主要通过NMI
引脚和8259A
中断控制器芯片提供,占比7%
,剩余的部分可由软件系统使用,占比91%
。
过程调用时,call
指令必须指定目标位置的相对偏移量或绝对地址,在一个程序内部很方便,可以之间call 标号
即可;
但是要想调用别人的代码,比如操作系统的功能,call
指令就很麻烦。比如读取硬盘上的文件,因为操作系统有这样的功能,就不必自己造轮子,直接调用操作系统例程即可。但是操作系统不会给出例程的段地址和偏移地址,因为操作系统也是经常修改或者每次启动之后段地址和偏移地址都是变化的,导致例程的入口地址跟着变化。
因为有了软中断,操作系统可以使用软中断提供各种服务,每次操作系统加载完之后,将其提供的每一种服务功能做成中断处理过程,并指定以恶中断号,将服务例程地址填入中断向量表IVT
中。这样程序即可使用软中断调用操作系统提供的例程了,不需要知道具体的地址,只需要一个中断号即可。
BIOS(中断功能)调用
,在加载执行主引导扇区程序之前已经可以使用。BIOS
提供了很多硬件服务,按照类型划分,比如键盘输入、磁盘读写、文本和图形的显示服务。每一种服务都有一个中断号,键盘服务使用的中断号为0x16
,每一种服务又区分了很多功能,为了区分不同的功能,在发出软中断之前,使用ah
来指定功能号。如下:
mov ah, 0 ;0号功能表示从键盘读取字符存入寄存器AL中
int 0x16 ;键盘服务
;当中断服务例程返回之后,会监视键盘动作
;将读取的按键字符ASCII编码存入寄存器AL中
功能号可参考配套资料中的BIOS
功能调用表。
BIOS
功能调用是在BIOS
执行期间安装的,当主引导程序开始执行时即可使用,BIOS
是如何建立起这一套功能调用的呢?又是如何知道访问硬件的呢?起始BIOS
并不知道,很多功能调用并不是BIOS
提供的,而是由我们访问的硬件自己提供的,是由外部设备接口自己建立的。
在上图中,右侧是一些外部设备接口,入板卡、显卡、硬盘控制器等等,这些接口都有自己的ROM
,这些ROM
中提供了自己的功能调用例程,以及本设备的初始化代码。按照规范,在这些ROM
中前两个单元为55
和AA
两个字节,第三个单元是长度(以512
字节为单位的长度),从第4个单元一直响向后就是实际的功能调用例程的代码。
每一个外部设备的ROM
都映射到内存地址0xC0000~~0xE0000
中,如果设备存在则映射到分配给他的内存范围内。计算机启动期间会执行BIOS
,BIOS
程序会以2K字节
为单位搜索区域:0xC0000~~0xE0000
,即搜索的是外部设备的ROM
。若发现某个ROM
的前两个字节为55
和AA
,则表示此外社存在,BIOS
会从相应的单元进入,执行设备的ROM
代码。设备的ROM
代码用来初始化设备本身并提供功能调用,将自己的功能调用地址填写到中断向量表中。各自填写完成之后返回到ROM BIOS
,接着再搜索,再返回,直到所有的ROM
都初始化完成,包括中断向量表建立完成。之后再加载执行主引导扇区程序,ROM BIOS
就是这样的过程。
15、用BIOS功能调用接收并显示键盘输入
具体代码参考c09_2.asm
、c08_mbr.asm
。
显示字符使用BIOS
的0x10
中断,0x0E
号功能从键盘读取字符:
使用BIOS
的0x16
中断,0x00
号功能从键盘读取字符:
16、原书第9章习题
9.2题解:
- 设置断点:
b 0x7c00
c
命令持续执行到断点处,中断向量表就初始化完成xp /100xh 0
显示位于0地址
处的中断向量表
xp /2xh 0x70*4
,每一个中断4占据个字节,即乘以4;即可显示0x70处的偏移地址(低位)和段地址(高位)。
上一节:19、硬盘和显卡的访问与控制
下一节:21、32位x86处理器编程架构
版权声明:本文为CSDN博主「张登雨」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/God__Rain/article/details/121888229
暂无评论