OS_lab——中断与异常

  1. 理解中断与异常的机制
  2. 调试 8259A 的编程基本例程
  3. 调试时钟中断例程
  4. 实现一个自定义的中断向量,功能可自由设想。

中断和异常

中断和异常都是程序执行过程中的强制性转移,转移到相应的处理程序。

中断通常因为硬件而发生,用来处理处理器外部的事件, 比如外围设备的请求。

异常通常在处理器执行指令过程中检测到错误时发生,比如除零, 页错误等

什么是中断

在实模式下,使用的是 BIOS 中断。BIOS 中断是由系统提供的使用 BIOS 的一组功能。中断存在中断向量表中。

在保护模式下, BIOS 中断不能使用。原来的中断向量表被 IDT(中断描 述符表) 代替。

IDT

IDT 中的描述符有三种

•       中断门描述符(int 调用)

•       陷阱门描述符(call 调用)

•       任务门描述符(不常用, Linux 根本没用)

IDT 的作用是将每一个中断向量和一个描述符对应起来。

中断产生有两种情况

•        硬件产生的外部中断

•        intn

intn 类型调用使用 IDT 寻找中断处理程序,类似调用门的使用

外部中断: 要建立硬件中断和中断向量号之间的对应关系

•      不可屏蔽中断(NMI 引脚接收,于 IF 是否被设置无关,对应中断向 量号为 2)

•      可屏蔽中断(INTR 接收,中断和向量号的关系由可编程中断控制器 8259 A 建立)

什么是异常

异常的三种类型

•       Fault:可更正的异常, 处理完返回到原指令

•       Trap :原指令执行后报告,处理完返回到之后的指令
•       Abort :确认不了精确位置,不能继续运行
调试 8259 A
8259 A 工作原理

        功能

        •      根据优先级在同时发生的中断设备中选择应该处理的请求

        •      通过对其寄存器的设置来屏蔽或打开相应中断 结构和工作原理

        结构:两片级联的 8259A 与 CPU 相连,共挂十五个外设。

        工作:

        主 8259A 对应的端口地址是 20 h 和 21 h

        从 8259A 对应的端口地址是 A0 h  和 A1 h

        设置 8259A:通过向相应的端口写入特定的 ICW

        •       往端口 20 h (主片)或 A0 h (从片)写入 ICW 1
        •       往端口 21 h (主片)或 A1 h (从片)写入 ICW 2
        •       往端口 21 h (主片)或 A1 h (从片)写入 ICW 3
        •       往端口 21 h (主片)或 A1 h (从片)写入 ICW 4

 

ICW  格式

分析

ICW 1  触发模式,选择中断向量种类,选择 8259A 架构

ICW 2 决定对应哪个中断向量

ICW 3 决定了传进哪个外设中断

ICW 4  正常为 01 h

写入 ICW 2 时, IRQ 0~ IRQ 7 对应 20 h ~ 27 h;同理 IRQ 8 ~ IRQ 15 对应 28 h ~ 2 F h

io_delay 函数:延迟函数,等待操作完成

314 ~ 322 屏蔽了所有的外部中断,此时写入的是 OCW

使用 OCW 的两种情况

•      屏蔽或打开外部中断

•       发送 EOI 给 8259A 通知它中断处理结束

屏蔽或打开外部中断,只需往 8259A 写入 OCW 1 即可

OCW 1 被写入中断屏蔽寄存器(IMR)

发送 EOI 给 8259A 通过往 20 h  或 A0 h 写 OCW 2 来实现的

mov  al     ,  20h

out 20h/A0h, al

如何建立IDT,如何实现一个自定义的中断

建立 IDT

IDT 段

/*

255 个描述符完全相同

SpuriousHandler : 在屏幕右上角打印红色"!",然后死循环

*/

// SpuriousHandler 实现

// 加载IDTR

实现一个中断

