Linux中断机制之三:中断的执行

来源:互联网 发布:淘宝网秋天中老年帽子 编辑:程序博客网 时间:2024/05/16 14:40

在内核代码中,对X86平台中断执行的基本过程是:
1、 通过IDT中的中断描述符,调用common_interrupt;
2、 通过common_interrupt,调用do_IRQ,完成vector到irq_desc的转换,进入Generic interrupt layer(调用处理函数generic_handle_irq_desc);
3、 调用在中断初始化的时候,按照中断特性(level触发,edge触发等、simple等)初始化的irq_desc:: handle_irq,执行不同的通用处理接口,比如handle_simple_irq;
4、 这些通用处理接口会调用中断初始化的时候注册的外部中断处理函数;完成EOI等硬件相关操作;并完成中断处理的相关控制。

common_interrupt

按照之前CPU执行中断过程的描述,X86 CPU在准备好了中断执行环境后,会调用中断描述符定义的中断处理入口;根据中断相关初始化过程我们知道,对于用户自定义中断,中断处理入口都是(对系统预留的,就直接执行定义的接口了):

pushq_cfi $(~vector+0x80)jmp 2f (执行jmp common_interrupt)

就是在把vector入栈后,执行common_interrupt,common_interrupt在entry_64.S中定义,其中关键步骤为:调用do_IRQ,完成后会根据环境判断是否需要执行调度,最后执行iretq指令完成中断处理,iret指令的重要功能就是回复中断函数前的EFLAGS(执行中断入口前被入栈保存,并清零IF位关中断),并恢复执行被中断的程序(这里不一定会恢复到之前的执行环境,可能执行软中断处理,或者执行调度)。

do_IRQ

do_IRQ的基本处理过程如下,其负责中断执行环境建立、vector到irq的转换等

do_IRQ    irq_enter        tick_check_idle 如果需要,调用该函数更新时钟        __irq_enter            关键为add_preempt_count(HARDIRQ_OFFSET),表明当前系统在处理硬中断    exit_idle主要是对外通告退出idle状态        根据vector从vector_irq中获取irq号    handle_irq        desc = irq_to_desc(irq);根据irq号获取irq_desc结构        generic_handle_irq_desc调用Generic interrupt layer完成中断处理    irq_exit        sub_preempt_count(IRQ_EXIT_OFFSET)表明系统已经没有处理硬中断        invoke_softirq 如果发起软中断的处理

Generic interrupt layer

该层负责的是平台无关/设备无关的中断通用逻辑,对这部分,在《Linux generic IRQ handling》中有详细描述。其负责完成中断处理的接口是generic_handle_irq_desc,该接口会执行irq_desc::handle_irq; Generic interrupt layer根据中断特性的不同,把中断分成几类,包括:level type(handle_level_irq)、edge type(handle_edge_irq)、simple type(handle_simple_irq)等,这些中断类型对应的处理函数是都在kernel/irq/chip.c中定义,并入前面的描述,在相关中断初始化的时候,被赋值给irq_desc::handle_irq;对于PCI设备,只用了两种,level type(INT#模式)、edge type(MSI/MSI-X模式)。

edge 触发中断的基本处理过程:

电压跳变触发中断===>中断控制器接收中断,记IRR寄存器===>中断控制器置ISR寄存器===>CPU屏蔽本CPU中断===>CPU处理中断,发出EOI===>中断控制器确认可以处理下一次中断===>ISR清中断源,电压归位===>中断源可以发起下一次中断===>CPU中断处理完成,执行完现场处理后执行IRET,不再屏蔽本CPU中断。
edge触发的特点:
a) 中断不会丢
如果中断触发时中断被屏蔽,那么中断控制器会记录下该中断,在屏蔽取消的时候会再执行。
b) edge触发的缺点是完成共享不方便:
比如A和B两个中断源共享一个中断,每次ISR先检查A再检查B,如果B先发生中断,在ISR检查完A,检查B的过程中,A发生中断。那么在ISR处理开始的时候,A会告诉ISR,不是它干的,然后ISR处理B的中断,完成后通过清理中断源把B的电压归位,但是由于A的中断没有得到处理,电压没有归位,这个共享的中断就不能得到再次触发了。
edge触发对应的通用逻辑接口

