中断和异常向量:中断和异常在处理器中都有对应的编号,被称为向量。当中断或异常发生时,处理器会根据向量找到相应的中断处理程序或异常处理程序。
中断源和异常源:中断可以由硬件设备产生,也可以由软件指令产生。异常通常由于程序错误或其他意外情况引起。
异常的分类:异常可以分为故障、陷阱和中止。故障是可修复的错误,陷阱是有意的,中止是不可修复的错误。
程序或任务的重新执行:中断或异常处理程序执行完成后,处理器会返回到被中断的程序或任务。这个过程确保了程序的连续性。
开启和禁止中断:处理器可以通过特殊的指令来开启或禁止中断。禁止中断可以用来保护关键部分的代码,确保它们不会被中断。
异常和中断的优先级:不同的中断或异常有不同的优先级。处理器会根据优先级来确定哪个中断或异常应该被处理。
处理器如何处理?
处理器如何为每个特定的异常和中断分配一个独特的标识号,这个标识号被称为向量号。处理器使用这个向量号在中断描述符表中查找对应的处理程序。向量号的范围是0到255,其中0到31被体系结构保留,用于定义特定的异常和中断。32到255的范围用于用户定义的中断,通常用于外部I/O设备。
思考:实模式和保护模式下,中断向量表一样吗?
在实模式和保护模式下,中断向量表并不相同。实模式下,中断向量表是固定的,而在保护模式下,它是由操作系统动态设置的。在保护模式下,中断处理程序的地址是通过中断描述符表(IDT)来获得的。
通过INTR引脚或本地APIC传递到处理器的任何外部中断都被称为可屏蔽硬件中断。
可以通过INTR引脚传递的可屏蔽硬件中断包括从0到255的所有IA-32架构定义的中断向量;可以通过本地APIC传递的中断包括从16到255的中断向量。EFLAGS寄存器中的IF标志允许将所有可屏蔽硬件中断作为一个组进行屏蔽。
当在应用程序、操作系统或执行器的执行过程中检测到程序错误时,处理器会生成一个或多个异常。Intel 64和IA-32体系结构为每个处理器可检测到的异常定义了一个向量号。
INTO、INT 3 和 BOUND 指令允许在软件中生成异常。这些指令允许在指令流的某些点执行异常条件的检查。
例如,INT 3 会生成断点异常。INT n 指令可以用于在软件中模拟异常;但是有一个限制。如果 INT n 提供了一个体系结构定义的异常的向量,处理器将生成一个中断到正确的向量(以访问异常处理程序),但不会将错误码推送到栈上。即使相关的硬件生成的异常通常会产生一个错误码。异常处理程序在处理异常时仍然会尝试从栈中弹出一个错误码。因为没有错误码被推送,处理程序将弹出并丢弃EIP(代替缺失的错误码)。这将导致返回到错误的位置。
P6家族和Pentium处理器为检查内部芯片硬件和总线事务的操作提供了内部和外部机器检查机制。当检测到机器检查错误时,处理器会发出一个机器检查异常(向量18)并返回一个错误码。
异常根据它们的报告方式以及引发异常的指令是否可以在不丢失程序或任务连续性的情况下重启,被分类为故障、陷阱或中止。
故障(Faults):故障是一种通常可以纠正的异常,一旦纠正,允许程序在不丢失连续性的情况下重新启动。当报告故障时,处理器将机器状态恢复到执行引发故障指令之前的状态。故障处理程序的返回地址(CS和EIP寄存器的保存内容)指向引发故障的指令,而不是指向引发故障指令后面的指令。
陷阱(Traps):陷阱是在陷阱指令执行后立即报告的异常。陷阱允许继续执行程序或任务,而不会丢失程序连续性。陷阱处理程序的返回地址指向陷阱指令后要执行的指令。
中止(Aborts):中止是一种异常,不总是报告引发异常的指令的精确位置,并且不允许重新启动引发异常的程序或任务。中止用于报告严重错误,例如硬件错误和系统表中的不一致或非法值。
通常作为故障报告的一种子集的异常是不可重启的。这种异常会导致一些处理器状态的丢失。这类情况被视为编程错误。引发这类异常的应用程序应该被操作系统终止。
为了允许在处理异常或中断后重新启动程序或任务,所有的异常(除了中止)都保证在指令边界上报告异常。所有的中断也保证在指令边界上被触发。
对于属于故障类别的异常,返回指令指针(处理器生成异常时保存的)指向引发故障的指令。因此,在处理故障后重新启动程序或任务时,会重新执行引发故障的指令。重新启动引发故障的指令通常用于处理当对操作数的访问被阻止时生成的异常。
这类异常的最常见例子是当程序或任务引用位于内存中不存在的页面上的操作数时产生的页面故障异常(#PF)。当发生页面故障异常时,异常处理程序可以将页面加载到内存中,并通过重新执行引发故障的指令继续执行程序或任务。为了确保重启对当前正在执行的程序或任务透明,处理器保存了必要的寄存器和堆栈指针,以允许将状态还原到执行引发故障指令之前的状态。
对于属于陷阱类别的异常,返回指令指针指向陷阱指令后要执行的指令。如果在执行一个传递执行权的指令时检测到陷阱,返回指令指针会反映这次传递。
例如,如果在执行一个JMP指令时检测到陷阱,返回指令指针将指向JMP指令的目标,而不是指向JMP指令之后的地址。所有的陷阱异常允许在不丢失连续性的情况下重新启动程序或任务。
例如,溢出异常就是一种陷阱异常。在这种情况下,返回指令指针指向测试EFLAGS.OF(溢出)标志的INTO指令之后的指令。此异常的处理程序解决了溢出条件。从陷阱处理程序返回后,程序或任务的执行会继续执行INTO指令之后的指令。
属于中止类别的异常不支持可靠地重新启动程序或任务。中止处理程序设计用来收集处理器在发生中止异常时的诊断信息,然后尽可能平稳地关闭应用程序和系统。
当处理器从以上任一来源接收到NMI时,处理器会立即调用由中断向量号2指向的NMI处理程序来处理它。处理器还会调用特定的硬件条件,以确保在NMI处理程序执行完成之前,不会接收到其他中断,包括NMI中断。
在执行NMI中断处理程序时,处理器会阻止后续的NMI传递,直到IRET指令的下一次执行。这种NMI的阻止可以防止NMI处理程序的嵌套执行。
IF标志可以禁用通过处理器的INTR引脚或通过本地APIC接收到的可屏蔽硬件中断的服务。
IF标志不影响传递到NMI引脚或通过本地APIC传递模式NMI消息的非屏蔽中断,也不影响处理器生成的异常。与EFLAGS寄存器中的其他标志一样,处理器在响应硬件复位时会清除IF标志。
IF标志还受以下操作的影响:
EFLAGS寄存器中的RF(恢复)标志控制处理器对指令断点条件的响应。RF标志的主要功能是防止处理器在指令断点上进入调试异常循环。
在切换到不同的堆栈段时,软件通常使用一对指令,例如:
MOV SS, AX
MOV ESP, StackTop
处理器会在MOV到SS指令或POP到SS指令之后,直到下一条指令的边界被达到,禁止中断、调试异常和单步陷阱异常。
处理器首先处理具有最高优先级的异常或中断,将执行转移到处理程序的第一条指令。较低优先级的异常会被丢弃;较低优先级的中断则会保持挂起状态。当中断处理程序将执行返回到程序或任务中发生异常和/或中断的点时,被丢弃的异常会重新生成。
中断描述符表 (IDT) 将每个异常或中断向量与处理相关异常或中断的过程或任务的门描述符关联起来。
构成中断描述符表(IDT)
中断描述符表(IDT)是一个包含各种中断和异常处理程序地址的数据结构。它是由多个描述符组成的数组,每个描述符对应一个特定的中断或异常类型。IDT中的每个描述符都包含了中断或异常处理程序的入口地址和相关的特殊标志。
获取中断处理程序的地址
中断处理程序的地址是存储在IDT的相应描述符中的。当中断或异常发生时,处理器会使用中断向量号(或异常号)作为索引,找到IDT中相应的描述符。描述符中包含了处理程序的地址。处理器使用这个地址来跳转到中断或异常处理程序的代码。
设置中断描述符表寄存器:
为了告诉处理器在哪里可以找到IDT,需要使用中断描述符表寄存器(IDTR)。IDTR是一个64位寄存器,包含了IDT的基地址和限制(IDT的大小减去1)。要设置IDTR,需要将IDT的基地址(即IDT的起始物理地址)和大小(以字节为单位,减去1)加载到IDTR寄存器中。
IDT可以包含三种类型的门描述符:
任务门描述符(Task-gate descriptor): 任务门描述符允许程序切换到不同的任务。它包含了指向任务状态段(TSS)的选择子,当发生任务切换时,处理器将加载TSS中的任务上下文。
中断门描述符(Interrupt-gate descriptor): 中断门描述符用于处理中断。当中断门描述符被调用时,处理器会自动禁用中断(IF标志被清除),以防止嵌套中断。中断门通常用于非可屏蔽中断(NMI)等紧急情况。
陷阱门描述符(Trap-gate descriptor): 陷阱门描述符类似于中断门描述符,但不会禁用中断。当陷阱门被调用时,处理器不会改变IF标志,允许嵌套发生。陷阱门通常用于调试器或系统监视器等需要处理中断的情况。
中断门或陷阱门引用了在当前执行任务的上下文中运行的异常或中断处理程序。门的段选择子指向GDT或当前LDT中的可执行代码段的段描述符。门描述符的偏移字段指向异常或中断处理程序的开始处。
当通过IDT中的任务门访问异常或中断处理程序时,会导致任务切换。使用单独的任务处理异常或中断的8优点:
一般的中断处理流程:
中断触发: 中断可以由外部硬件、软件指令(例如INT指令)、或者处理器内部发生的错误(例如除以零)触发。当中断发生时,处理器会停止当前正在执行的任务,保存当前的执行上下文。
中断向量的确定: 中断向量是中断类型的唯一标识符。处理器使用中断向量作为索引,从中断描述符表(IDT)中找到相应的中断门描述符。中断门描述符中包含了中断处理程序的地址和代码段选择子。
特权级别检查: 处理器会检查中断门描述符中的特权级别(DPL),确保当前特权级别(CPL)小于或等于中断门描述符中的DPL。如果不满足这个条件,会产生通用保护异常(#GP)。
堆栈切换(如果需要): 如果中断处理程序使用了不同的堆栈,处理器会执行堆栈切换,将当前堆栈指针(ESP)切换到中断处理程序的堆栈。
中断处理程序执行: 处理器跳转到中断处理程序的地址,开始执行中断处理程序的指令。中断处理程序执行期间,它可以访问中断相关的数据,执行必要的操作,最终处理中断。
中断处理完成: 中断处理程序执行完毕后,它需要调用IRET(Interrupt Return)指令返回。IRET指令会从堆栈中弹出保存的执行上下文,包括标志寄存器的值,并将控制权返回到之前被中断的程序。
恢复执行: 一旦中断处理程序执行完毕,处理器会从中断点的下一条指令继续执行。如果中断发生时处于用户模式(CPL=3),处理器会恢复用户模式,并继续执行用户程序。
在64位模式下的中断和异常处理的一些特点:
中断过程调用的流程通常如下:
触发中断/异常: 当硬件或软件条件满足时,中断或异常会被触发。
中断处理程序的查找: 处理器使用中断向量(或异常向量)作为索引,查找中断描述符表(IDT)中的对应门描述符。中断描述符表中的门描述符可以是中断门、陷阱门或者任务门。
根据门类型选择处理方式: 如果是中断门,会将当前处理器状态保存(通常是EFLAGS、CS、EIP等),然后跳转到中断服务例程。如果是陷阱门,不会保存处理器状态,直接跳转到中断服务例程。
处理中断/异常: 中断服务例程处理中断/异常,可能会修改处理器状态、处理中断/异常相关的操作,执行完中断服务例程后,会根据中断门类型的不同,进行不同的操作。
如果是中断门(Interrupt Gate),执行完中断服务例程后,使用IRET指令恢复被保存的处理器状态,包括EFLAGS、CS、EIP等。IRET会将栈中的内容弹出到相应寄存器,从而返回到之前被中断的程序。
如果是陷阱门(Trap Gate),执行完中断服务例程后,使用IRET指令恢复被保存的处理器状态,同样也会从栈中弹出内容,返回到之前被中断的程序。
继续执行中断后的指令: 返回到中断后的程序,继续执行中断后的指令。
判断中断处理过程与被中断任务的优先级
通常基于中断门或陷阱门描述符的特权级(DPL字段)。DPL字段表示中断服务例程的权限级别。如果中断服务例程的DPL大于或等于当前CPL(当前特权级别),则可以被执行。如果DPL小于CPL,则会发生“通用保护故障”(General Protection Fault)。
不同优先级上,处理方式可能是不同的。
通常情况下,更高优先级的中断会打断当前正在执行的低优先级任务,以便迅速响应。这是通过中断嵌套(Interrupt Nesting)来实现的。处理高优先级中断时,可能会禁用低优先级中断,以确保高优先级任务的快速响应。在低优先级中断处理结束后,高优先级中断会被重新启用。
如果发生堆栈切换,处理器通常会在堆栈上保存当前的处理器状态(EFLAGS、CS、EIP等)和其他寄存器的值。然后,处理器会将新的堆栈指针(SS:RSP)加载到RSP寄存器,以便中断服务例程使用新的堆栈。
如果没有发生堆栈切换
处理器会继续使用当前的堆栈,不会改变堆栈指针的值。中断服务例程执行完后,处理器会使用IRET指令将之前保存的处理器状态从堆栈中弹出,恢复到中断发生时的状态,然后继续执行中断后的指令。
异常和中断处理过程中的保护通常包括以下几个方面:
栈保护: 确保在中断处理过程中正确使用栈,避免栈溢出或栈下溢。
数据保护: 确保在中断处理过程中不会损坏关键数据,避免数据被错误地修改。
指令保护: 中断服务例程通常是受信任的代码,但仍然需要确保不受到恶意指令注入的影响。
异常和中断处理过程中的标志使用方式通常包括以下几个方面:
中断允许(IF)标志: 当IF标志为1时,处理器可以响应外部中断。在中断服务例程开始执行时,通常会将IF标志清零,以防止被其他中断打断。在中断服务例程结束时,会使用IRET指令将IF标志恢复到先前的值。
方向(DF)标志: DF标志用于字符串操作中的方向。在中断服务例程中,通常需要根据具体需求来设置DF标志。
中断门与陷阱门的唯一区别
在于CPU在使用中断门时会自动将IF(中断允许)标志清零,而在使用陷阱门时不会改变IF标志的值。这意味着,使用中断门时,会在进入中断服务例程前禁用外部中断,而使用陷阱门时,外部中断仍然会被响应,可以嵌套处理中断。