/*

修改IDT  ---> 80h 中断单独列出来 ---> 新增一个函数来处理中断 UserIntHandler  在末尾iretd 指令返回

*/

新增一个函数

UserIntHandler 实现

3.  时钟中断例程

pmtest9.asm 部分代码展示

时钟中断处理程序

_ClockHandler:

ClockHandler   equ _ClockHandler - $$

inc byte [gs:((80 * 0 + 70) * 2)]  ; 屏幕第 0 行, 第 70 列。

mov al, 20h

out 20h, al            ; 发送 EOI

iretd

打开时钟中断

;mov   al, 11111111b   ; 屏蔽主 8259 所有中断

mov al, 11111110b   ; 仅仅开启定时器中断

out 021h, al   ; 主 8259, OCW1.

call   io_delay



mov al, 11111111b   ; 屏蔽从 8259 所有中断

out 0A1h, al   ; 从 8259, OCW1.

call   io_delay

IDT 表的修改

LABEL_IDT:

; 门                        目标选择子,          偏移, DCount, 属性

%rep 32

Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate

%endrep

.020h: Gate SelectorCode32, ClockHandler, 0, DA_386IGate

%rep 95

Gate SelectorCode32, SpuriousHandler, 0, DA_386IGate

%endrep

.080h: Gate SelectorCode32, UserIntHandler, 0, DA_386IGate

IdtLen equ $ - LABEL_IDT

IdtPtr dw IdtLen - 1 ; 段界限

dd 0 ; 基地址



; END of [SECTION .idt]

开中断

call  Init8259A



int 080h

sti

jmp $

代码调试调试

在时钟中断处理程序 ClockHandler 中插入 Magic Break:

_ClockHandler:

ClockHandler   equ _ClockHandler - $$

xchg bx, bx

inc byte [gs:((80 * 0 + 70) * 2)]  ; 屏幕第 0 行, 第 70 列。

mov al, 20h

out 20h, al            ; 发送 EOI

iretd

调试截图展示如下:

尽管没有使用 int 指令直接跳转,因为外中断的特性,程序进入了设置好的 02h 中断,将 int 80h 设置的字符进行变化。  实验结果展示如下:

4.   自定义的中断向量

自定义中断功能

int 80h 中断

功能说明:初始化时间字符串"20yy/mm/dd hh:mm:ss"为当前时间, 并输出在屏幕上

int 20h 中断(时间中断)

功能说明:将屏幕上已输出的时间字符串更新为当前时间

部分代码展示

int 80h 中断

LABEL_DATA:
_szTime               db  "20yy/mm/dd hh:mm:ss", 0
szTime         equ _szTime            - $$
DataLen        equ $ - LABEL_DATA
; END of [SECTION .data1]
...

