《深入理解Linux内核》学习笔记-第四章 中断和异常

中断和异常

中断通常分为同步中断synchronous和异步中断asynchronous

  • 同步中断是指当指令执行时由CPU控制单元产生的,之所以成为同步,是因为只有在一条指令终止执行后CPU才会发出中断
  • 异步中断是由其他硬件设备依照CPU时钟信号随机产生的

在intel处理器手册中,把同步和异步中断分别称为异常(exception)和中断(interrupt)

  • 中断
    • 可屏蔽中断:I/O设备发出的所有中断请求(IRQ)都产生可屏蔽中断
    • 非屏蔽中断:只有几个危急事件(如硬件故障)才引起非屏蔽中断
  • 异常
    • 处理器探测异常
      • 故障(fault)
      • 陷阱(trap)
      • 异常中止(abort)
    • 编程异常

IRQ和中断

每个能发出中断请求的硬件设备控制器都有一条名为IRQ(Interrupt ReQuest)的输出线。所有IRQ线都与一个名为可编程中断控制器(Programmable Interrupt Controller,PIC)的硬件电路的输入引脚相连。

如果系统只有一个CPU,那么主PIC的输出线将直接连接到CPU的INTR引脚。如果系统中有两个或多个CPU,则需要高级可编程中断控制器。 intel从Pentium II引入了一种名为I/O高级可编程控制器(I/O Advanced Programmable Interrupt Controller,APIC)的新组件用于处理多处理器的中断请求。

异常

x86处理器发布大约20种不同的异常,详细信息可以在Intel的技术文档中找到。

中断描述符表

中断描述符表(Interrupt Descriptor Table,IDT)是一个系统表。内核在允许中断发生前,必输适当地初始化IDT。IDT包含三种类型的描述符:

  • 任务门(task gate)
  • 中断门(interrupt gate)
  • 陷阱门(trap gate)

中断和异常处理程序的嵌套执行

内核控制路径可以任意嵌套,一个中断处理程序可以被另一个中断程序中断,因此引起内核控制路径的嵌套执行。

一个中断处理程序既可以抢占其他的中断处理程序,也可以抢占异常处理程序。相反,异常处理程序从不抢占中断处理程序。

在内核态能触发的唯一异常就是缺页异常。中断处理程序从不执行可以导致缺页(因此意味着进程切换)的操作。

异常处理

异常处理程序有一个标准的结构,由以下三部分组成:

  1. 在内核堆栈中保存大多数寄存器的内容(这部分用汇编语言实现)
  2. 用高级的C函数处理异常
  3. 用过ret_from_exception()函数从异常处理程序退出

中断处理

主要讨论三种中断:I/O中断、时钟中断、处理器间中断

不管引起中断的电路种类如何,所有的I/O中断处理程序都执行四个相同的基本操作:

  1. 在内核态堆栈中保存IRQ的值和寄存器的内容
  2. 为正在给IRQ线服务的PIC发送一个应答,这将允许PIC进一步发出中断
  3. 执行共享这个IRQ服务的所有设备的中断服务例程
  4. 跳到ret_from_intr()的地址后停止

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

Linux遵循对称多处理器模型(SMP),这就意味着内核从本质上对任何一个CPU都没有偏爱。因此所有CPU服务于I/O中断的时间片几乎相同。正常情况下,多APIC系统初始化后无需内核费心,但在有些情况下,硬件不能公平地处理CPU之间的中断分配。因此,在必要的时候,Linux2.6利用叫做kirqd的特殊内核线程来纠正对CPU进行的IRQ自动分配。

IRQ线的动态分配

在激活一个准备利用IRQ线的设备之前,其相应的驱动程序调用request_irq()。这个函数建立一个新的irqaction描述符,并用参数初始化它。然后调用setup_irq()函数把这个描述符插入到合适的IRQ链表。如果setup_irq()返回一个出错码,设备驱动程序中止操作,这意味着IRQ线已由另一个设备所使用,而这个设备不允许中断共享。当设备操作结束时,驱动程序调用free_irq()函数从IRQ链表中删除这个描述符,并释放相应的内存区。

可延迟函数及工作队列

Linux 2.6 中引用两种非紧迫、可中断内核函数:可延迟函数工作队列来把可延迟中断从中断处理程序中抽出来,使内核保持较短的响应时间。

可延迟函数包括软中断(softirq)和tasklet,tasklet是在软中断之上实现

在Linux 2.6 中引入了工作队列,它与Linux 2.4中的任务队列具有类似的构造,用来代替任务队列。 它允许内核函数被激活,而且稍后由一种叫做工作者线程(worker thread)的特殊内核线程来执行。

尽管可延迟函数和工作队列非常相似,但是他们的区别还是很大的。主要区别在于,可延迟函数运行在中断上下文中,而工作队列中的函数运行在进程上下文中。

从中断和异常返回

从中断和异常退出时,分别有两个不同的入口点,从中断结束时,内核进入ret_from_intr();从异常结束时,内核进入ret_from_exception()

0 条评论
发表一条评论