操作系统之中断(四)

来源:互联网 发布:剑侠情缘2白金版 mac 编辑:程序博客网 时间:2024/05/20 02:26

上一篇文章中简单的提到了IDT是记录了终端号和中断函数之间的关系,实际上在保护模式下,IDT在中断中的地位举足轻重。


一、实模式切换到保护模式

IDT是在保护模式中出现的,计算机启动后运行在实模式下,所以需要将CPU从实模式切换到保护模式。切换这一步大约位于BIOS加载完bootsector后,执行部分或者执行完bootsector代码时进行的,这么靠前是因为如果不切换就无法进入32位模式,寻址空间还是可怜的1MB。
切换的步骤一般为以下几步:

  • 关闭PIC中断,CPU中断;
  • 使能A20地址线;
  • 设置GDT;
  • 进行一步长跳转,进入保护模式。

二、中断与IDT

在实模式下,中断处理函数注册在中断向量表(IVT)中,切换到保护模式后,中断函数注册在IDT中,IDT是一个8字节的描述符数组,共包含256项,具体的IDT表项的结构前面的一篇里说过了。
设置好IDT后,将首地址装载到idtr寄存器中,剩下的就可以注册使用的中断函数了。
IDT可以位于内存的任意位置,CPU通过IDT寄存器(IDTR)的内容来寻址IDT的起始地址。指令LIDT和SIDT用来操作IDTR。两条指令都有一个显示的操作数:一个6字节表示的内存地址。指令的含义如下:

  • LIDT(Load IDT Register)指令:使用一个包含线性地址基址和界限的内存操作数来加载IDT。操作系统创建IDT时需要执行它来设定IDT的起始地址。这条指令只能在特权级0执行。
  • SIDT(Store IDT Register)指令:拷贝IDTR的基址和界限部分到一个内存地址。这条指令可以在任意特权级执行。

IDT和IDTR寄存器的结构和关系如下图所示:
这里写图片描述

三、中断

操作系统需要对计算机系统中的外设进行管理,而管理就需要和外设进行通信。CPU的处理速度比外设快很多,如果是通过CPU主动“询问”(即轮询(polling)机制)外设是否产生事件则会浪费许多时间。所以提供外设可以主动通知CPU和操作系统的机制非常必要,也就是中断机制。
80386共支持256种中断,其中异常(包括故障(Fault)和陷阱(Trap))由CPU自身产生,不使用中断控制器,也不能被屏蔽。硬件中断又分为可屏蔽中断(INTR)和非屏蔽中断(NMI),I/O设备产生的中断请求(IRQ)引起可屏蔽中断,而紧急的外设事件(如掉电故障)引起的中断事件引起非屏蔽中断。
非屏蔽中断和异常的编号是固定的,而屏蔽中断的编号可以通过对中断控制器的编程来调整。256个中断的分配如下:

  • 0~31号的中断对应于故障、陷阱和非屏蔽外设中断,(见文章最后附录A)。
  • 32~47号的中断分配给可屏蔽硬件中断。
  • 48~255号的中断可以用软件来设置。比如用中断号来实现系统调用。

四、PIC

PIC,即Programmable Interrupt Controller,可编程中断控制器。硬件中断由它实现。
80386通过两片中断控制器8259A来响应15个外中断源,每个8259A可管理8个中断源。第一级(称主片,master)的第二个中断请求输入端,与第二级8259A(称从片,salve)的中断输出端INT相连,如下图所示。IRQ号和中断号之间的映射关系可以通过中断控制器来调整。

这里写图片描述

保护模式下可以设定 PIC 产生的中断对应的 ISR 所在 IDT 中的 offset,通常设置为从 0x20 开始,到 0x2F 结束(0x0 到 0x1F 被异常占用)。

PIC 的端口号如下表:

PIC IO Port Master Command 0x20 Master Data 0x21 Slave Command 0xA0 Slave Data 0xA1

在中断产生过程中,中断控制器8259A监视外设产生的中断请求(IRQ)信号,如果外设产生了一个中断请求信号,则8259A执行如下操作:

  • 把接受到的IRQ信号转换成一个对应的中断编号;
  • 把这个中断编号值存放在中断控制器的一个I/O地址单元中,CPU通过数据/地址总线可访问到此I/O地址单元;
  • 给CPU的INTR引脚触发信号,即发出一个中断;

屏蔽外部I/O请求有两种方法。一种是从CPU的角度清零CPU的EFLAG的中断标志位(IF);另一种是从中断控制器的角度,即通过把中断控制器中的中断屏蔽寄存器(IMR)相应位置1,则表示禁用某条中断线。

五、Spurious IRQs

