https://blog.csdn.net/mcsbary/article/details/103728816
总结:
request_threaded_irq:中断处理线程化,很好解决高频率的中断响应,类似异步处理
request_irq:类似于同步处理事务,适合非高频率中断响应。
https://zhuanlan.zhihu.com/p/151888181
Linux 中断处理分为顶半部(top half)和底半部(bottom half),一般要求在顶半部里处理优先级比较高的事情,处理时间应尽量短,在处理完成后就激活底半部,在底半处部理其余任务。底半部的处理方式主要有soft_irq, tasklet, workqueue三种,它们的使用方式和适用情况各有不同。
soft_irq 用在对执行时间要求比较紧急或者非常重要的场合。 tasklet 和 work queue 在普通的driver里用的较多,主要区别是tasklet是在中断环境中执行,而work queue 则是在进程中执行,因此可以使用sleep()。
Linux 中断的优先级比进程高,一旦中断过来普通进程实时进程通通都要给中断处理程序让路,如果顶半部处理任务较多就会对实时进程造成很大的影响,并且这种影响存在较大的不确定性因此难以准确评估。为了解决这些实时性相关问题,Linux RT_PREEMPT 补丁引入了中断线程化的机制。在新的机制中,中断虽然还会打断实时进程,但中断处理程序所执行的操作仅仅是唤醒中断线程,原本的中断服务程序主体放到一个内核线程中延迟执行,这样中断执行的速度就很快也很确定,实时进程被打断后可以迅速再次运行,而中断服务程序会在实时进程挂起之后被系统调度执行。
Linux 2.6.30里,在ingo molnar的RT tree里存在有一段时间的interrupt thread终于merge到mainline了。此时如果使用request_threaded_irq申请的中断,handler 不是在中断环境里执行,而是在新创建的线程里执行,这样该handler非常像执行workqueue,拥有所有work queue的特性,但是省掉了创建、初始化、调度workqueue的步骤,处理起来非常简单。
让我们看看这个接口:
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)
其中,
irq 是中断号,
handler 是在发生中断时首先要执行的处理程序,非常类似于顶半部,该函数最后会返回 IRQ_WAKE_THREAD 来唤醒中断线程。handler 一般设为NULL,用系统提供的默认处理。
thread_fn 是要在线程里执行的处理程序,非常类似于底半部。
后三个参数基本和request_irq相同。
irqsflags 新增加了一个标志IRQF_ONESHOT,用来声明需要在中断线程执行完后才能打开该中断。对于电平有效的中断类型,如果不设置该标志可能会出现死锁情形:CPU 执行完成顶半部代码后会打开中断,此时如果中断信号电平没有变化(中断源在等待CPU发出reset信号),CPU会立刻重复响应该中断,导致永远没有机会进入线程处理,也就永远不会reset该中断。
下边一个实际例子来说明它的应用。在手机平台中,检测耳机的插入一般是通过耳机插孔中机械变化导致一个baseband gpio的电平的变化,在该gpio中断里进行耳机插入处理。但是耳机插入一般都有个抖动的过程,需要消抖处理。最简单的办法是在中断发生后,延时一段时间(例如200ms),然后再检查GPIO状态是否稳定来确定是否有效插入。如果用老的中断方式,不得不用workqueue的方式,你需要在顶半里激活一个delay 200ms的workqueue,然后在workqueue里检查。如果用线程化的处理方式,则只需要在thread_fn里sleep 200ms,然后再检查即可。
有一个项目对实时性要求比较高,于是在linux内核上打了RT_PREEMPT补丁。最终碰到的一个问题是,芯片本身性能不强,CPU资源不足,急需优化。
看了下cpu占用率,除了主应用之外,有一个名为irq/38-twi0的进程引起了我们的注意,因为它竟然占据了10%的cpu。
这个irq开头的进程是做什么的呢?原来这是一个被线程化了的中断服务程序,负责处理i2c中断的。这个项目i2c总线上挂载了多个设备,压力是比较大的。
第一个想法是能否减少设备数量或者减低采集频率,但这会影响到应用的算法表现,暂时不考虑。
第二个想法是优化代码,但打开中断服务程序的源码一看,其实现非常简单,基本就只是从硬件寄存器中把接收到的数据取出来而已,看来从这里入手也希望不大。
再仔细想想,这个进程执行的操作这么简单,CPU占用率却这么高,那么主要就是因为其执行的频率过高,每次执行其实都会伴随着进程调度以及上下文切(context switching)换带来的开销,这部分是否可以进行优化呢?
从中断线程化的初衷看,当前这种场景根本就不适用中断线程化。
2. 这个中断服务程序非常关键,其中采集的数据的实时性也非常重要,不应该被延迟执行。中断切换回实时进程后,实时进程依赖这些数据,还是要等这个进程把数据取出。
解决方式很简单,对于这个具体的中断,取消线程化,让它变回一个朴素的中断。中断线程化的机制虽好,也要分情况来使用,不然反而会造成系统的巨大负担。
代码改动是在request_irq时,传入IRQF_NO_THREAD标志,即可避免这个中断被线程化。
实际做改动还要注意,RT_PREEMPT使用rt_mutex代替传统的禁用抢占的spin_lock,因此如果需要用真正的spin_lock,需要使用raw_spin_lock_t
在这个具体的例子中,中断大概为一万两千次/秒,取消线程化之后,CPU占用率下降了约10%,效果显著。