Linux源代码阅读:request_irq()
本文尝试阅读Linux kernel 5.0 + ARM64上中断申请过程的源代码,但并非逐字逐句地解析,只是梳理一些关键点(在工作中经常可能会用到的知识点)。为了提升Linux的实时性,kernel引入了中断线程化概念。其实就是将中断的下半部放在内核线程(FIFO,也称实时进程)中执行,这样可以减少中断对高优先级进程的饥饿感。因为传统的中断上半部执行完成后,需要通过tasklet,或者softi
本文尝试阅读Linux kernel 5.0 + ARM64上中断申请过程的源代码,但并非逐字逐句地解析,只是梳理一些关键点(在工作中经常可能会用到的知识点)。
为了提升Linux的实时性,kernel引入了中断线程化概念。其实就是将中断的下半部放在内核线程(FIFO,也称实时进程)中执行,这样可以减少中断对高优先级进程的饥饿感。因为传统的中断上半部执行完成后,需要通过tasklet,或者softirq来执行中断下半部,这些都属于中断上下文,会被内核优先处理,这样就会导致进程产生饥饿,即使是优先级比较高的实时进程。
一、request_irq()--->request_threaded_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,即中断线程化
二、request_threaded_irq()
1)desc = irq_to_desc(irq):根据linux内核中断号(并非hwirq中断号)得到对应desc,因为在解析devicetree时,已经为每个hwirq分配了对应的linux内核中断号,并分配了irq_desc结构体。
2)判断handler和thread_fn是否为NULL,如果同时为NULL,则直接异常退出,如果handler为NULL,thread_fn不为NULL,则默认使用irq_default_primary_handler
if (!handler) {
if (!thread_fn)
return -EINVAL;
handler = irq_default_primary_handler;
}
3)分配并填充对应irqaction结构体
action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
if (!action)
return -ENOMEM;
action->handler = handler;
action->thread_fn = thread_fn;
action->flags = irqflags;
action->name = devname;
action->dev_id = dev_id;
4)__setup_irq(irq, desc, action):将irq,desc,新分配的irqaction建立关联,且听下文详解
三、__setup_irq()
1)new->irq = irq:将linux内核中断号和irqaction结构体关联起来
2)创建对应的中断线程,且听下文详解
/*
* Create a handler thread when a thread function is supplied
* and the interrupt does not nest into another interrupt
* thread.
*/
if (new->thread_fn && !nested) {
ret = setup_irq_thread(new, irq, false);
if (ret)
goto out_mput;
}
3) 判断是否share类型的中断
old_ptr = &desc->action;
old = *old_ptr;
if (old) {
.....
}
4)如果为非share类型中断,则先创建一个等待队列,再设置中断触发类型,并激活中断
if (!shared) {
init_waitqueue_head(&desc->wait_for_threads);
/* Setup the type (level, edge polarity) if configured: */
if (new->flags & IRQF_TRIGGER_MASK) {
ret = __irq_set_trigger(desc,
new->flags & IRQF_TRIGGER_MASK);
if (ret)
goto out_unlock;
}
/*
* Activate the interrupt. That activation must happen
* independently of IRQ_NOAUTOEN. request_irq() can fail
* and the callers are supposed to handle
* that. enable_irq() of an interrupt requested with
* IRQ_NOAUTOEN is not supposed to fail. The activation
* keeps it in shutdown mode, it merily associates
* resources if necessary and if that's not possible it
* fails. Interrupts which are in managed shutdown mode
* will simply ignore that activation request.
*/
ret = irq_activate(desc);
if (ret)
goto out_unlock;
}
5)*old_ptr = new:将irqaction赋值给desc->action
6) 将irq_thread线程唤醒
/*
* Strictly no need to wake it up, but hung_task complains
* when no hard interrupt wakes the thread up.
*/
if (new->thread)
wake_up_process(new->thread);
四、 setup_irq_thread()
1) 调用kthread_create()创建内核线程irq_thread
t = kthread_create(irq_thread, new, "irq/%d-%s", irq,
new->name);
2) 将标为FIFO实时进程,进程优先级为 100/2 = 50 (位于0-99的中间位置)
struct sched_param param = {
.sched_priority = MAX_USER_RT_PRIO/2,
};
sched_setscheduler_nocheck(t, SCHED_FIFO, ¶m);
3) 将创建的线程与irqaction关联起来
new->thread = t;
五、irq_thread()
1) 将进程设置为TASK_INTERRUPT状态,等中断到来,然后schedule(),通过判断IRQTF_RUNTHREAD标志是否被设置,因为在action->handler()执行后,如果需要唤醒中断线程,会主动设置IRQTF_RUNTHREAD标志。
while (!irq_wait_for_interrupt(action)) {
}
static int irq_wait_for_interrupt(struct irqaction *action)
{
for (;;) {
set_current_state(TASK_INTERRUPTIBLE);
if (test_and_clear_bit(IRQTF_RUNTHREAD,
&action->thread_flags)) {
__set_current_state(TASK_RUNNING);
return 0;
}
schedule();
}
}
2)如果中断来了,irq_wait_for_interrupt(action)就会返回0,然后进while循环,执行action->thread_fn()
handler_fn = irq_thread_fn;
while (!irq_wait_for_interrupt(action)) {
handler_fn(desc, action);
}
static irqreturn_t irq_thread_fn(struct irq_desc *desc,
struct irqaction *action)
{
irqreturn_t ret;
ret = action->thread_fn(action->irq, action->dev_id);
return ret;
}
至此,request_irq()整个过程梳理完毕,^_^
更多推荐
所有评论(0)