这系列文章将会介绍Linux内核的中断子系统,关于中断的基本概念网上有很多文章,这里就不再介绍了。
内核中第一个被汇编语言调用的C函数设置
在梳理Linux内核的中断服务程序执行路线过程中,在arch/arm/kernel/entry-armv.S文件中,程序的执行将会出现分支。具体取决于内核配置。接下来介绍详细内容。
ARM架构下Linux内核的中断向量表如下
/*arch/arm/kernel/entry-armv.S*/
/*
* Interrupt dispatcher
*/
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
其中__irq_svc即为发生异常或中断时的向量,循迹追索
/*arch/arm/kernel/entry-armv.S*/
.align 5
__irq_svc:
svc_entry
irq_handler
#ifdef CONFIG_PREEMPTION
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif
svc_exit r5, irq = 1 @ return from exception
UNWIND(.fnend )
ENDPROC(__irq_svc)
这里的.align用来指定下一条指令或数据应该对齐到内存中的某个边界,对于大多数编译器这意味着将下一条指令对其到2的指数幂大小的地址,即为2^5=32,有效的指令对齐可以更高的性能效率。svc向量里,调用了irq_handler函数,这是一个汇编宏定义,继续追踪。
/*arch/arm/kernel/entry-armv.S*/
/*
* Interrupt handling.
*/
.macro irq_handler
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
badr lr, 9997f
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
9997:
.endm
我的源码树中,定义了CONFIG_GENERIC_IRQ_MULTI_HANDLER这个宏定义,因此走入handle_arch_irq这个分支。
在内核代码中,查找handle_arch_irq的定义,这是一个函数指针。
/*include/linux/irq.h*/
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
/*
* Registers a generic IRQ handling function as the top-level IRQ handler in
* the system, which is generally the first C code called from an assembly
* architecture-specific interrupt handler.
*
* Returns 0 on success, or -EBUSY if an IRQ handler has already been
* registered.
*/
#ifndef CONFIG_IRQCHIP_XILINX_INTC_MODULE_SUPPORT_EXPERIMENTAL
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *));
#else
int set_handle_irq(void (*handle_irq)(struct pt_regs *));
#endif
/*
* Allows interrupt handlers to find the irqchip that's been registered as the
* top-level IRQ handler.
*/
extern void (*handle_arch_irq)(struct pt_regs *) __ro_after_init;
#else
#define set_handle_irq(handle_irq) \
do { \
(void)handle_irq; \
WARN_ON(1); \
} while (0)
#endif
/*kernel/irq/handle.c*/
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
void (*handle_arch_irq)(struct pt_regs *) __ro_after_init;
#endif
#ifdef CONFIG_GENERIC_IRQ_MULTI_HANDLER
#ifndef CONFIG_IRQCHIP_XILINX_INTC_MODULE_SUPPORT_EXPERIMENTAL
int __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
#else
int set_handle_irq(void (*handle_irq)(struct pt_regs *))
#endif
{
if (handle_arch_irq)
return -EBUSY;
handle_arch_irq = handle_irq;
return 0;
}
#endif
以上代码可以看出,handle_arch_irq函数的值在set_handle_rq函数里被赋值,__init是否启用则取决于另一个宏定义,感兴趣的话可以自行继续查找。
set_handle_irq函数则在另外的地方被调用,这部分就和中断控制器的初始化有关了,放到下一篇再讲。
/*drivers/irqchip/irq-gic.c*/
__gic_init_bases()->set_handle_irq(gic_handle_irq)
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
...
}