《一个操作系统的实现》(三):4.中断和异常&5.保护模式下的I/O

前面讲到了实模式下用int 15h得到内存信息,然后在保护模式下把它们显示出来。保护模式下中断机制发生了很大的变化,原来的中断向量表被IDT(Interrupt Descriptor Table,中断描述符表)代替,实模式下能用的BIOS中断在保护模式下已经不能用了。IDT可以将每一个中断向量和一个描述符对应起来。IDT中的描述符可以是中断门描述符、陷阱门描述符、任务门描述符。尽管IDT在形式上与实模式下的向量表非常不同,但它也是一种向量表。

中断门和陷阱门的作用机理几乎一样,只不过使用调用门时用call指令,而在这里我们使用int指令。

每一种中断(异常)都会对应一个中断向量号,而这个向量号通过IDT就与相应的中断处理程序对应起来。保护模式的中断和异常见下表:

 

向量号 助记符 描述 类型 出错码
0 #DE 除法错 Fault DIV和IDIV指令
1 #DB 调试异常 Fault/Trap 任何代码和数据的访问
2 非屏蔽中断 Interrupt 非屏蔽外部中断
3 #BP 调试断点 Trap 指令INT 3
4 #OF 溢出 Trap 指令INTO
5 #BR 越界 Fault 指令BOUND
6 #UD 无效(未定义)操作码 Fault 指令UD2或无效指令
7 #NM 设备不可用(无数学协处理器) Fault 浮点或WAIT/FWAIT指令
8 #DF 双重错误 Abort 有(0) 所有能产生异常或NMI或INTR
的指令
9   协处理器段越界(保留) Fault 浮点指令(386后不再处理此
异常)
10 #TS 无效TSS Fault 任务切换或访问TSS时
11 #NP 段不存在 Fault 加载段寄存器或访问系统段时
12 #SS 堆栈段错误 Fault 堆栈操作或加载SS时
13 #GP 常规保护错误 Fault 内存或其他保护检验
14 #PF 页错误 Fault 内存访问
15 Intel保留,未使用      
16 #MF x87FPU浮点错(数学错) Fault x87FPU浮点指令或WAIT/FWAIT指令
17 #AC 对齐检验 Fault 有(0) 内存中的数据访问(486开始支持)
18 #MC Machine Check Abort 错误码(若有的话)和源依赖于
具体模式(奔腾CPU开始支持)
19 #XF SIMD浮点异常 Fault SSE和SSE2浮点指令(奔腾三
开始支持)
20~31 Inter保留,未使用      
32~255 用户定义中断 Interrupt   外部中断或int n指令

对于异常的三种类型解释如下:

 

Fault(错误)——可被更正的异常,更正后程序可继续执行。当一个fault发生时,处理器会把产生fault的指令之前的状态保存起来。异常处理程序的返回地址将会是产生fault的指令,而不是其后的那条指令。

Trap(陷阱)——一种在发生trap的指令执行后立即被报告的异常,它也允许程序或任务不失连续性地继续执行。异常处理程序的返回地址将会是产生trap的指令之后的那条指令。

About(终止)——不总是报告精确异常发生位置,它不允许程序或任务继续执行,而是用来报告严重错误。

中断产生的原因有两种:

外部中断(即由硬件产生的)——需要建立硬件中断与向量号之间的对应关系。分为可屏蔽中断和不可屏蔽中断两种。不可屏蔽中断由CPU的NMI引脚接收,可屏蔽中断则由CPU的INTR引脚接收。可屏蔽中断与CPU的关系是通过对可编程中断控制器8259A(可以认为是中断机制中所有外围设备的一个代理,这个代理不但可以根据优先级在同时发生中断的设备中选择应该处理的请求,而且可以通过对其寄存器的设置来屏蔽或打开相应的中断)建立起来的。

内部中断(由int n产生)——类似于调用门的使用,n即为向量号。

设置8259A:

