中断类型
根据中断的来源分为外部中断和内部中断,外部中断来源于CPU外部,由外设发出,内部中断就是CPU内部触发(软件中断指令、溢出、除法错误等)。根据中断能否被屏蔽分为可屏蔽中断和不可屏蔽中断,可屏蔽中断就是可以被屏蔽,被屏蔽之后中断不再响应。不可屏蔽中断就是不能被屏蔽。
中断的申请/释放函数
申请irq函数:
int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long flags, const char *dev_name, void *dev_id);
/*
irq:中断号
irqreturn_t (*handler):中断处理函数
flags:中断管理位掩码
dev_name:请求中断的设备的名称,显示在/proc/interrupts
dev_id:共享中断时用,一般设置为这个设备的设备结构体或者NULL
*/
参数flags可以为0,也可能是下列一个或多个标志的位掩码。其定义在<linux/interrupt.h>。
#define IRQF_TRIGGER_NONE 0x00000000
#define IRQF_TRIGGER_RISING 0x00000001 //上升沿触发
#define IRQF_TRIGGER_FALLING 0x00000002 //下降沿触发
#define IRQF_TRIGGER_HIGH 0x00000004 //高电平触发
#define IRQF_TRIGGER_LOW 0x00000008 //低电平触发
#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)
#define IRQF_TRIGGER_PROBE 0x00000010
#define IRQF_SHARED 0x00000080 //允许在多个设备之间共享IRQ
#define IRQF_PROBE_SHARED 0x00000100 //由呼叫者在期望发生共享不匹配时设置
#define __IRQF_TIMER 0x00000200
#define IRQF_PERCPU 0x00000400
#define IRQF_NOBALANCING 0x00000800
#define IRQF_IRQPOLL 0x00001000
#define IRQF_ONESHOT 0x00002000 //Hardirq处理程序完成后,中断不会重新启用。由需要在线程处理程序运行之前保持IRQ行禁用的线程中断使用
#define IRQF_NO_SUSPEND 0x00004000 //在挂起期间不要禁用此IRQ。不保证此中断将使系统从暂停状态唤醒。
#define IRQF_FORCE_RESUME 0x00008000
#define IRQF_NO_THREAD 0x00010000 //中断不能被线程化
#define IRQF_EARLY_RESUME 0x00020000
#define IRQF_COND_SUSPEND 0x00040000
#define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD) //将此中断标记为计时器中断的标志
释放irq函数:
void free_irq(unsigned int irq, void *dev_id);
中断处理前/后半部
因为中断可能随时发生,因此中断处理程序也就随时可能执行,所以必须保证中断处理程序能够快速执行,这样才能保证尽可能快的恢复中断你代码的执行。又想中断处理程序运行的快,又想中断处理程序完成的工作量多,为了满足这两个要求,一般将中断处理程序分为两个部分。
中断上半部接收到一个中断,立即执行,只做严格的限时工作,其他耗时操作留到下半部执行。实现下半部可以使用tasklet或者工作队列。
中断线程化
在 Linux 中,中断具有最高的优先级。不论在任何时刻,只要产生中断事件,内核将立即执行相应的中断处理程序,等到所有挂起的中断和软中断处理完毕后才能执行正常的任务,因此有可能造成实时任务得不到及时的处理。中断线程化之后,中断将作为内核线程运行而且被赋予不同的实时优先级,实时任务可以有比中断线程更高的优先级。这样,具有最高优先级的实时任务就能得到优先处理,即使在严重负载下仍有实时性保证。但是,并不是所有的中断都可以被线程化,比如时钟中断,主要用来维护系统时间以及定时器等,其中定时器是操作系统的脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当被线程化。
中断线程化使用的函数如下:
request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn,
unsigned long flags, const char *name, void *dev);
与request_irq函数的区别就是差一个thread_fn,此处如果传递NULL,表示没有中断线程化。事实上在linux/include/interrupt.h中看request_irq的实现如下:
static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}
request_irq函数就是将request_threaded_irq的thread_fn设置为NULL。
使能和禁止中断
为何要禁止中断?
因为中断的高优先级,一旦触发 了中断,系统就会进入中断处理函数。理想状态下,中断触发的时候,前一次的中断处理函数已经执行完成。如果前面一个中断处理函数没有处理完成,那么就会导致中断嵌套。为了避免出现这种情况,需要在处理中断的时候禁止中断,处理完成之后再使能中断。
什么叫中断嵌套?
是指中断系统正在执行一个中断服务时,有另一个优先级更高的中断提出中断请求,这时会暂时终止当前正在执行的级别较低的中断源的服务程序,去处理级别更高的中断源,待处理完毕,再返回到被中断了的中断服务程序继续执行,这个过程就是中断嵌套。
禁止所有中断
用于禁止当前处理器(仅仅是当前处理器)上的本地中断,随后又激活它们的函数如下,定义在<asm/system.h>
void local_irq_save(unsigned long flags); /* 保存中断状态到flags,flags是值传递,不是通过指针 */
void local_irq_disable(void); /* 关闭本地中断递交而不保存状态 */
void local_irq_restore(unsigned long flags); /* 恢复由local_irq_save存储于flags的中断状态 */
void local_irq_enable(void); /* 无条件的打开中断,不管之前的中断状态如何 */
禁止指定的中断
某些情况下,只需要禁止一条中断线就够了。使用下面的函数:
void disable_irq(unsigned int irq); /* 禁止给定的中断, 还等待一个当前执行的中断处理结束 */
void disable_irq_nosync(unsigned int irq); /* 立刻返回,但是可能使你的设备有竞争情况 */
void enable_irq(unsigned int irq); /* 使能中断 */
int enable_irq_wake(unsigned int irq); /* 使能中断,不仅在系统正常运行状态下产生的IRQ会上报,同时在系统休眠的情况下产生的IRQ会上报 */
disable_irq不能放在中断处理函数中,系统可能会出现死锁状态,disable_irq_nosync可以在中断处理函数中使用。在中断处理函数完成所有的工作之后,要主动调用enable_irq,否则该中断将一直处于禁止状态。
示例
后续补上