Why
中断线程化(Threaded IRQs)的概念在 Linux 内核中的发展与采用可以追溯到 2008 年。中断线程化背后的主要动力是提高实时性能(real-time performance)和响应性,特别是对于实时(real-time)和嵌入式(embedded)系统。
在中断线程化之前,中断处理程序(Interrupt Service Routines, ISRs)直接在中断上下文中运行,这会导致一些问题:
中断上下文不能被抢占,因此中断处理程序需要尽快执行并返回。较长的中断处理可能导致系统的实时性能下降和响应延迟。
中断处理程序运行时,禁止了相应的中断。这可能导致中断丢失或中断处理延迟。
中断处理程序中不能使用导致调度的内核 API,如互斥锁(mutex)或睡眠等待队列(sleeping wait queues)。
中断线程化解决了这些问题,它将中断处理程序分为两部分:
一个快速的硬件中断处理程序(Fast hardware ISR),负责处理硬件中断、禁用中断源,并将剩余工作传递给线程中断处理程序。
一个线程中断处理程序(Threaded IRQ handler),在独立的线程上下文中运行,可以被抢占、使用互斥锁等内核 API。
中断线程化提供了以下优势:
改善实时性能和响应性,因为线程化中断处理程序可以被抢占,从而允许其他高优先级任务在必要时抢占执行。
允许在中断处理程序中使用内核 API,如互斥锁等,简化了驱动程序的设计和同步。
How
核心函数irq_thread
irq_thread
是线程化中断处理程序的核心函数,负责执行线程化中断处理。下面是irq_thread
函数的实现,我为代码添加了详细的注释,以帮助理解其工作原理。
static int irq_thread(void *data)
{
/* 'data' 参数包含中断描述符的指针,其中包括中断处理程序信息 */
struct irq_desc *desc = data;
struct irqaction *action = desc->action;
/* 获取线程化中断处理程序的指针 */
irqreturn_t (*handler)(int, void *) = action->handler;
irqreturn_t ret;
/* 无限循环,直到收到线程停止信号 */
while (!kthread_should_stop()) {
/* 等待硬中断处理程序唤醒本线程
* 当硬中断处理程序返回 IRQ_WAKE_THREAD 时,会将 'desc->irqs_queued' 计数器递增。
* 这个线程会等待计数器变为非零值。 */
wait_event_interruptible(desc->wait_for_irq, desc->irqs_queued);
/* 调用线程化中断处理程序处理中断 */
ret = handler(action->irq, action->dev_id);
/* 中断处理完成后,检查处理程序是否正确处理了中断 */
if (ret == IRQ_HANDLED) {
/* 如果线程化中断处理程序返回 IRQ_HANDLED,表示中断已被正确处理。
* 这时,我们递减 'desc->irqs_queued' 计数器。 */
atomic_dec(&desc->irqs_queued);
}
}
/* 线程正常退出,返回 0 */
return 0;
}
irq_thread
函数首先从传入的data参数中获取中断描述符(irq_desc
)以及中断处理程序(handler)的信息。然后,它进入一个无限循环,直到收到线程停止信号。
在循环内,wait_event_interruptible
函数使线程进入等待状态,直到硬中断处理程序将desc->irqs_queued
计数器递增。这通常在硬中断处理程序完成处理并返回IRQ_WAKE_THREAD
时发生。一旦线程被唤醒,它将调用线程化中断处理程序来处理中断。
如果线程化中断处理程序返回IRQ_HANDLED
,表明中断已被正确处理,irq_thread
函数会递减desc->irqs_queued
计数器。这样,只要还有未处理的中断,线程就会继续处理它们。
当线程收到停止信号(例如,当设备驱动程序卸载时),kthread_should_stop
函数返回true,使irq_thread函数跳出循环并返回0,表示线程已正常退出。
irq_thread
核心函数来自哪里?Who create it
irq_thread
函数由内核线程调度器负责调用。当设备驱动程序通过request_threaded_irq
函数请求线程化中断时,内核将创建一个新的内核线程来处理线程化中断。这个内核线程将运行irq_thread
函数。
在request_threaded_irq
函数中,会创建一个kthread(内核线程)来执行irq_thread函数。下面是request_threaded_irq
函数相关的部分实现:
struct irqaction *new_action;
struct task_struct *t;
/* ... */
/* 创建一个新的内核线程来执行 irq_thread 函数。'irq' 是中断号,'desc' 是对应的中断描述符。*/
t = kthread_create(irq_thread, desc, "irq/%d-%s", irq, new_action->name);
if (IS_ERR(t)) {
/* 错误处理 */
}
/* 设置内核线程的优先级 */
sched_setscheduler_nocheck(t, SCHED_FIFO, param);
/* 唤醒新创建的内核线程 */
wake_up_process(t);
request_threaded_irq
函数首先通过kthread_create
函数创建一个新的内核线程,该线程将运行irq_thread
函数。kthread_create
的第一个参数是将要运行的函数,即irq_thread
,第二个参数是传递给irq_thread
的参数,即中断描述符desc。
在创建新的内核线程之后,request_threaded_irq
函数会设置线程的优先级(通过sched_setscheduler_nocheck
函数)并唤醒线程(通过wake_up_process
函数)。这样,新创建的内核线程就开始执行irq_thread
函数,并传递desc作为data参数。
综上所述,irq_thread
函数由内核线程调度器负责调用,data参数来源于request_threaded_irq
函数,其中包含了中断描述符。
核心函数kthread_should_stop
kthread_should_stop
是内核线程中一个关键函数,用于检查线程是否被通知停止。当内核线程需要被结束时,通常会调用kthread_stop
函数。这会触发一个标志,kthread_should_stop
可以检查这个标志来决定线程是否应该退出。
以下是一个简单的内核线程示例,用于演示 kthread_should_stop 的用法:
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/delay.h>
static struct task_struct *example_thread;
// 示例内核线程函数
static int example_thread_func(void *data)
{
// 在线程中的主循环
while (!kthread_should_stop()) // 检查线程是否应该停止
{
printk(KERN_INFO "example_thread_func: 运行中\n");
ssleep(1); // 休眠1秒
}
printk(KERN_INFO "example_thread_func: 线程即将停止\n");
return 0;
}
// 加载模块时创建内核线程
static int __init example_init(void)
{
printk(KERN_INFO "example_init: 初始化模块\n");
example_thread = kthread_run(example_thread_func, NULL, "example_thread");
if (IS_ERR(example_thread))
{
printk(KERN_ERR "example_init: 无法创建内核线程\n");
return PTR_ERR(example_thread);
}
return 0;
}
// 卸载模块时停止内核线程
static void __exit example_exit(void)
{
printk(KERN_INFO "example_exit: 卸载模块\n");
kthread_stop(example_thread); // 停止内核线程
}
module_init(example_init);
module_exit(example_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Kthread 示例");
在这个示例中,我们创建了一个内核线程 example_thread
,线程函数为 example_thread_func
。在 example_thread_func
函数中,我们使用 kthread_should_stop
检查线程是否应该停止。当我们卸载模块时,example_exit
函数会被调用,它使用 kthread_stop
停止内核线程。这会导致 kthread_should_stop
返回 true,从而使线程函数退出循环并结束线程。
一个例子,如何使用
存在的问题,talks more
线程化中断处理程序的调度和上下文切换可能导致一定程度的性能开销。
在某些情况下,过度依赖线程化中断处理程序可能导致系统的整体响应性能下降,尤其是在高负载或大量中断源的场景下。
线程化中断处理可能不适用于所有类型的中断,例如对于高实时性要求的中断仍然需要使用传统的硬件中断处理程序。
总之,中断线程化在 Linux 内核中的引入解决了实时性能和响应性问题,同时允许在中断处理程序中使用更丰富的内核 API。然而,它可能导致一定程度的性能开销,并且在某些场景下可能不适用于所有类型的中断。
总结、概括
尽管存在这些问题,中断线程化在很多实时和嵌入式系统领域已经变得非常普遍。许多驱动程序和系统已经成功地采用了线程化中断处理程序,从而改善了系统的实时性能和响应性。
为了充分利用中断线程化的优势并减少潜在问题,驱动开发人员和系统设计者需要考虑以下因素:
仅对需要实时性能和响应性的中断使用线程化处理程序。对于不需要实时性能的中断,可以继续使用传统的硬件中断处理程序。
仔细权衡线程化中断处理程序的优先级,以确保高优先级任务可以及时抢占执行。
在选择使用线程化中断处理程序时,注意避免不必要的上下文切换和调度开销。在适当的情况下,可以考虑使用其他实时调度策略,如 SCHED_FIFO 或 SCHED_RR。
针对具体系统和应用场景,对中断线程化处理程序进行性能分析和调优,以实现最佳性能和响应性。
通过合理地利用中断线程化技术,系统设计者和驱动开发人员可以在提高实时性能和响应性的同时,尽量减少潜在的性能开销和问题。
参考文献
以下是关于中断线程化的一些建议阅读的论文。有些论文可能涉及到实时操作系统、Linux内核优化等相关领域,它们都可以为你提供中断线程化的深入理解和启发:
Mogul, Jeffrey C., and K. K. Ramakrishnan. “Eliminating receive livelock in an interrupt-driven kernel.” ACM Transactions on Computer Systems (TOCS) 15.3 (1997): 217-252.
Nieh, Jason, and Monica S. Lam. “The design, implementation and evaluation of SMART: A scheduler for multimedia applications.” ACM SIGOPS Operating Systems Review 31.5 (1997): 52-63.
Duda, K. J., and D. R. Cheriton. “Borrowed-virtual-time (BVT) scheduling: supporting latency-sensitive threads in a general-purpose scheduler.” ACM SIGOPS Operating Systems Review 33.5 (1999): 261-276.
Brandenburg, Bjorn B., and James H. Anderson. “Scheduling soft-real-time applications on multiprocessors: A decoupled approach.” Proceedings of the 2009 ACM SIGPLAN/SIGBED conference on Languages, compilers, and tools for embedded systems. 2009.
Bletsas, Konstantinos, and Björn Andersson. “Preemption-light multiprocessor scheduling of sporadic tasks with high utilisation bound.” Real-Time Systems Symposium, 2009, RTSS 2009. 30th IEEE. IEEE, 2009.
Calandrino, John M., and James H. Anderson. “On the design and implementation of a cache-aware soft real-time scheduler for multicore platforms.” Proceedings of the 14th IEEE International Conference on Embedded and Real-Time Computing Systems and Applications (RTCSA). 2008.
Goyal, Pawan, Xingang Guo, and Harrick M. Vin. “A hierarchical CPU scheduler for multimedia operating systems.” ACM SIGOPS Operating Systems Review 30.5 (1996): 107-121.
Devi, UmaMaheswari C., and James H. Anderson. “Bounding tardiness under global EDF scheduling on a multiprocessor.” Real-Time Systems Symposium, 2005. (RTSS 2005). 26th IEEE International. IEEE, 2005.
Leontyev, Hennadiy, and James H. Anderson. “Tardiness bounds for global EDF scheduling on a multiprocessor.” Real-Time Systems Symposium, 2006. RTSS’06. 27th IEEE International. IEEE, 2006.
Marquet, Pascal, et al. “LITMUS^ RT: A status report.” Proceedings of the 9th Real-Time Linux Workshop. 2007.
Nieh, Jason, et al. “A comparison of Internet-scale workloads on Linux and Solaris.” ACM SIGMETRICS Performance Evaluation Review 29.4 (2002): 41-49.
Brandenburg, Bjorn B., et al. “A comparison of scheduling latency in Linux, PREEMPT-RT, and LITMUS^ RT.” 13th Real