有两片级联的8259A与CPU相连,每个8259A有8根中断信号线,两片级联公可以挂接15个不同的外设。对8259A进行设置,可以使这些外设发出的中断请求与中断向量对应起来。在BIOS初始化它的时候,IRQ0~IRQ7被设置为对应向量号08h~0Fh,但保护模式下这些向量号已被占用,所以需要重新设置。8259A是可编程中断控制器,对它的设置可通过向相应端口写入特定的ICW(Initialization Command Word)来实现。主8259A对应的端口地址是20h和21h,从8259A对应的端口地址是A0h和A1h。初始化过程(不能颠倒顺序):往端口20h或A0h写入ICW1->往端口21h或A1h写入ICW2->往端口21h或A1h写入ICW3->往端口21h或A1h写入ICW4(注:2、3、4的写入的端口相同)。

还有一个东西叫做OCW(Operation Control Word),共有OCW1、OCW2、OCW3共三个,用于屏蔽或打开外部中断(向21h或A1h写入OCW1即可,实际上OCW1被写入了中断屏蔽寄存器IMR中,当一个中断到达,IMR会判断此中断是否应被丢弃)或发送EOI给8259A以通知它中断处理结束。

对于EOA,每一次中断处理结束,需要发送一个EOI给8259A,以便继续接受中断。发送EOI通过向20h或A0h端口写OCW2实现。

可屏蔽中断与NMI的区别在于是否受到IF位的影响,而8259A的中断屏蔽寄存器(IMR)也影响着中断是否会被响应。所以外部可屏蔽中断受IF位(需为1)和IMR位(需为0)的影响。

实际应用中,中断的产生大多带有特权级变换,规则与用call指令调用一个调用门的规则完全一样。如果中断或异常发生时没有特权级变换,则eflags、cs、eip、将依次被压入堆栈,如果有出错码,则出错码最后被压栈。如果有特权级变换,ss和esp被压入内层堆栈,然后是eflags、cs、eip、出错码(若有的话)。总之不管怎样,中断或异常发生时,堆栈都会发生变化。

从中断或异常返回时必须使用指令iretd,与ret的区别就在于它同时改变eflags的值。需要注意的是,只有当CPL为0时,eflags中的IOPL域才会改变,而且只有当CPL小于等于IOPL时,IF才会改变。还要注意的是,iretd执行的时候Error Code不会被自动从堆栈中弹出,所以执行它之前要先将它从栈中清除掉。

还有一点,中断门和陷阱门有一个小区别。由中断门向量引起的中断会复位IF,因为可以避免其他中断干扰当前中断的处理,随后的iret指令会从堆栈上回复IF的原值;而通过陷阱门产生的中断不会改变IF。



下面的内容是保护模式下的I/O,内容很少~

I/O敏感指令:in、ins、out、outs、cli、sti,它们只有在CPL小于等于IOPL时才能执行,如果低特权级的指令试图访问这些I/O敏感指令将会导致常规保护错误(#GP)。

可以改变IOPL的指令只有popf和iretd,但只有运行在ring0的程序才能将其改变,低特权级的无法改变,但也不会产生异常(会维持原样)。指令popf也可用来改变IF(就像执行了cli和sti)。但在这种情况下,popf也变成了I/O敏感指令,只有CPL小于等于IOPL时,popf才能改变IF,否则保持原样(不会产生异常)。

I/O位图基址是一个以TSS的地址为基址的偏移,指向的便是I/O许可位图。它的每一位表示一个字节的端口地址是否可用(0为可用,1为不可用)。由于每个任务都可以有单独的TSS,所以每个任务可以有它单独的I/O许可位图。I/O许可位图必须以0FFh结尾。如果I/O位图基址大于或等于TSS段界限,则表示没有I/O许可位图,如果CPL小于等于IOPL,则所有I/O指令都会引起异常。I/O许可位图的使用使得即便在同一特权级下不同的任务也可以有不同的I/O访问权限。

 

posted @ 2013-05-11 19:33  xinyuyuanm  阅读(484)  评论(0编辑  收藏  举报