本文尝试阅读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, &param);

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()整个过程梳理完毕,^_^ 

Logo

更多推荐