中断线程化与强制中断线程化
1 中断线程化的概念
中断具有最高的优先级,当有中断产生时,CPU会暂停当前的执行流程,转而去执行中断处理程序。硬件中断处理过程中会关掉中断,如果此时有其它中断产生,那么这些中断将无法及时得到处理,这也是导致内核延迟的一个重要原因。另外,中断优先级比进程高,一旦有中断产生,无论是普通进程还是实时进程都要给中断让路,如果中断处理耗时过长,则会严重影响到系统的实时性。因此内核设计的目标是将关中断状态下需要执行的工作量尽量压缩到最低限度。
传统的中断处理流程由两部分处理逻辑协同完成,“上半部(top half)”负责实际的对硬件中断的响应处理,“下半部(bottom half)”由 上半部负责调度并执行额外的处理。上半部在禁用中断的情况下执行,因此必须尽可能地快,从而不会给系统响应造成太大的延迟。比如说网卡驱动在上半部完成一些硬件设置或数据收发,在下半部完成网络数据处理。但是这种设计,上半部的运行时长也是不够确定的,受各驱动实现的影响。
中断线程化后进一步压缩了上半部的工作量,上半部的工作仅仅需要完成 “快速检查”,譬如确保中断的确来自期望的设备;如果检查通过,它将对硬件中断完成确认并通知内核唤醒中断处理线程完成中断处理的下半部。
降低内核中的延迟只是中断线程化带来的好处之一,除此之外它还具备其他方面的优点。其中最突出的一点是:由于中断线程化后简化乃至避免了整个中断处理流程中 “关中断处理(术语上称之为 “hard” 部分)” 和 “开中断处理(术语上称之为 “soft” 部分)” 两个阶段之间可能涉及的锁同步机制,从而降低了整体实现上的复杂性。中断处理线程化还有助于内核的调试。
2 中断线程化的使用
如果一个驱动希望将其中断处理线程化,那么可以调用如下函数接口:
函数request_threaded_irq和传统函数request_irq的形式基本一致,只是多了一个thread_fn参数,thread_fn表示线程化的中断服务函数。比如omap4-keypad.c的代码如下,omap4_keypad_irq_handler是上半部的服务函数,omap4_keypad_irq_thread_fn是线程化的中断服务函数。
上半部服务函数做的工作很简单,只是判定是否是对应的中断,如果是,则返回IRQ_WAKE_THREAD唤醒中断处理线程;如果不是,则返回IRQ_NONE,完成此次中断处理。
线程化的中断服务函数则作实际的中断处理工作,例如读取按键扫描值,上报给INPUT子系统等。
如果直接将第二个参数handler置为NULL,则当中断产生时,内核会自动唤醒中断处理线程,并回调线程化的中断服务函数。这是因为request_threaded_irq函数检测到hander为NULL时,会自动注册一个缺省的handler,而缺省的handler就是简单返回IRQ_WAKE_THREAD。
实际上,不少驱动都是将第二个参数handler置为NULL:
3 中断线程化的内部实现
中断线程化,是通过实时线程来实现。调用request_threaded_irq过程中会调用到__setup_irq函数,该函数判断thread_fn不为NULL时,会进一步调用setup_irq_thread函数来创建实时线程。
中断处理线程会等待中断唤醒自己,唤醒后回调线程化中断处理函数thread_fn。irq_wake_secondary是为了处理中断handler也被强制线程化的情况,此时,handler和thread_fn都由实时线程来回调。相关细节会在本文“强制中断线程化”章节讲述。
中断唤醒中断处理线程的流程如下,__handle_irq_event_percpu由内核中断处理流程调用,该函数会回调由request_threaded_irq注册的中断handler,如果handler返回IRQ_WAKE_THREAD,则会唤醒中断处理线程。
以上就是内核实现中断线程化的关键流程。
4 强制中断线程化
回顾一下线程化中断注册函数的原型,它第二个参数是handler,第三个参数是thread_fn。前面讲的是内核如何将thread_fn线程化,那handler有没有可能也被线程化呢?答案是肯定的,内核提供了强制中断线程化的机制。
内核源码通过CONFIG_IRQ_FORCED_THREADING来控制是否把强制中断线程化的功能编译进内核。实际上是通过启动参数threadirqs来控制,启动参数threadirqs又控制了force_irqthreads变量是否为true。
__setup_irq函数会调用irq_setup_foced_threading函数,该函数会判断当force_irqthreads变量为true,且该irq的flag没有带IRQF_NO_THREAD标志时,则会创建一个新的secondary irqaction来接管当前irqacton的thread_fn,当前的thread_fn则接管自己的handler,自己的handler则被强制赋值为系统缺省的handler,完成hanlder和thread_fn两者的线程化。这个系统缺省的hanlder前文有讲述,其函数实现就是返回IRQ_WAKE_THREAD。这段话比较绕,可以结合实际代码理解,如下图。
5 试验【强制中断线程化】
用QEMU验证强制中断线程化,实验结果如下。
用QEMU启动内核image后,发现只有名为ksoftirqd/x的处理软中断的内核线程,并没有出现其它驱动主动申请的线程化中断。如果有驱动申请了线程化的中断,理应会出现名为irq/xx的线程(setup_irq_thread函数创建线程时会按此规则命名,详见前文)。
用同一个内核image,启动参数加上threadirqs,内核会出现多个名为irq/xx的线程。
以其中的irq/38-uart-pl0为例,查看其驱动源码,确实是申请的传统中断,而没有申请线程化中断。所以默认不会被线程化,只有当启动参数配置了threadirqs,才会被强制线程化。
------ END ------
作者:bigfish99
博客:https://www.cnblogs.com/bigfish0506/
公众号:大鱼嵌入式