关于handle_level_irq、handle_edge_irq和中断嵌套问题

时间:2021-09-17 01:13:48

在中断的响应和服务的博文中,我们提到了handle_level_irq,但是忽略了另一个和他对应的函数handle_edge_irq。现在我们需要单独地对他们分析一下,并借此来分析有关Linux中断嵌套的问题!

在说这两个函数之前,我们有必要先了解电平触发和边缘触发,及他们之间的区别。可以参考博文http://blog.csdn.net/sunnybeike/article/details/6984286。

在你了解了电平触发和边缘触发之后,我们就可以开始分析这两个函数了。

首先来看handle_level_irq:

/**
 *	handle_level_irq - Level type irq handler
 *	@irq:	the interrupt number
 *	@desc:	the interrupt description structure for this irq
 *
 *	Level type interrupts are active as long as the hardware line has
 *	the active level. This may require to mask the interrupt and unmask
 *	it after the associated handler has acknowledged the device, so the
 *	interrupt line is back to inactive.
 */

void
handle_level_irq(unsigned int irq, struct irq_desc *desc)
{
	raw_spin_lock(&desc->lock);
	mask_ack_irq(desc);  //屏蔽中断*******************************

	if (unlikely(irqd_irq_inprogress(&desc->irq_data)))
		if (!irq_check_poll(desc))
			goto out_unlock;

	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);
	kstat_incr_irqs_this_cpu(irq, desc);

	/*
	 * If its disabled or no action available
	 * keep it masked and get out of here
	 */
	if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data)))
		goto out_unlock;

	handle_irq_event(desc);

	if (!irqd_irq_disabled(&desc->irq_data) && !(desc->istate & IRQS_ONESHOT))
		unmask_irq(desc);  //打开中断******************************
out_unlock:
	raw_spin_unlock(&desc->lock);
}
先不要管这个函数的实现,一定要把这个函数前面的注释给看懂了,我试着来翻译一下:

handle_level_irq-电平型irq处理函数

@irq 中断号

@desc 与irq对应的中断描述结构

电平型触发中断在有效电平下会保持其中断请求状态。那么这需要在handler对相应的中断应答之后首先将对应的中断屏蔽了,然后再开启中断以使其恢复到无效电平的状态。

那么,我们来看一下在函数中关闭中断和打开中断的位置。如函数中标有星号的代码所示,发现在一开始我们就把中断开启了,而到了中断处理结束之后,也就是再调用handle_irq_event()之后,才开启中断,也就是说在这期间,是不会有来自同一中断源的中断来干扰这段程序的执行的,也就该中断源不会产生新的中断,中断不会嵌套!!!


我们再来看handle_edge_irq做了什么:

/**
 *	handle_edge_irq - edge type IRQ handler
 *	@irq:	the interrupt number
 *	@desc:	the interrupt description structure for this irq
 *
 *	Interrupt occures on the falling and/or rising edge of a hardware
 *	signal. The occurrence is latched into the irq controller hardware
 *	and must be acked in order to be reenabled. After the ack another
 *	interrupt can happen on the same source even before the first one
 *	is handled by the associated event handler. If this happens it
 *	might be necessary to disable (mask) the interrupt depending on the
 *	controller hardware. This requires to reenable the interrupt inside
 *	of the loop which handles the interrupts which have arrived while
 *	the handler was running. If all pending interrupts are handled, the
 *	loop is left.
 */

void
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
	raw_spin_lock(&desc->lock);

	desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING); 
	/*
	 * If we're currently running this IRQ(注意这句话的理解,这句话不是说我们正在处理这个中断,而是说这个中断对应的desc正在处理中断,不是这个中断哦),
         * or its disabled,
	 * we shouldn't process the IRQ. Mark it pending, handle
	 * the necessary masking and go out
	 */
	if (unlikely(irqd_irq_disabled(&desc->irq_data) ||
		     irqd_irq_inprogress(&desc->irq_data) || !desc->action)) {
		if (!irq_check_poll(desc)) {
			desc->istate |= IRQS_PENDING;  //将istate置位IRQS_PENDING状态
			mask_ack_irq(desc);            //屏蔽中断
			goto out_unlock;
		}
	}
	kstat_incr_irqs_this_cpu(irq, desc);

	/* Start handling the irq */
	desc->irq_data.chip->irq_ack(&desc->irq_data);  //向设备发回响应信号

	do {
		if (unlikely(!desc->action)) {       
			mask_irq(desc);
			goto out_unlock;
		}

		/*
		 * When another irq arrived while we were handling
		 * one, we could have masked the irq.
		 * Renable it, if it was not disabled in meantime.
		 */
		if (unlikely(desc->istate & IRQS_PENDING)) {
			if (!irqd_irq_disabled(&desc->irq_data) &&
			    irqd_irq_masked(&desc->irq_data))
				unmask_irq(desc);     //打开中断
		}

		handle_irq_event(desc);  //在handle_irq_event中,会执行: desc->istate &= ~IRQS_PENDING;

	} while ((desc->istate & IRQS_PENDING) &&
		 !irqd_irq_disabled(&desc->irq_data));

