内核中断处理-从CPU说起

当ARM CPU接收到中断线上过来的信号以后,会执行一系列操作,如下面所示:

1、把cpsr保存到相应模式下的spsr(spsr_mode)
2、把pc保存到相应模式下的lr (lr_mode)
3、设置cpsr为相应异常模式,并屏蔽中断
4、设置pc为相应异常处理程序的入口地址

这些都是硬件上帮你做好的,接下来的工作就需要软件来处理了,进入了异常处理程序的入口,也就是linux内核中所设置的异常向量表的vector_irq。

.section .vectors, "ax", %progbits 
__vectors_start: 
    W(b)    vector_rst 
    W(b)    vector_und 
    W(ldr)    pc, __vectors_start + 0x1000 
    W(b)    vector_pabt 
    W(b)    vector_dabt 
    W(b)    vector_addrexcptn 
    W(b)    vector_irq ------------IRQ Vector 
    W(b)    vector_fiq

这些都是汇编代码,而后会进入vector_irq中来执行。我们来看下vector_irq的实现:
在kernel/arch/arm/kernel/entry-armv.S中的定义如下,其中vector_stub是一个宏,展开以后就会成为vector_irq的函数实现,在此函数中会判断中断发生时是cpu是处于用户态还是内核态,而它下面紧接着就是不同模式下发生中断后要跳转的函数地址。

kernel/arch/arm/kernel/entry-armv.S:

1009 /*
1010  * Interrupt dispatcher
1011  */
1012     vector_stub irq, IRQ_MODE, 4
1013 
1014     .long   __irq_usr           @  0  (USR_26 / USR_32)
1015     .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)
1016     .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)
1017     .long   __irq_svc           @  3  (SVC_26 / SVC_32)
1018     .long   __irq_invalid           @  4
1019     .long   __irq_invalid           @  5
1020     .long   __irq_invalid           @  6
1021     .long   __irq_invalid           @  7
1022     .long   __irq_invalid           @  8
1023     .long   __irq_invalid           @  9
1024     .long   __irq_invalid           @  a
1025     .long   __irq_invalid           @  b
1026     .long   __irq_invalid           @  c
1027     .long   __irq_invalid           @  d
1028     .long   __irq_invalid           @  e
1029     .long   __irq_invalid           @  f

可以看出内核只有两种模式下的中断处理,一个是用户模式下发生了中断,另一个是内核态时发生了中断,两种中断分开来处理的。注意这两种模式指的是在中断发生前的现场,而代码执行到此时,cpu已经处于了中断irq模式了,前面硬件处理部分已经做了说明。
在__irq_usr和__irq_svc中,他们虽有不同,但是有些操作也是必须要做的,和裸机中断处理函数类似,都是需要先保存现场,切换CPU进入到SVC模式,在SVC模式下进入真正的C中断服务代码中,并且在最终处理完之后恢复现场,打开中断。所以说在linux内核中,中断是不会被打断和嵌套的。

那么接下来我们来看一下,CPU的执行时如何从汇编代码跑到C代码中的。在__irq_usr和__irq_svc函数中,前后都是汇编,分别是用来保存现场和恢复现场用的,中间会调用中断核心处理irq_handler,在irq_handler里面会进入C代码中来执行上层的中断服务程序。

kernel/arch/arm/kernel/entry-armv.S:
35 /*
  36  * Interrupt handling.
  37  */
  38     .macro  irq_handler
  39 #ifdef CONFIG_MULTI_IRQ_HANDLER
  40     ldr r1, =handle_arch_irq
  41     mov r0, sp
  42     adr lr, BSYM(9997f)
  43     ldr pc, [r1]
  44 #else
  45     arch_irq_handler_default
  46 #endif
  47 9997:
  48     .endm

irq_handler的处理有两种配置。一种是配置了CONFIG_MULTI_IRQ_HANDLER。这种情况下,linux kernel允许run time设定irq handler。如果我们需要一个linux kernel image支持多个平台,这是就需要配置这个选项。另外一种是传统的linux的做法,irq_handler实际上就是arch_irq_handler_default.