由于 CPU 与 PIC 之间的竞争条件可能会产生 IRQ 7(Master 产生) 和 IRQ 15(Slave 产生) 的 Spurious IRQs。为了处理这种情况,我们要知道什么时候是无效的 IRQ,通过判断 IRR(Interrupt Request Register) 寄存器的值可以获知哪些 IRQ 发生了,这个寄存器的每个 bit 表示相应的 IRQ 是否发生。在 IRQ 7 和 IRQ 15 的 ISR 中先读取 IRR,然后判断对应的 bit 位是否被设置,如果没有设置,那么表示当前是一个 Spurious IRQ,不需要处理,也不需要写入 EOI,直接返回即可(如果是 Slave PIC 产生的,需要往 Master PIC 写入 EOI,由于 Master 不知道 Slave 产生的 IRQ 是不是 Spurious 的)。

六、门描述符(Gate Descriptors)

在保护模式下,中断门描述符表(IDT)中的每个表项由8个字节组成,其中的每个表项叫做一个门描述符(Gate Descriptor), “门”的含义是指当中断发生时必须先访问这些“门”,能够“开门”(即将要进行的处理需通过特权检查,符合设定的权限等约束)后,然后才能进入相应的处理程序。而门描述符则描述了“门”的属性(如特权级、段内偏移量等)。在IDT中,可以包含如下3种类型的系统段描述符:

  • 中断门描述符(Interrupt-gate descriptor): 用于中断处理,其类型码为110,中断门包含了一个外设中断或故障中断的处理程序所在段的选择子和段内偏移量。当控制权通过中断门进入中断处理程序时,处理器清IF标志,即关中断,以避免嵌套中断的发生。中断门中的DPL(Descriptor Privilege Level)为0,因此用户态的进程不能访问中断门。所有的中断处理程序都由中断门激活,并全部限制在内核态。
  • 陷阱门描述符(Trap-gate descriptor):用于系统调用,其类型码为111,与中断门类似,其唯一的区别是,控制权通过陷阱门进入处理程序时维持IF标志位不变,也就是说,不关中断。
  • 任务门描述符(Task-gate descriptor)和调用门描述符(Call-gate descriptor): 这两种主要是Intel设置的“任务”切换的手段。

80386的中断门描述符、陷阱门描述符的格式:

这里写图片描述

七、中断处理中硬件负责完成的工作

中断服务例程包括具体负责处理中断(异常)的代码是操作系统的重要组成部分。需要注意区别的是,有两个过程由硬件完成:

硬件中断处理过程1(起始):
从CPU收到中断事件后,打断当前程序或任务的执行,根据某种机制跳转到中断服务例程去执行的过程。其具体流程如下:

  • CPU在执行完当前程序的每一条指令后,都会去确认在执行刚才的指令过程中中断控制器(如8259A)是否发送中断请求过来,如果有那么CPU就会在相应的时钟脉冲到来时从总线上读取中断请求对应的中断向量;
  • CPU根据得到的中断向量(以此为索引)到IDT中找到该向量对应的中断描述符,中断描述符里保存着中断服务例程的段选择子;
  • CPU使用IDT查到的中断服务例程的段选择子从GDT中取得相应的段描述符,段描述符里保存了中断服务例程的段基址和属性信息,段描述符的基址+中断描述符中的偏移地址形成了中断服务例程的起始地址;
  • CPU会根据CPL和中断服务例程的段描述符的DPL信息确认是否发生了特权级的转换。比如当前应用程序正运行在用户态,而中断服务例程是运行在内核态的,则意味着发生了特权级的转换,这时CPU会从当前应用程序的TSS信息(该信息在内存中的起始地址存在TR寄存器中)里取得该程序的内核栈地址,即包括内核态的ss和esp的值,并立即将系统当前使用的栈切换成新的内核栈。这个栈就是即将运行的中断服务程序要使用的栈。紧接着就将当前程序使用的用户态的ss和esp压到新的内核栈中保存起来;如果当前程序运行在内核态,则不会发生特权转移
  • CPU需要开始保存当前被打断的用户态程序的现场(即一些寄存器的值),以便于将来恢复被打断的程序继续执行。这需要利用内核栈来保存相关现场信息,即依次压入当前被打断程序使用的eflags,cs,eip,errorCode(如果是有错误码的异常)信息;
  • CPU把中断服务例程的地址加载到cs和eip寄存器中,开始执行中断服务例程。这意味着先前的程序被暂停执行,中断服务程序正式开始工作。