out_unlock:
	raw_spin_unlock(&desc->lock);
}
同样的,我们需要首先关注这段代码的注释部分,试着来解释这段注释:

handle_edge_irq-边缘型IRQ处理函数

@irq: 中断号

@desc: 与irq对应的中断描述结构体

中断会在电平下降或者上升的时候被触发。被触发的中断会被锁存在irq controller hadrware中,并且必须被响应以使其能够被再次被触发。在得到响应之后,即使第一个中断正在被相应的处理函数处理,仍然能够触发第二个中断。如果这种情况发生了,那我们需要将依附于这个controller hardware的中断给屏蔽掉。这需要打开循环内部的用来处理在第一个中断正在被处理时到来的第二个中断的中断(这里比较拗口,还是看原文吧!)。如果所有pending的中断都被处理过了,那么循环就会退出。

我们还有必要对函数中间的那一大段注释给出翻译:

如果处理irq时有另一个irq到达,那么当时可能屏蔽了该irq。解除对irq的屏蔽,如果它在此期间没有被禁用的话。

对这段注释的解释是:IRQ的处理是在一个循环中进行的。假定我们刚好处于调用handle_IRQ_event之后的位置上。在第一个IRQ的ISR处理程序正在运行时,可能同时有第二个IRQ请求发送过来,那么IRQ_PENDING置位,因此循环将从头开始。但是在这种情况下,IRQ已经被屏蔽,因而chip->unmask接触IRQ的屏蔽,并清除IRQ_MASKED标志。这确保在handle_IRQ_event执行期间只能发生一个中断。

我们首先来看pending信号的问题。

如果一个中断请求队列的服务是关闭的(irqd_irq_disable()返回为真),或者中断服务队列正在为来自同一个中断源的某一个中断进行服务(irqd_irq_inprogress()返回为真),或者中断服务对列为空,也就是说尚没有相应的服务程序挂载到该中断服务程序队列,并且irq没有在轮询desc指向的中断服务队列(说明什么呢?)则desc->istate = | IRQ_PENDING。并且会将中断屏蔽。以后,CPU在开启这个服务的时候,会看到这个标志而补上上一次中断服务。(至于上述几种情况发生的情景,可以参考《情景分析》 P215)。由此可以看出,handle_edge_irq也是不允许中断嵌套的。而且,假如中断服务队列正在为中断A服务,此时中断B到来,那么其istate中的IRQ_PENDING会被置位,同时关中断,那么在中断B被处理之前,是不可能接受中断C的,因为该中断源一直处于关中断状态,一直要到中断B被处理,取消其IRQ_PENDING设置,并且打开中断为止。


我们上边分析的只是中断的前部,得到的结论是不支持中断嵌套,但是,在中断后部中,也就是软中断中是中断是可以被打断的。

/*
 * do_IRQ handles all normal device IRQ's (the special
 * SMP cross-CPU interrupts have their own specific
 * handlers).
 */
unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);

	/* high bit used in ret_from_ code  */
	unsigned vector = ~regs->orig_ax;
	unsigned irq;

	exit_idle();
	irq_enter();

	irq = __this_cpu_read(vector_irq[vector]);

	if (!handle_irq(irq, regs)) {
		ack_APIC_irq();

		if (printk_ratelimit())
			pr_emerg("%s: %d.%d No irq handler for vector (irq %d)\n",
				__func__, smp_processor_id(), vector, irq);
	}

	irq_exit();

	set_irq_regs(old_regs);
	return 1;
}

在do_IRQ的最后,我们会调用irq_exit()操作,它做了什么呢?

/*
 * Exit an interrupt context. Process softirqs if needed and possible:
 */
void irq_exit(void)
{
	account_system_vtime(current);
	trace_hardirq_exit();
	sub_preempt_count(IRQ_EXIT_OFFSET); 
	if (!in_interrupt() && local_softirq_pending())
		invoke_softirq();

	rcu_irq_exit();
#ifdef CONFIG_NO_HZ
	/* Make sure that timer wheel updates are propagated */
	if (idle_cpu(smp_processor_id()) && !in_interrupt() && !need_resched())
		tick_nohz_stop_sched_tick(0);
#endif
	preempt_enable_no_resched();
}

我们首先来看看in_interrupt():

#define in_interrupt()		(irq_count())


#define irq_count()    (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
                 | NMI_MASK))
 
我们来看一下还行soft_irq的条件:

首先,当外部中断处于屏蔽状态或者软中断处于屏蔽状态,或者非屏蔽中断处于屏蔽状态时并且thread_info->preempt_count不为0的时候,说明正处于“硬中断”服务程序中,这是不允许调度或者打断的。

其次:

#define local_softirq_pending()	percpu_read(irq_stat.__softirq_pending)
只有在尚有未处理的软中断存在的时候,才会激活sofr_irq。


现在,对于中断嵌套我们应该有个总结性的认识了:

在中断服务的前部是不允许嵌套的,中断服务不能被打断,而在中断服务的后部,也就是soft_irq阶段,中断服务是可以被打断的。