您的位置:首页 > 运维架构 > Linux

Linux中断的响应流程

2018-02-11 12:12 176 查看
原创文章, 转载请注明出处。
这篇文章主要讨论的话题是当中断发生时Linux内核是如何处理中断的。

当CPU检测到中断的时候,linux内核首先会跳转至arch/arm/kernel/entry-armv.S中进行处理,
然后切换到IRQ_MODE(vector_stub   irq, IRQ_MODE, 4),最后运行到irq_handler。code如下:

vector_irq: irq中断的入口, vector_irq的定义为:vector_stub
vector_stub     irq, IRQ_MODE, 4

__irq_svc:
        svc_entry
        irq_handler

.macro  irq_handler
        ldr     r1, =handle_arch_irq
        mov     r0, sp
        ldr     pc, [r1]

可知该段汇编最终会调用handle_arch_irq,那么handle_arch_irq是什么呢?想了解这个问题,
需要对gic的初始化过程进行分析, code如下:
gic_of_init() drivers/irqchip/irq-gic.c
->__gic_init_bases()
->set_handle_irq(gic_handle_irq)
{
handle_arch_irq = handle_irq;
}

至此,中断处理过程从汇编跳到了C语言,不熟悉汇编的人可以放松下来了,继续分析:
gic_handle_irq(struct pt_regs *regs) drivers/irqchip/irq-gic.c 
{
1. 获取struct gic_chip_data的结构体gic,gic = &gic_data[0];

2. 获取cpu interface的base address,void __iomem *cpu_base = gic_data_cpu_base(gic);

3. do while(1)循环进行中断处理
{
1. 获得硬件中断号
{
1. irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
2. irqnr = irqstat & GICC_IAR_INT_ID_MASK;
}

2. if (likely(irqnr > 15 && irqnr < 1020)), 如果为PPI或SPI中断, 走irq domain的处理方式 
{
irq_enter()
{
account_irq_enter_time(current); 
                                preempt_count_add(HARDIRQ_OFFSET);
}

__handle_domain_irq(gic->domain, irqnr, regs);
{
1. 保护现场, struct pt_regs *old_regs = set_irq_regs(regs);

2. 根据硬件中断号找到gic domain对应的软件中断号, 如果找到的软件中断号有误,直接返回
{
irq = irq_find_mapping(domain, hwirq); 
if (unlikely(!irq || irq >= nr_irqs)) 
{
ack_bad_irq(irq);
ret = -EINVAL;
}

3. generic_handle_irq(irq);
{
1. 根据软中断号找到对应的struct irq_desc结构体, desc = irq_to_desc(irq);

2. generic_handle_irq_desc(irq, desc);
{
desc->handle_irq(desc);

generic_handle_irq_desc()很简单,就是一句话,但是要理解这一句话还是的下点功夫的,
首先得弄明白何为desc->handle_irq()? 要明白这个问题,就得从中断号的申请说起,这里
先进行一个简单的介绍, 后面会准备写一篇文章进行介绍。直接进入正题:

之前我在分析Linux Gic初始化的时候说到irq domain的注册,drivers/irqchip/irq-gic.c
gic->domain = irq_domain_create_linear(handle, gic_irqs,
                                                       &gic_irq_domain_hierarchy_ops,
                                                       gic);
   
当device driver进行中断号申请的时候会对desc->handle_irq()进行初始化, 初始化流程如下:
->gic_irq_domain_alloc() drivers/irqchip/irq-gic.c
->gic_irq_domain_map()
->irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data,
handle_fasteoi_irq, NULL, NULL);
{
irq_domain_set_hwirq_and_chip(domain, virq, hwirq, chip, chip_data);
__irq_set_handler(virq, handler, 0, handler_name);
{
__irq_do_set_handler(desc, handle, is_chained, name);
desc->handle_irq = handle = handle_fasteoi_irq;
}
irq_set_handler_data(virq, handler_data);
}

分析了这么多, 终于解开了desc->handle_irq()的真面目,接着我们继续分析
handle_fasteoi_irq() kernel/irq/chip.c
{
1. 获取struct irq_chip,这个chip是在irq_domain_set_hwirq_and_chip()的时候和
desc帮顶起来的, chip = desc->irq_data.chip;

2. 如果当前中断处于 disabled状态或者没有对应的action,mask掉中断然后退出 
if (unlikely(!desc->action || irqd_irq_disabled(&desc->irq_data))) 
{
desc->istate |= IRQS_PENDING;
mask_irq(desc);
goto out;
}

3. 处理中断 
->handle_irq_event(desc);
->handle_irq_event_percpu(desc);
->__handle_irq_event_percpu(desc, &flags);
{
1. 定义一个action结构体, struct irqaction *action;

2. 循环遍历当前中断的所有action链表,一个中断号可能服务于多个设备
for_each_action_of_desc(desc, action)
{
进入中断处理函数:
irqreturn_t res = action->handler(irq, action->dev_id);

switch (res)
{
如果在申请中断的时候采用request_threaded_irq(), 则唤醒thread
case IRQ_WAKE_THREAD:
__irq_wake_thread(desc, action);
->wake_up_process(action->thread);

case IRQ_HANDLED:
*flags |= action->flags;
break;
}
}
}

4. 如果需要end当前irq,即中断的有限状态机由ACTIVE状态转化为DEACTIVE状态
chip->irq_eoi(&desc->irq_data);
}

/*在 gpc 中断控制器初始化的时候,将gic domain的desc->handle_irq设置为                                                                                                                         handle_fasteoi_irq, drivers/irqchip/irq-gic.c line797 */
1. desc->handle_irq(irq, desc)<=> handle_fasteoi_irq(irq, desc)
{
/*kernel/irq/chip.c*/
1. struct irq_chip *chip = desc->irq_data.chip;

2. 清状态, desc->istate &= ~(IRQS_REPLAY | IRQS_WAITING);

3. 如果中断disable或desc->action不存在, mask当前中断
{
1.  desc->istate |= IRQS_PENDING;

2.  mask_irq(desc);
}

4. 如果设置IRQS_ONESHOT, mask_irq(desc); 

5. 进行中断处理, handle_irq_event(desc);
{
1. 设置status
{
desc->istate &= ~IRQS_PENDING;
irqd_set(&desc->irq_data, IRQD_IRQ_INPROGRESS);
}

2. ret = handle_irq_event_percpu(desc, action);
{
do while()进行中断处理
{
1. 调用action->handler, res = action->handler(irq, action->dev_id);

2. switch (res)
{
case IRQ_WAKE_THREAD:  irq为treaded irq, 在此唤醒thread_fn
{
__irq_wake_thread(desc, action);
wake_up_process(action->thread);
}

case IRQ_HANDLED:
flags |= action->flags;

}

3. action = action->next; 
}  while (action); 支持share的中断action为链表, 再次依次处理
}
}
}

2. 处理完成后,irqd_clear(&desc->irq_data, IRQD_IRQ_INPROGRESS);
}

3.  cond_unmask_eoi_irq(desc, chip); eoi, 告诉cpu interfce cpu已经处理完中断
{
1. if (!(desc->istate & IRQS_ONESHOT))
{
chip->irq_eoi(&desc->irq_data);
return;
}

2. if (!irqd_irq_disabled(&desc->irq_data) && irqd_irq_masked(&desc->irq_data) && !desc->threads_oneshot) 
{
chip->irq_eoi(&desc->irq_data);
unmask_irq(desc);

else if (!(chip->flags & IRQCHIP_EOI_THREADED)) 
{
chip->irq_eoi(&desc->irq_data);
}
}
}

4. 恢复现场,set_irq_regs(old_regs);
}

irq_exit()
{
account_irq_exit_time(current);
preempt_count_sub(HARDIRQ_OFFSET);

如果触发了软中断, 处理软中断
if (!in_interrupt() && local_softirq_pending())
invoke_softirq();
}
}

3. 如果为SGI中断
{
1. writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI); 中断有限状态机由active转向deactive

2. handle_IPI(irqnr, regs); 处理软中断,硬件中断号为(0-15)

3. continue;
}

4. 没有pending的中断时,break while循环
} while(1)
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息