第七章 中断和中断处理

中断和中断处理

处理器和外部硬件的速度往往不在一个数量级,如何高效地让处理器和这些外部设备协调工作?

1. 中断

处理器和外部设备协调的方式,从处理器出发,很容易想到的是轮询的方式,但是轮询肯定会做很多无用功;所以应该从外部设备角度,让硬件在需要的时候向内核发出信号,即中断机制。

中断本质上是一种特殊电信号,由硬件设备产生,直接送入中断控制器输入引脚,再发向处理器,处理器接收到中断马上向操作系统反映信号的到来,然后由操作系统执行对应的中断处理程序。

每个中断都有一个中断号,被称为中断请求IRQ

中断与异常

异常产生需要考虑处理器时钟同步,常常被称为同步中断。这样我们可以把中断分为异步中断和同步中断.

2. 中断处理程序

响应一个特定中断时,内核会执行对应的中断处理程序(interrupt handler)或中断服务例程(interrupt service routine, ISR),一个设备的中断处理程序是它设备驱动程序的一部分。

中断程序程序运行在中断上下文,这个上下文中执行的代码不能阻塞,而且中断随时可能发生,必须保证中断处理程序快速执行。又想中断处理程序运行快,又想中断处理程序完成的工作量多,这是矛盾的,为了解决这个矛盾,中断被分成了两个部分:

  • top half:中断到来就立即执行,但只做严格时限的工作,如对接收的中断进行应答或复位硬件,这些工作都是在所有中断被禁止的情况下完成。
  • bottom half:允许稍后完成的工作会放在这部分,在合适的时机,被执行。

3. 注册中断处理程序

如果设备要使用中断,那么相应的驱动程序就要注册一个中断处理程序。

驱动程序通过request_irq()函数注册一个中断处理程序,linux/interrupt.h

1
2
3
4
5
6
7
8
/* 分配一条给定的中断线 */
static inline int __must_check
request_irq(unsigned int irq, //要分配的中断号
irq_handler_t handler, //实际的中断处理程序
unsigned long flags, //标志位,中断的具有特性
const char *name, /*中断设备名称的ASCII 表示,被/proc/irq和/proc/interrupts文件使用*/
void *dev //用于共享中断线,多个中断程序共享一个中断线时(共用一个中断号),依靠dev来区别各个中断程序
)
  • 实际中断处理函数的原型为:

    1
    typedef irqreturn_t (*irq_handler_t)(int, void *);
  • 中断处理程序标志:定义在linux/interrupt.h

  • request_irq()函数可能会睡眠
  • 释放中断处理程序:卸载驱动程序需要注销相应的中断处理程序,释放中断线
    • free_irq(unsigned int irq, void *dev)

4. 编写中断处理程序

  • 中断处理程序返回值类型为:irqreturn_t,返回IRQ_NONE或IRQ_HANDLED
  • 中断处理程序不需要是重入的,因为当一个中断执行时相应中断线会被屏蔽,因此同一个中断处理程序不可能被同时调用处理嵌套中断
  • 共享中断处理程序
    • request_irq()的flags必须设置IRQF_SHARED
    • dev参数必须唯一,通常选择设备结构
    • 中断处理程序必须能够区分它的设备是否真的产生了中断
    • 中断线共享必须满足:中断线当前未被注册或在该线上的所有已注册处理程序都指定了IRQF_SHARED

5. 中断上下文

当执行一个中断处理程序时,内核处于中断上下文(interrupt context)

中断上下文不可以睡眠,具有较严格的时间限制,因为:中断处理程序打断了其他的代码。由于异步执行的关系,中断处理程序应该迅速、简洁,尽量把工作从中断处理程序中分离。

中断处理程序拥有自己的中断栈。

6. 中断处理机制

中断处理的过程主要包含:

Linux内核提供了一组接口用于操作机器上的中断状态,对于一个多处理器内核,内核代码通过锁防止来自其他处理器的并发访问,通过禁止中断防止来自其他中断处理程序的并发访问。

中断控制方法列表

函数 说明
local_irq_disable() 禁止本地中断传递
local_irq_enable() 激活本地中断传递
local_irq_save() 保存本地中断传递的当前状态,然后禁止本地中断传递
local_irq_restore() 恢复本地中断传递到给定状态
disable_irq() 禁止给定中断线,并确保该函数返回之前在该中断线上没有处理程序在运行
disable_irq_nosync() 禁止给定中断线
enable_irq() 激活给定中断线
irqs_disabled() 如果本地中断传递被禁止,返回非0,否则返回0
in_interrupt() 如果在中断上下文中,返回非0;在进程上下文中,返回0
in_irq() 如果当前正在执行中断处理程序,返回非0;否则返回0