当前位置首页 > Linux知识

【Linux操作系统分析】中断和异常(1)——中断描述符表IDT,I/O中断处理,中断向量

阅读次数:264 次  来源:admin  发布时间:

1 中断

中断通常被定义为一个事件,该事件改变处理器执行的指令顺序。

中断通常分为同步中断和异步中断。

同步中断(中断)是当前指令执行时由CPU控制单元产生的,之所以称为同步,是因为只有在一条指令终止执行后CPUT才会发出中断。 异步中断(异常)是由其他硬件设备依照CPU时钟信号随机产生的。 分类:中断:分为可屏蔽中断(控制单元会忽略屏蔽的中断)和非屏蔽中断(由CPU辨认)。 异常:处理器探测异常,故障,陷阱,异常中止,编程异常

向量:每个中断和异常是由0~255之间的一个数来标识。Intel把这个8位的无符号整数叫做一个向量。非屏蔽中断的向量和异常的向量是固定的,而可屏蔽中断的向量可以通过对中断控制器的编程来改变。

IRQ:每个能够发出中断请求的硬件设备控制器都有一条名为IRQ的输出线。所有现有的IRQ线都与一个名为可编程中断控制器的硬件电路的输入引脚相连。 IRQ线从0开始编号,与IRQn关联的Intel的缺省向量是n+32。

可编程中断控制器(PIC): 可编程中断控制器执行以下动作:

高级可编程控制器(APIC)

I/O APIC的组成为:一组24条IRP线,一张24项的中断重定向表,可编程寄存器,以及通过APIC总线发送和接收APIC信息的一个信息单元。 中断优先级并不与引脚号相关联:中断重定向表的每一项都可以被单独编程以指明中断向量和优先级,目标处理器及选择处理器的方式。重定向表中的信息用于把每个外部IRQ信号转换为每个外部IRQ信号转换为一条消息,然后,通过APIC总线把消息发送给一个或多个本地APIC单元。

来自外部硬件设备的中断请求以两种方式在可用CPU之间分发:静态分发和动态分发(可编程任务优先级寄存器,仲裁优先级寄存器)。 除了在处理器之间分发中断外,多APIC系统还允许CPU产生处理器间中断(IPI)。当一个CPU希望把中断发给另一个CPU时,它就在自己本地APIC的中断指令寄存器(ICR)中存放这个中断向量和目标本地APIC的标识符。然后,通过APIC总线想目标本地APIC发送一条消息,从而向自己的CPU发出一个相应的中断。

2 异常 内核必须为每种异常提供一个专门的异常处理程序。对于某些异常,CPU控制单元在开始执行异常处理程序前会产生一个硬件出错码,并且压入内核态堆栈。

3 中断描述符表(IDT) 中断描述符表是一个系统表,它与每一个中断或异常向量相联系,每一个向量在表中有相应的中断和异常处理程序的入口地址。表中的每一项对应一个中断或异常向量,每个向量由8个字节组成。因此,最多需要256*8=2048字节来存放IDT。 idtr CPU寄存器使IDT可以位于内存的任何地方,它指定IDT的线性基址及其限制,在允许中断之前,必须用lidt汇编指令初始化idtr。

Linux利用中断门处理中断,利用陷阱们处理异常。 任务门:当中断信号发生时,必须取代当前进程的那个进程的TSS选择符存放在任务门中。 中断门:包含段选择符和中断或异常处理程序的段内偏移量。当控制权转移到一个适当的段时,处理器清IF标志,从而关闭将来会发生的可屏蔽中断。 陷阱们:与中断门相似,只是控制权传递到一个适当的段时处理器不修改IF标志。

中断和异常的硬件处理 当执行了一条指令后,cs和eip这对寄存器包含下一条将要执行的指令的逻辑地址。在处理那条指令之前,控制单元会检查在运行前一条指令时是否发生了一个中断或异常。如果发生了一个中断或异常,那么控制单元执行下列操作:

控制单元所执行的最后一步就是跳转到中断或者异常处理程序,处理完中断信号后,控制单元所执行的指令就是被选中处理程序的第一条指令。 中断或异常被处理完后,相应的处理程序必须产生一条iret指令,把控制权交给被中断的进程,这将迫使控制单元:

4 中断和异常处理程序的嵌套执行 每个中断或异常都会引起一个内核控制路径,或者说代表当前进程在内核态执行单独的指令序列。 例如:当I/O设备发出一个中断时,相应的内核控制路径的第一部分指令就是那些把寄存器的内容保存在内核堆栈的指令,而最以后一部分指令就是恢复寄存器内容并让CPU返回到用户态的那些指令。 内核控制路径可以任意嵌套:一个中断处理程序可以被另一个中断处理程序“中断”,因此引起内核控制路径的嵌套执行。