_UserIntHandler:
UserIntHandler equ _UserIntHandler - $$
    xor eax, eax

    xor ebx, ebx 
    xor esi, esi 
    xor edi, edi 
    mov esi, szTime ; 源数据偏移

    mov al,  9         ; 年
    out 70h, al
    in  al,  71h
    mov bl,  al        ; 十位
    shr bl, 4
    and al,  0fh       ; 个位
    mov ah,  bl
    add ax, 03030h    ; 转换为 ascii 码
    mov [esi + 2], ah  ; 赋值
    mov [esi + 3], al

    mov al,  8         ; 月
    out 70h, al
    in  al,  71h
    mov bl,  al        ; 十位
    shr bl, 4
    and al,  0fh       ; 个位
    mov ah,  bl
    add ax, 03030h    ; 转换为 ascii 码
    mov [esi + 5], ah  ; 赋值
    mov [esi + 6], al

    mov al,  7         ; 日
    out 70h, al
    in  al,  71h
    mov bl,  al        ; 十位
    shr bl, 4
    and al,  0fh       ; 个位
    mov ah,  bl
    add ax, 03030h    ; 转换为 ascii 码
    mov [esi + 8], ah  ; 赋值
    mov [esi + 9], al

    mov al, 4 ; 时 
    out 70h, al 
    in al, 71h 
    mov bl, al ; 十位 
    shr bl, 4 
    and al, 0fh ; 个位

    mov ah,  bl
    add ax, 03030h    ; 转换为 ascii 码
    mov [esi + 11], ah  ; 赋值
    mov [esi + 12], al

    mov al, 2         ; 分
    out 70h, al
    in  al,  71h
    mov bl,  al        ; 十位
    shr bl, 4
    and al,  0fh       ; 个位
    mov ah,  bl
    add ax, 03030h    ; 转换为 ascii 码
    mov [esi + 14], ah  ; 赋值
    mov [esi + 15], al

    mov al,  0         ; 秒
    out 70h, al
    in  al,  71h
    mov bl,  al        ; 十位
    shr bl, 4
    and al,  0fh       ; 个位
    mov ah,  bl
    add ax, 03030h    ; 转换为 ascii 码
    mov [esi + 17], ah  ; 赋值
    mov [esi + 18], al

    ; 下面显示一个字符串
    mov ah, 0Ch                ; 0000: 黑底    1100: 红字
    mov edi, (80 * 8 + 0) * 2  ; 目的数据偏移。屏幕第 8 行, 第 0 列。
    cld
.1:
    lodsb
    test   al, al
    jz     .2
    mov     [gs:edi], ax
    add    edi, 2
    jmp    .1
.2: ; 显示完毕

    iretd

int 20h 时间中断

_ClockHandler:

ClockHandler   equ _ClockHandler - $$

    xor eax, eax
    xor ebx, ebx



    mov al,  9                    ; 年
    out 70h, al
    in  al,  71h
    mov bl,  al                   ; 十位
    shr bl, 4
    and al,  0fh                   ; 个位
    mov ah,  bl
    add ax, 03030h                ; 转换为 ascii 码
    mov [gs:((80 * 8 + 2) * 2)], ah ; 更改屏幕显示
    mov [gs:((80 * 8 + 3) * 2)], al

    mov al,  8                    ; 月
    out 70h, al
    in  al,  71h
    mov bl,  al                   ; 十位
    shr bl, 4
    and al,  0fh                   ; 个位
    mov ah,  bl
    add ax, 03030h                ; 转换为 ascii 码
    mov [gs:((80 * 8 + 5) * 2)], ah ; 更改屏幕显示
    mov [gs:((80 * 8 + 6) * 2)], al

    mov al,  7                    ; 日
    out 70h, al
    in  al,  71h
    mov bl,  al                   ; 十位
    shr bl, 4
    and al,  0fh                   ; 个位
    mov ah,  bl
    add ax, 03030h                ; 转换为 ascii 码
    mov [gs:((80 * 8 + 8) * 2)], ah ; 更改屏幕显示
    mov [gs:((80 * 8 + 9) * 2)], al

    mov al,  4                     ; 时
    out 70h, al
    in  al,  71h
    mov bl,  al                   ; 十位
    shr bl, 4
    and al,  0fh                   ; 个位
    mov ah,  bl
    add ax, 03030h                ; 转换为 ascii 码
    mov [gs:((80 * 8 + 11) * 2)], ah; 更改屏幕显示
    mov [gs:((80 * 8 + 12) * 2)], al

    mov al, 2                    ; 分
    out 70h, al
    in  al,  71h
    mov bl,  al                   ; 十位
    shr bl, 4
    and al,  0fh                   ; 个位
    mov ah,  bl
    add ax, 03030h                ; 转换为 ascii 码
    mov [gs:((80 * 8 + 14) * 2)], ah; 更改屏幕显示
    mov [gs:((80 * 8 + 15) * 2)], al

    mov al,  0                    ; 秒
    out 70h, al
    in  al,  71h
    mov bl,  al                   ; 十位
    shr bl, 4
    and al,  0fh                   ; 个位
    mov ah,  bl
    add ax, 03030h                ; 转换为 ascii 码
    mov [gs:((80 * 8 + 17) * 2)], ah; 更改屏幕显示
    mov [gs:((80 * 8 + 18) * 2)], al

    mov al, 20h
    out 20h, al                   ; 发送 EOI

    iretd