handle_edge_irq    if(如果已经有CPU在处理该中断)        置irq_desc状态为PENDING,让当前处理该中断的CPU处理完成后帮忙处理        mask_ack_irq关闭(mask)并ACK(对MSI就是发送EOI)该中断,为什么必须这样做?是因为后续中断的工作本身会在pending的中断处理时被执行?        END    desc->irq_data.chip->irq_ack ACK该中断,chip为msi_chip        ack_apic_edge            irq_complete_move 处理irq在CPU之间迁移的情况            irq_move_irq 处理irq在CPU之间迁移的情况            ack_APIC_irq ACK该中断,通知APIC可以触发下一次中断了        do{            如果是帮其它CPU处理PENGDING的中断,需要unmask该中断,因为该中断已经开始处理,系统能够接受下一个了            handle_irq_event}while(PENGDING标志位被设置)如果本CPU上次处理的时候,其它CPU上有中断,其它CPU上的只是设置了PENGDING,本CPU负责处理。

level 触发:

这种模式下,外设通过把电压保持到某个门限值来完成触发中断,在处理完成(EOI)后,如果电压还在门限值,就会再次触发中断的执行。
level触发的特点:
a) 方便中断共享
b) 对中断触发时中断被屏蔽的情况,如果中断屏蔽解除后仍然引脚电压仍然在门限值,就执行该中断的ISR,否则不执行。
需要说明的是:对于使用local APIC的系统,level触发和edge触发需要配置local APIC的Local Vector Table。
4、 level触发对应的通用逻辑接口

handle_level_irq    mask_ack_irq 关闭(mask)并ACK(对MSI就是发送EOI)该中断    如果已经有CPU在处理该中断,则退出执行    handle_irq_event处理该中断    cond_unmask_irq使能该中断

level触发和edge触发在通用逻辑层最大的不同就是当其他CPU正在处理该中断的时候,系统的行为,对edge触发,会把该中断记录下来,当前处理结束后再次执行,而level直接退出。产生这种差异的原因是:level触发不怕丢?

无论是那种触发方式,都会调用handle_irq_event处理中断,该函数中会遍历irq_desc::action链表,执行action->handler,也就是驱动在中断初始化的时候,通过request_irq注册的中断处理接口。

总结

中断的使能状态

1、 在local APIC层次(当前CPU),一个中断正在处理的时候,不会有相同的中断或者优先级低于该中断的其它中断来打断当前中断的执行;但是高优先级中断可以打断低优先级中断。
2、 在X86 CPU层次(当前CPU),从中断执行开始到IRET,IF位都被清零,也就是只有不可屏蔽中断能够打断当前中断的执行。
3、 在Generic interrupt layer层次,如果一个中断已经在系统中执行,会阻止该中断在其它CPU上的执行。
4、 在外设/驱动中断处理函数层次往往也有中断使能的功能,比如启用了NAPI的网卡,在中断处理函数开始执行的时候,往往会通过硬件功能关闭该中断,要在对应的软中断完成处理后才通过硬件功能使能该中断。
注:NMI中断虽然称为不可屏蔽中断,也有一个例外:NMI中断执行过程中,该CPU屏蔽了后来的NMI中断。

中断的执行CPU

通过中断初始化过程我们知道:中断在那个CPU上执行,取决于在那个CPU上申请了vector并配置了对应的中断控制器(比如local APIC)。如果想要改变一个中断的执行CPU,必须重新申请vector并配置中断控制器。一般通过echo xxx > /proc/irq/xxx/affinity来完成调整,同时irq_balance一类软件可以用于完成中断的均衡。

0 0
原创粉丝点击