一、80386,内存,8259A的连接如图1
图 1
二、编程8259A中断控制器(将ICW写入特定的寄存器)
8259A是可编程中断控制器,对它的设置并不复杂,是通过向相对应的端口写入特定的ICW(Initialization Command Word)来实现的。主8259A对应的端口地址是20h和21h,从8259A对应的端口地址是A0h和A1h。ICW共有4个,每一个都是具有特定格式的字节,为了先对初始化8259A的过程有一个概括的了解,我们过一会再来关注每一个ICW的格式,现在,先来看一下初始化过程:
1、往端口20h(主片)或A0h(从片)写入ICW1。
2、往端口21h(主片)或A1h(从片)写入ICW2。
3、往端口21h(主片)或A1h(从片)写入ICW3。
4、往端口21h(主片)或A1h(从片)写入ICW4。
ICW格式如图2:
图 2 ICW的格式 图 3 OCW1
若想屏蔽或者打开外部中断,只需要往8259A写入OCW1就可以了,即写入中断屏蔽寄存器(IMR),OCW1的格式如图3。
可屏蔽中断与NMI的区别在于是否受到IF位的影响,而8259A的中断屏蔽寄存器(IMR)也影响着中断是否会被响应。所以,外部可屏蔽中断的发生就会受到两个因素的影响,只有当IF位为1,并且IMR相应位为0时才会发生。除单步中断外,所有内部中断都无法禁止,即不能通过CLI指令使IF=0,使其不响应。
三、保护模式下的中断
1、0-19的中断和异常已经安排好了,如书上89页表3.8
2、有特权级变化时,中断堆栈变化如图4。从中断或异常返回时必须使用指令iretd,它和ret很相似,只是它同时会改变eflags的值。需要注意的是,只有当CPL为0时,eflags中的IOPL域才会改变,而且只有当CPL小于等于IOPL时,IF才会被改变。另外,iretd执行时Error Code不会被自动从堆栈弹出,所以执行它之前要先将它从栈中清除掉。指令in、ins、out、outs、cli、sti只有在CPL小于等于IOPL时才能执行。这些指令被称为I/O敏感指令。如果低特权级的指令试图访问这些I/O敏感指令将会导致常规保护错误(#GP)。
图 4 中断或异常发生时的堆栈
3、I/O许可位
TSS偏移102字节处有一个被称做"I/O许可位图基址"的东西,它是一个以TSS的地址为首地址的偏移,指向的便是I/O许可位图。之所以叫做位图,是因为它的每一位表示一个字节的端口地址是否可用。如果某一位为0,则表示此位对应的端口号可用,为1则不可用。由于每一个任务都可以有单独的TSS,所以每个任务可以有它单独的I/O许可位图。
4、保护模式下中断执行过程
以下由硬件自动完成:
①从中断信息中取得中断类型码。
②标志寄存器eflags的值入栈,因为在中断过程中要改变标志寄存器的值,所以先将其保存在栈中。
③设置标志寄存器IF和TF位为0,为了防止在此中断中再来其他外部中断。
④cs,eip,error code入栈。
⑤根据中断类型码在IDT中找到中断门执行代码。
IRETD 指令先弹出一个32位的EIP值,然后再弹出一个32位值并将最低的2个字节值传入CS寄存器,最后再弹出一个32位的标志寄存器值。
pmtest9.asm代码如下:
%include "pm.inc" ; 常量, 宏, 以及一些说明 org 07c00h jmp LABEL_BEGIN [SECTION .gdt] ; GDT ; 段基址, 段界限 , 属性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_NORMAL: Descriptor 0, 0ffffh, DA_DRW ; Normal 描述符 LABEL_DESC_CODE32: Descriptor 0, SegCode32Len - 1, DA_C + DA_32; 非一致代码段 LABEL_DESC_CODE16: Descriptor 0, 0ffffh, DA_C ; 非一致代码段, 16 LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW ; 显存首地址 ; GDT 结束 GdtLen equ $ - LABEL_GDT ; GDT长度 GdtPtr dw GdtLen - 1 ; GDT界限 dd 0 ; GDT基地址 ; GDT 选择子 SelectorNormal equ LABEL_DESC_NORMAL - LABEL_GDT SelectorCode32 equ LABEL_DESC_CODE32 - LABEL_GDT SelectorCode16 equ LABEL_DESC_CODE16 - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT ; END of [SECTION .gdt] [SECTION .data1] ; 数据段 ALIGN 32 [BITS 32] LABEL_DATA: ; 实模式下使用这些符号 ; 变量 _wSPValueInRealMode dw 0 _SavedIDTR: dd 0 ; 用于保存 IDTR dd 0 _SavedIMREG: db 0 ; 中断屏蔽寄存器值 DataLen equ $ - LABEL_DATA ; END of [SECTION .data1] ; IDT [SECTION .idt] ALIGN 32 [BITS 32] LABEL_IDT:;最多256个中断门,每个占8个字节,所以IDT最大占2K。前面20个已被占用。 ; 门 目标选择子, 偏移, DCount, 属性 %rep 32 Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate %endrep .020h: Gate SelectorCode32, ClockHandler, 0, DA_386IGate ;转换成10进制是32 %rep 95 Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate %endrep .080h: Gate SelectorCode32, UserIntHandler, 0, DA_386IGate ;转换为10进制是128 IdtLen equ $ - LABEL_IDT IdtPtr dw IdtLen - 1 ; 段界限 dd 0 ; 基地址 ; END of [SECTION .idt] [SECTION .s16] [BITS 16] LABEL_BEGIN: mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, 0100h mov [LABEL_GO_BACK_TO_REAL+3], ax mov [_wSPValueInRealMode], sp ; 初始化 16 位代码段描述符 mov ax, cs movzx eax, ax shl eax, 4 add eax, LABEL_SEG_CODE16 mov word [LABEL_DESC_CODE16 + 2], ax ;ds:LABEL_DESC_CODE16 shr eax, 16 mov byte [LABEL_DESC_CODE16 + 4], al mov byte [LABEL_DESC_CODE16 + 7], ah ; 初始化 32 位代码段描述符 xor eax, eax mov ax, cs shl eax, 4 add eax, LABEL_SEG_CODE32 mov word [LABEL_DESC_CODE32 + 2], ax shr eax, 16 mov byte [LABEL_DESC_CODE32 + 4], al mov byte [LABEL_DESC_CODE32 + 7], ah ; 为加载 GDTR 作准备 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_GDT ; eax <- gdt 基地址 mov dword [GdtPtr + 2], eax ; [GdtPtr + 2] <- gdt 基地址 ; 为加载 IDTR 作准备 xor eax, eax mov ax, ds shl eax, 4 add eax, LABEL_IDT ; eax <- idt 基地址 mov dword [IdtPtr + 2], eax ; [IdtPtr + 2] <- idt 基地址 ; 保存 IDTR sidt [_SavedIDTR] ;保存IDTR寄存器的值到ds:_SavedIDTR ; 保存中断屏蔽寄存器(IMREG)值 in al, 21h mov [_SavedIMREG], al ; 加载 GDTR lgdt [GdtPtr] ; 关中断 cli ;int 80h可以起作用,但是可屏蔽中断(通过8255设置的)就不能响应了 IF=0 ; 加载 IDTR lidt [IdtPtr] ; 打开地址线A20 in al, 92h or al, 00000010b out 92h, al ; 准备切换到保护模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正进入保护模式 jmp dword SelectorCode32:0 ; 执行这一句会把 SelectorCode32 装入 cs, ; 并跳转到 Code32Selector:0 处 LABEL_REAL_ENTRY: ; 从保护模式跳回到实模式就到了这里 mov ax, cs mov ds, ax mov es, ax mov ss, ax mov sp, [_wSPValueInRealMode] lidt [_SavedIDTR] ; 恢复 IDTR 的原值 mov al, [_SavedIMREG] ; ┓恢复中断屏蔽寄存器(IMREG)的原值 out 21h, al ; ┛ in al, 92h ; ┓ and al, 11111101b ; ┣ 关闭 A20 地址线 out 92h, al ; ┛ sti ; 开中断 mov ax, 4c00h ; ┓ int 21h ; ┛回到 DOS ; END of [SECTION .s16] [SECTION .s32]; 32 位代码段. 由实模式跳入. [BITS 32] LABEL_SEG_CODE32: mov ax, SelectorVideo mov gs, ax ; 视频段选择子(目的) call Init8259A int 080h ;内中断,类似调用门 sti ;开中断后,时钟中断开始生效 IF=1 jmp $ ;为了演示结果,去掉后跳回实模式 call SetRealmode8259A ; 到此停止 jmp SelectorCode16:0 ; Init8259A --------------------------------------------------------------------------------------------- Init8259A: mov al, 011hn ;00010001 out 020h, al ; 主8259, ICW1. call io_delay out 0A0h, al ; 从8259, ICW1. call io_delay mov al, 020h ; IRQ0 对应中断向量 0x20 00100000 因为前0-19号中断已经被占了 out 021h, al ; 主8259, ICW2. call io_delay mov al, 028h ; IRQ8 对应中断向量 0x28 00101000 out 0A1h, al ; 从8259, ICW2. call io_delay mov al, 004h ; IR2 对应从8259 00000100 out 021h, al ; 主8259, ICW3. call io_delay mov al, 002h ; 对应主8259的 IR2 0000 0010 out 0A1h, al ; 从8259, ICW3. call io_delay mov al, 001h 0000 0001 out 021h, al ; 主8259, ICW4. call io_delay out 0A1h, al ; 从8259, ICW4. call io_delay ;mov al, 11111111b ; 屏蔽主8259所有中断 mov al, 11111110b ; 仅仅开启定时器中断,外部可屏蔽中断只有在IF=1和IMR对应位为0时才响应 out 021h, al ; 主8259, OCW1. call io_delay mov al, 11111111b ; 屏蔽从8259所有中断 out 0A1h, al ; 从8259, OCW1. call io_delay ret ; Init8259A --------------------------------------------------------------------------------------------- ; SetRealmode8259A --------------------------------------------------------------------------------------------- SetRealmode8259A: mov al, 017h 0001 0111 out 020h, al ; 主8259, ICW1. call io_delay ;4个字节中断向量,单个8259 mov al, 008h ; IRQ0 对应中断向量 0x8 out 021h, al ; 主8259, ICW2. call io_delay ;ICW3已经不需要了 mov al, 001h out 021h, al ; 主8259, ICW4. call io_delay ret ; SetRealmode8259A --------------------------------------------------------------------------------------------- io_delay: nop nop nop nop ret ; int handler --------------------------------------------------------------- _ClockHandler: ClockHandler equ _ClockHandler - $$ inc byte [gs:((80 * 0 + 70) * 2)] ; 屏幕第 0 行, 第 70 列。 mov al, 20h out 20h, al ; 发送 EOI iretd ;IRETD 指令先弹出一个32位的EIP值,然后再弹出一个32位值并将最低的2个字节值传入CS寄存器, ;最后再弹出一个32位的标志寄存器值 _UserIntHandler: UserIntHandler equ _UserIntHandler - $$ mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov al, 'I' mov [gs:((80 * 0 + 70) * 2)], ax ; 屏幕第 0 行, 第 70 列。 iretd _SpuriousHandler: SpuriousHandler equ _SpuriousHandler - $$ mov ah, 0Ch ; 0000: 黑底 1100: 红字 mov al, '!' mov [gs:((80 * 0 + 75) * 2)], ax ; 屏幕第 0 行, 第 75 列。 jmp $ iretd ; --------------------------------------------------------------------------- SegCode32Len equ $ - LABEL_SEG_CODE32 ; END of [SECTION .s32] ; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式 [SECTION .s16code] ALIGN 32 [BITS 16] LABEL_SEG_CODE16: ; 跳回实模式: mov ax, SelectorNormal mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov eax, cr0 and al, 11111110b mov cr0, eax LABEL_GO_BACK_TO_REAL: jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值 Code16Len equ $ - LABEL_SEG_CODE16 ; END of [SECTION .s16code]