中断调用

; 下面显示一个字符串
push   szPMMessage
call   DispStr
add    esp, 4

; 调用中断向量
int    080h
sti
jmp    $

实验结果展示

成功输出当前时间, 且不断进行更新。

=========================================================================

实验结果总结
  1. 什么是中断,什么是异常
  2. 8259A 的工作原理是怎样的?怎么给这些中断号的处理向量初始化值?
  3. 如何建立 IDT,如何实现一个自定义的中断
  4. 如何控制时钟中断,为什么时钟中断时候,没有看到 int 的指令?
  5. 简要解释一下 IOPL 的作用与基本机理

1.   什么是中断,什么是异常?

中断包括外部中断和来自于指令 int n 的中断(n 即向量号),其中外部中断又分为不可 屏蔽中断(NMI 引脚接收,于 IF 是否被设置无关,对应中断向量号为 2)和可屏蔽中断(INTR 接收, 中断和向量号的关系由可编程中断控制器 8259A  建立)。中断是由硬件设备产生的, 从物理上说就是电信号, 它们通过中断控制器(8259A)发送给 CPU,接着 CPU 判断收到的 中断来自于哪个硬件设备(定义在内核中),最后由 CPU 发送给内核, 由内核处理中断。

异常包括 Fault 、Trap 和 Abort,分别对应出错、陷入和编程异常。出错保存的 EIP 指向 触发异常的那条指令,也就是说当从异常返回时, 出错会重新执行那条指令,如缺页异常就 是是一种出错, 所以当缺页异常处理完成之后,还会去尝试重新执行那条触发异常的指令;  陷入保存的 EIP 指向触发异常的那条指令的下一条指令,陷入不会重新执行因为异常的指令; 可编程中断可由编程者用 int  指令来触发,和陷入类似,从此类异常返回时也是返回到触发 异常的下一条指令。

中断和异常的相同点在于: 最后都是由 CPU 发送给内核, 由内核去处理;处理程序的流 程设计上是相似的。两者的不同点在于: 产生源不相同, 异常是由 CPU 产生的, 而中断是由 硬件设备产生的; 中断是异步的,这意味着中断可能随时到来; 而异常是 CPU 产生的, 所以是时钟同步的; 当处理中断时, 处于中断上下文中;处理异常时, 处于进程上下文中。 

2.   8259A 的工作原理是怎样的?怎么给这些中断号的处理向量初始化值?

8259A 的结构如下图所示, 它的功能是根据优先级在同时发生的中断设备中选择应该处 理的请求,并通过对其寄存器的设置来屏蔽或打开相应中断。

通过初始化编程向 8259A 写入相应的初始化命令 ICW,可以使芯片处于一个规定的基本 工作方式, 并在此方式下进行工作。8259A 的初始化命令字共有 4 个 ICW1-ICW4,进行初始 化时要求 ICW1-ICW4  按如下图所示的顺序写入。其中, ICW1 触发模式,选择中断向量种 类,选择 8259A 架构;ICW2 决定对应哪个中断向量;ICW3 决定了传进哪个外设中断; ICW4 正常为 01h。

当 8259A 开始工作时,一个外部中断请求信号会首先通过中断请求线 IRQ 传输到 IMR (中断屏蔽寄存器),IMR 根据所设定的 OCW1,决定是将其丢弃还是接受。使用 OCW 的两 种情况,一种是用于屏蔽或打开外部中断,另一种发送 EOI 给 8259A 通知它中断处理结束, 这里使用的是前者。

