Linux系统对中断处理的处理

360 阅读3分钟

本文已参与【新人创作礼】活动,一起开启掘金创作之路。

Linux中断系统变化并不大,比较重要新的是引入内核线程threaded_irq来处理中断。

硬件中断

Linux系统把中断的意义扩展了,对于按键中断等硬件产生的中断,称之为“硬件中断”(hard irq)。 不同的硬件又不同的中断号,对应的中断处理函数也不同。 在这里插入图片描述

软件中断

还可以人为地制造中断:软件中断(soft irq),如下图所示: 在这里插入图片描述

中断相关的代码

//include/linux/interrupt.h
enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	IRQ_POLL_SOFTIRQ,
	TASKLET_SOFTIRQ,
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ, /* Unused, but kept as tools rely on the numbering. Sigh! */
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
	NR_SOFTIRQS
};
/* 设置softirq_veq[nr]的标记位 */
extern void raise_softirq(unsigned int nr);
/* 设置软件中断的处理函数 */
extern void open_softirq(int nr, void (*action)(struct softirq_action *));

中断处理原则

不能嵌套

官方资料 中断处理函数触发时,需要把当前的函数压入栈中。如果中断能够嵌套,那么如果短时间内大量中断。 栈会迅速耗尽,为了防止这种情况的发生,也为了简单处理中断,Linux系统中断无法嵌套。 即当前中断没有被处理完前,不会处理另一个中断。

越快越好

因为中断的处理函数,CPU不能进行调度(无法嵌套)。所以在中断处理函数中,应该越简单越好。 保证尽快的被处理,进程调度靠定时器中断来实现。 在Linux中使用下面的函数来注册中断处理函数:

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
	    const char *name, void *dev);

中断处理过程

如果事情太多,拆分为上半部、下半部

在这里插入图片描述

如果中断需要做的事情过于复杂,那么我们应该把中断要做的事情进行切分:紧急的、不紧急的。 下半部实现的方式很多种,主要有taskletwork queue

下半部简单:tasklet(小任务)

当下半部比较耗时但是能忍受,并且它的处理比较简单时,可以用tasklet来处理下半部。tasklet是使用软件中断来实现。 在这里插入图片描述 一些主要的中断处理流程: 在这里插入图片描述

下半部负责:workqueue(工作队列)

内核线程是系统是系统帮忙创建的,一般是kworker线程: 在这里插入图片描述

kworker线程要去"工作队列"(work queue)取出一格个"work",来执行它里面的函数。

//创建work函数:
static DECLARE_WORK(aer_recover_work, aer_recover_work_func);
//第一个参数是work结构体,第二个参数是要执行的函数

//要执行这个函数时,work提交给work queue
schedule_work(&aer_recover_work);
//函数会把work提供给系统默认的work queue:system_wq,它是一个队列
//当线程抢到时间来运行时,就会从work队列中获取函数并执行

//所以想要把需要的work提交到work queue
//需要在中断上半部中,调用schedule_work函数

总结:

  1. 很耗时的中断处理,应该放到线程里去
  2. 可以使用work、work queue
  3. 在中断上半部调用schedule_work函数,触发work的处理
  4. 既然是在线程中运行,那对应的函数可以休眠。

新技术(thread_irq)

/* irq:哪个中断 
 * handler:上半部函数,可以为空
 * thread_fn:在线程里运行的函数
 */
int request_threaded_irq(unsigned int irq, irq_handler_t handler,
             irq_handler_t thread_fn, unsigned long irqflags,
             const char *devname, void *dev_id);

可以只提供thread_fn,系统会为这个函数创建一个内核线程。发生中断时,内核线程就会执行这个函数。 为每一个中断都创建一个内核线程,多个中断的内核线程可以分配到多个CPU上执行,提高了效率。