硬件中断处理过程2(结束):
每个中断服务例程在有中断处理工作完成后需要通过iret(或iretd)指令恢复被打断的程序的执行。CPU执行IRET指令的具体过程如下:

  • 程序执行这条iret指令时,首先会从内核栈里弹出先前保存的被打断的程序的现场信息,即eflags,cs,eip重新开始执行;
  • 如果存在特权级转换(从内核态转换到用户态),则还需要从内核栈中弹出用户态栈的ss和esp,这样也意- 味着栈也被切换回原先使用的用户态的栈了;
  • 如果此次处理的是带有错误码(errorCode)的异常,CPU在恢复先前程序的现场时,并不会弹出errorCode。这一步需要通过软件完成,即要求相关的中断服务例程在调用iret返回之前添加出栈代码主动弹出errorCode。

下图显示了从中断向量到GDT中相应中断服务程序起始位置的定位方式:

3.4.3.4

八、中断处理的特权级转换

中断处理得特权级转换是通过门描述符(gate descriptor)和相关指令来完成的。一个门描述符就是一个系统类型的段描述符,一共有4个子类型:调用门描述符(call-gate descriptor),中断门描述符(interrupt-gate descriptor),陷阱门描述符(trap-gate descriptor)和任务门描述符(task-gate descriptor)。与中断处理相关的是中断门描述符和陷阱门描述符。这些门描述符被存储在中断门描述符表(Interrupt Descriptor Table,简称IDT)当中。CPU把中断向量作为IDT表项的索引,用来指出当中断发生时使用哪一个门描述符来处理中断。中断门描述符和陷阱门描述符几乎是一样的。中断发生时实施特权检查的过程如下图所示:

3.4.3.5

门中的DPL和段选择符一起控制着访问,同时,段选择符结合偏移量(Offset)指出了中断处理例程的入口点。内核一般在门描述符中填入内核代码段的段选择子。产生中断后,CPU一定不会将运行控制从高特权级转向低特权级,特权级必须要么保持不变(当操作系统内核自己被中断的时候),或被提升(当用户态程序被中断的时候)。无论哪一种情况,作为结果的CPL必须等于目的代码段的DPL。如果CPL发生了改变(比如从用户态到内核态),一个栈切换操作(通过TSS完成)就会发生。如果中断是被用户态程序中的指令所触发的(比如软件执行INT n生产的中断),还会增加一个额外的检查:门的DPL必须具有与CPL相同或更低的特权。这就防止了用户代码随意触发中断。如果这些检查失败,就会产生一个一般保护异常(general-protection exception)。



附A:异常中断表

Name Vector nr. Type Mnemonic Error code? Divide-by-zero Error 0 (0x0) Fault DE No Debug 1 (0x1) Fault/Trap DB No Non-maskable Interrupt 2 (0x2) Interrupt - No Breakpoint 3 (0x3) Trap BP No Overflow 4 (0x4) Trap OF No Bound Range Exceeded 5 (0x5) Fault BR No Invalid Opcode 6 (0x6) Fault UD No Device Not Available 7 (0x7) Fault NM No Double Fault 8 (0x8) Abort DF Yes (Zero) Coprocessor Segment Overrun 9 (0x9) Fault - No Invalid TSS 10 (0xA) Fault TS Yes Segment Not Present 11 (0xB) Fault NP Yes Stack-Segment Fault 12 (0xC) Fault SS Yes General Protection Fault 13 (0xD) Fault GP Yes Page Fault 14 (0xE) Fault PF Yes Reserved 15 (0xF) - - No x87 Floating-Point Exception 16 (0x10) Fault MF No Alignment Check 17 (0x11) Fault AC Yes Machine Check 18 (0x12) Abort MC No SIMD Floating-Point Exception 19 (0x13) Fault XM/#XF No Virtualization Exception 20 (0x14) Fault VE No Reserved 21-29 (0x15-0x1D) - - No Security Exception 30 (0x1E) - SX Yes Reserved 31 (0x1F) - - No Triple Fault - - - No FPU Error Interrupt IRQ 13 Interrupt FERR No

附录B:PIC 产生的标准 IRQ 如下表:

IRQ Description 0 Programmable Interrupt Timer Interrupt 1 Keyboard Interrupt 2 Cascade (used internally by the two PICs. never raised) 3 COM2 (if enabled) 4 COM1 (if enabled) 5 LPT2 (if enabled) 6 Floppy Disk 7 LPT1 / Unreliable “spurious” interrupt (usually) 8 CMOS real-time clock (if enabled) 9 Free for peripherals / legacy SCSI / NIC 10 Free for peripherals / SCSI / NIC 11 Free for peripherals / SCSI / NIC 12 PS2 Mouse 13 FPU / Coprocessor / Inter-processor 14 Primary ATA Hard Disk 15 Secondary ATA Hard Disk
1 0