如果可以接受,则 8259A 将 IRR(中断请求暂存寄存器)中代表此 IRQ 的位置 1,以表 示此 IRQ 有中断请求信号,并同时向 CPU 的 INTR(中断请求)管脚发送一个信号,在等待 CPU 转到中断服务的过程中可能有其余的 IRQ 线送来中断请求,这些请求都会接受 IMR 的 挑选, 如果没有被屏蔽, 那么这些请求也会被放到 IRR  中,也即 IRR 中代表它们的 IRQ 的 相应位会被置 1。当 CPU 执行完一条指令时后, 会检查一下 INTR 管脚是否有信号,如果发 现有信号, 就会转到中断服务, 此时,CPU 会立即向 8259A 芯片的 INTA(中断应答) 管脚 发送一个信号。当芯片收到此信号后,判优部件开始工作,它在 IRR 中,挑选优先级最高的 中断, 将中断请求送到 ISR(中断服务寄存器),也即将 ISR 中代表此 IRQ 的位置位,并将 IRR 中相应位置 0,表明此中断正在接受 CPU 的处理。同时,将它的编号写入中断向量寄存 器 IVR 的低三位(IVR 是由 ICW2 所指定的)。这时,CPU 还会送来第二个 INTA 信号,当 收到此信号后, 芯片将 IVR 中的内容, 也就是此中断的中断号送上通向 CPU 的数据线。

3.   如何建立 IDT,如何实现一个自定义的中断?

1)将 IDT 放入一个单独的段中, 定义了 IDT 所对应的中断向量的偏移。

2)定义偏移定义

上图展现的是时钟中断处理程序。其功能为在时钟中断到来时,将[gs:((80 * 0 + 70)*2]位 置的字符的值增 1。

3)调用中断, 开中断

4.   如何控制时钟中断, 为什么时钟中断的时候没有看到 int 指令?

控制时钟中断的方法就是在 IDT 表中的中断向量号单独写出来,对应一个中断处理的函 数,在函数里定义时钟中断的处理过程, 比如此例中就单独定义了 020h 中断和 080h 中断:

没有看到 int 的原因是我们在 289 行进行了开中断,时钟中断是外部中断, 每次中断都 会通过 IRQ 中断请求线来请求中断, 不需要 int 调用。

5.   简要解释一下 IOPL 的作用与基本机理

(1)IOPL 的作用

保护模式通过 IOPL 和 I/O 许可位图来限制用户进程进行的 I/O 操作。 IOPL 是 I/O 保护机制的 关键之一,位于寄存器 eflags 的第 12 、13 位。指令 in 、ins 、out 、outs 、cli 、sti 只有在 CPL  <= IOPL 时才能执行。这些指令被称为 I/O 敏感指令(I/O Sensitive Instructions)。如果低特权 级的指令试图访问这些 I/O 敏感指令将会导致常规保护错误(#GP)。

(2)IOPL 的运行机理

I/O 位图基址是一个以 TSS 的地址为基址的偏移, 指向的便是 I/O 许可位图。之所以叫做位 图,是因为它的每一位表示一个字节的端口地址是否可用。如果某一位为 0,则表示此位对 应的端口号可用,为 1 则不可用。由于每一个任务都可以有单独的 TSS,所以每一个任务可 以有它单独的 I/O 许可位图。

以教材例子演示:

由于 I/O 许可位图开始有 12 字节内容为 0FFh,即有 12×8=96 位被置为 1,所以从端口 00h 到 5Fh 共 96 个端口地址对此任务不可用。同理,接下来的 1 字节只有第 1 位(从 0 开始数) 是 0,表示这一位对应的端口(61h)可用。如果 I/O 位图基址大于或等于 TSS 段界限,就表示没有 I/O 许可位图, 如果 CPLIOPL,则所有 I/O 指令都会引起异常。 I/O 许可位 图的使用使得即便在同一特权级下不同的任务也可以有不同的 I/O 访问权限。

猜你喜欢

转载自blog.csdn.net/weixin_49816179/article/details/135456856