允许内核控制路径嵌套执行必须付出代价,那就是中断处理程序必须永不阻塞。换句话说,中断处理程序运行期间不能发生进程切换,因为只有一个内核态堆栈,这个栈属于当前进行。 大多数异常都是在用户态发生。只有一种异常发生在内核态——“Page Fault”缺页中断。当处理这样一个异常时,内核可以挂起当前进程,并用另一个进程代替它,知道请求的页可以使用为止。只要被挂起的进程又或得处理器,处理缺页异常的内核控制路径就恢复执行。 因为缺页异常处理程序从不进一步引起异常,所以与异常相关的至多两个内核控制路径(第一个由系统调用引起,第二个由缺页引起)会堆叠在一起。一个在另一个之上。 一个中断处理程序既可以抢占其他的终端处理程序,也可以抢占异常处理程序。相反,异常处理程序从不抢占中断处理程序。

5 初始化中断描述符表 内核启动中断以前,必须把IDT表的初始化地址撞到idtr寄存器,并初始化表中的每一项。 int指令允许用户态进程发出一个中断信号,其值可以是0~255的任意一个向量。 在少数情况下,用户态进程必须能发出一个编程异常,为此,只要把中断或陷阱门描述符的DPL字段设置成3,即特权级尽可能一样高就足够了。

Linux下的中断门、陷阱门及系统门

IDT的初步初始化: IDT存放在idt_table表中,有256个表项,6字节的idt_descr变量指定了IDT的大小和它的地址,只有当内核用lidt汇编指令初始化idtr寄存器才用到这个变量。 在内核初始化过程中,setup_idt()汇编语言函数用同一个中断门(即指向ingnore_int()中断处理程序)来填充所有这256个idt_table表项。 用汇编写的ignore_int(0中断处理程序,可以看作一个空的处理程序,它执行下列动作:

6 异常处理 CPU产生的大部分异常都由LInux解释为出错条件,当其中一个异常发生时,内核就向引起异常的进程发送一个信号向它通知一个反常条件。 异常处理程序有一个标准的结构,由一下三部分组成: 在内核堆栈中保存大多数寄存器的内容(这部分用汇编语言实现) 用高级的C函数处理异常 通过ret_from_exception()函数从异常处理程序退出 为了利用异常,必须对IDT进行适当的初始化,使得每个被确认的异常都有一个异常处理程序。trap_init()函数的工作是将一些最终值(即处理异常的函数)插入到IDT的非屏蔽中断及异常表项中。

一个典型的异常处理程序被调用: 1 为异常处理程序保存寄存器的值 handler_name来表示一个通用的异常处理程序的名字。每一个异常处理程序都以下列的汇编指令开始:

当异常发生时,如果控制单元没有自动地把一个硬件出错码插入到栈中,相应的汇编语言片段会包含一条pushl $0指令,从栈中垫上一个空值。然后把高级C函数的地址压进栈中,它的名字由异常处理程序名与do_前缀组成。 标号为error_code的汇编语言片段对所有的异常处理程序都是相同,这段代码执行以下步骤: 进入和离开异常处理程序:

7 中断处理 内核只要给引起异常的进程发送一个Unix信号就能处理大多数异常。因此,要采取的行动被延迟,直到进程接收到这个信号。所以,内核能很快地处理异常 这种方法并不适合中断,因为经常会出现一个进程,被挂起好久后中断才到达的情况,因此,一个完全无关的进程可能正在运行。所以给当前进程发送一个Unix信号是毫无意义的。 中断处理依赖中断类型:I/O中断,时钟中断,处理器间中断。 1)1 I/O中断处理 一般来说,I/O终端处理程序必须足够灵活以给多个设备同时提供服务。 几个设备可以共享同一个IRQ线,这就意味着仅仅中断向量不能说明所有问题。 中断处理程序的灵活性实现:IRQ共享,IRQ动态分配。 当一个中断发生时,并不是所有的操作都具有相同的紧迫性,因为当一个终端处理程序正在运行时,相应的IRQ线上发出的信号就被暂时忽略,因此,终端处理程序不能执行任何阻塞过程。 LInux把紧随中断要执行的操作分为三类: 紧急的 非紧急的 非紧急可延迟的 所有的I/O中断处理程序都执行四个相同的基本操作: 在内核态堆栈中保存IRQ的值和寄存器的内容 为正在给IRQ线服务的PIC发送一个应答,这将允许PIC进一步发出中断 执行共享这个IRQ的所有设备的中断服务例程(ISR) 跳过ret_from_intr()的地址后终止

中断向量:

内核必须在启用中断前发现IRQ号与I/O设备之间的对应,IRQ号与I/O设备之间的对应是在初始化每个设备驱动程序时建立的。

IRQ数据结构(支持中断处理的数据结构):

每个中断向量都有它自己的irq_desc_t描述符,所有的这些描述符组织在一起形成irq_desc数组:

如果一个中断内核没有处理,那么这个中断就是意外中断。

内核把中断和意外中断的总次数分别存放在irq_desc_t描述符的irq_count和irqs_unhandled字段中,当第100 000次中断产生时,如果意外中断的次数超过99 900,内核才禁用这条IRQ线。

上一篇:Debian无线网卡从安装驱动到网络配置
下一篇:Linux下安装Oracle,图形化界面乱码