第一种方式
对于第一种方式,直接跳转到handle_arch_irq的地址处开始执行,而这个地址就是个C函数的地址,并且是可以动态设置的,由此就进入的C代码阶段进行处理了,可以搜索一下这个函数地址在哪里赋值就能跟踪到C代码处理部分了。


kernel/arch/arm/kernel/setup.c:
void __init setup_arch(char **cmdline_p)
{
……
 #ifdef CONFIG_MULTI_IRQ_HANDLER
      handle_arch_irq = mdesc->handle_irq;
  #endif
……
}
kernel/arch/arm/kernel/irq.c:
124 #ifdef CONFIG_MULTI_IRQ_HANDLER
125 void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
126 {
127     if (handle_arch_irq)
128         return;
129 
130     handle_arch_irq = handle_irq;
131 }
132 #endif

由上可知,在setup_arch的时候就会设置这个函数地址为machine中的handle_irq.除此之外也可以利用下面的set_handle_irq接口来动态设置此函数。这种方式支持irq_domain和级联,各个中断控制器负责各自的irq_domain,并负责各自硬件中断线到irq num的转换。

第二种方式
第二种就是比较老的做法了,我们接下来进行介绍arch_irq_handler_default,在函数中会调用asm_do_IRQ进入C代码来处理。


3 /*
  4  * Interrupt handling.  Preserves r7, r8, r9
  5  */
  6     .macro  arch_irq_handler_default
  7     get_irqnr_preamble r6, lr
  8 1:  get_irqnr_and_base r0, r2, r6, lr
  9     movne   r1, sp
 10     @
 11     @ routine called with r0 = irq number, r1 = struct pt_regs *
 12     @
 13     adrne   lr, BSYM(1b)
 14     bne asm_do_IRQ
 15 
 16 #ifdef CONFIG_SMP
 17     /*
 18      * XXX
 19      *
 20      * this macro assumes that irqstat (r2) and base (r6) are
 21      * preserved from get_irqnr_and_base above
 22      */
 23     ALT_SMP(test_for_ipi r0, r2, r6, lr)
 24     ALT_UP_B(9997f)
 25     movne   r1, sp
 26     adrne   lr, BSYM(1b)
 27     bne do_IPI
 28 #endif
 29 9997:
 30     .endm

这种方法通过asm_do_IRQ函数来进入到C代码中的,我们来看此函数的实现过程:


kernel/arch/arm/kernel/irq.c:
59 /*
 60  * handle_IRQ handles all hardware IRQ's.  Decoded IRQs should
 61  * not come via this function.  Instead, they should provide their
 62  * own 'handler'.  Used by platform code implementing C-based 1st
 63  * level decoding.
 64  */
 65 void handle_IRQ(unsigned int irq, struct pt_regs *regs)
 66 {
 67     struct pt_regs *old_regs = set_irq_regs(regs);
 68 
 69     irq_enter();
 70 
 71     /*
 72      * Some hardware gives randomly wrong interrupts.  Rather
 73      * than crashing, do something sensible.
 74      */
 75     if (unlikely(irq >= nr_irqs)) {
 76         if (printk_ratelimit())
 77             printk(KERN_WARNING "Bad IRQ%u\n", irq);
 78         ack_bad_irq(irq);
 79     } else {
 80         generic_handle_irq(irq);
 81     }
 82 
 83     irq_exit();
 84     set_irq_regs(old_regs);
 85 }
86 
 87 /*
 88  * asm_do_IRQ is the interface to be used from assembly code.
 89  */
90 asmlinkage void __exception_irq_entry
 91 asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
 92 {
 93     handle_IRQ(irq, regs);
 94 }
 95

由此进入generic_handle_irq,它的实现是在kernel/kernel/irq/irqdesc.c中的。它会做相关的irq num的映射并且调用注册到相应irq num的irq handle。

对于ARM平台而言,我们推荐使用第一种方法,因为从逻辑上讲,中断处理就是需要根据当前的硬件中断系统的状态,转换成一个IRQ number,然后调用该IRQ number的处理函数即可。generic_handle_irq是旧的ARM SOC系统使用的方法,它处理的硬件中断号和IRQ number之间的关系非常简单。但是实际上,ARM平台上的硬件中断系统已经是越来越复杂了,需要引入interrupt controller级联,irq domain等等概念,因此,第一种方法更加适应未来的发展。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值