中断和中断处理
处理器和外部硬件的速度往往不在一个数量级,如何高效地让处理器和这些外部设备协调工作?
1. 中断
处理器和外部设备协调的方式,从处理器出发,很容易想到的是轮询的方式,但是轮询肯定会做很多无用功;所以应该从外部设备角度,让硬件在需要的时候向内核发出信号,即中断机制。
中断本质上是一种特殊电信号,由硬件设备产生,直接送入中断控制器输入引脚,再发向处理器,处理器接收到中断马上向操作系统反映信号的到来,然后由操作系统执行对应的中断处理程序。
每个中断都有一个中断号,被称为中断请求IRQ。
中断与异常
异常产生需要考虑处理器时钟同步,常常被称为同步中断。这样我们可以把中断分为异步中断和同步中断.
2. 中断处理程序
响应一个特定中断时,内核会执行对应的中断处理程序(interrupt handler)或中断服务例程(interrupt service routine, ISR),一个设备的中断处理程序是它设备驱动程序的一部分。
中断程序程序运行在中断上下文,这个上下文中执行的代码不能阻塞,而且中断随时可能发生,必须保证中断处理程序快速执行。又想中断处理程序运行快,又想中断处理程序完成的工作量多,这是矛盾的,为了解决这个矛盾,中断被分成了两个部分:
- top half:中断到来就立即执行,但只做严格时限的工作,如对接收的中断进行应答或复位硬件,这些工作都是在所有中断被禁止的情况下完成。
- bottom half:允许稍后完成的工作会放在这部分,在合适的时机,被执行。
3. 注册中断处理程序
如果设备要使用中断,那么相应的驱动程序就要注册一个中断处理程序。
驱动程序通过request_irq()函数注册一个中断处理程序,linux/interrupt.h
|
|
实际中断处理函数的原型为:
1typedef 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. 中断处理机制
中断处理的过程主要包含:
- do_IRQ
- handle_IRQ_event
- ret_from_intr
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 |