Linux异常中断处理结构

内核在start_kernel()函数(init/main.c)中会调用trap_init()个函数来设置异常的处理函数。

1、trap_init()函数

/*
	 * Copy the vectors, stubs and kuser helpers (in entry-armv.S)
	 * into the vector page, mapped at 0xffff0000, and ensure these
	 * are visible to the instruction stream.
	 */
	memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
	memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
	memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);

在trap_init()函数中会将异常向量表vectors中的内容拷贝到固定的地址0xffff0000。而__vectors_start又是哪里来的呢?

/*arch/arm/kernel/entry-arm.S*/
__vectors_start:
	swi	SYS_ERROR0
	b	vector_und + stubs_offset
	ldr	pc, .LCvswi + stubs_offset
	b	vector_pabt + stubs_offset
	b	vector_dabt + stubs_offset
	b	vector_addrexcptn + stubs_offset
	b	vector_irq + stubs_offset
	b	vector_fiq + stubs_offset
   可以看到各种异常处理函数的入口都从__vectors_start开始。这里我们以irq中断举例,继续往下看当一个中断发生是,流程会怎么走。

   全局搜索了一下vector_irq关键字,我们都搜不到什么东西,原来这是一个宏。
vector_stub	irq, IRQ_MODE, 4

我们继续看一下这个宏定义里面做了什么

.macro	vector_stub, name, mode, correction=0
	.align	5
 
vector_\name:
	.if \correction
	sub	lr, lr, #\correction
	.endif
 
	@
	@ Save r0, lr_<exception> (parent PC) and spsr_<exception>
	@ (parent CPSR)
	@
	stmia	sp, {r0, lr}		@ save r0, lr
	mrs	lr, spsr
	str	lr, [sp, #8]		@ save spsr
 
	@
	@ Prepare for SVC32 mode.  IRQs remain disabled.
	@
	mrs	r0, cpsr
	eor	r0, r0, #(\mode ^ SVC_MODE)
	msr	spsr_cxsf, r0
 
	@
	@ the branch table must immediately follow this code
	@
	and	lr, lr, #0x0f
	mov	r0, sp
	ldr	lr, [pc, lr, lsl #2]
	movs	pc, lr			@ branch to handler in SVC mode
	.endm

大概就是计算返回地址、保存上下文和跳转到下一个处理的函数,根据不同情况会跳转到__irq_usr或者__irq_svc。

.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_usr的话:

__irq_usr:
	usr_entry
 
#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_off
#endif
	get_thread_info tsk
#ifdef CONFIG_PREEMPT
	ldr	r8, [tsk, #TI_PREEMPT]		@ get preempt count
	add	r7, r8, #1			@ increment it
	str	r7, [tsk, #TI_PREEMPT]
#endif
 
	irq_handler
#ifdef CONFIG_PREEMPT
	ldr	r0, [tsk, #TI_PREEMPT]
	str	r8, [tsk, #TI_PREEMPT]
	teq	r0, r7
	strne	r0, [r0, -r0]
#endif
#ifdef CONFIG_TRACE_IRQFLAGS
	bl	trace_hardirqs_on
#endif
 
	mov	why, #0
	b	ret_to_user
 
	.ltorg

也是一堆的汇编代码,我们关注irq_handler函数就好了,也是一个宏定义:

	.macro	irq_handler
	get_irqnr_preamble r5, lr
1:	get_irqnr_and_base r0, r6, r5, lr
	movne	r1, sp
	@
	@ routine called with r0 = irq number, r1 = struct pt_regs *
	@
	adrne	lr, 1b
	bne	asm_do_IRQ
   上面的代码最终会走到asm_do_IRQ()。这是一个C语言书写的函数,下面就看一下,这个函数里面具体做了什么事情。以前我们写的单片机程序处理中断服务的流程大概是这样的:

(1)判断是哪一个中断

(2)调用相应的中断处理函数

(3)清中断

在Linux系统里面处理中断的流程也大同小异,下面就看一下asm_do_IRQ()怎么处理irq中断的

/*arch\arm\kernel\irq.c*/
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
	struct pt_regs *old_regs = set_irq_regs(regs);
	struct irq_desc *desc = irq_desc + irq;
 
	/*
	 * Some hardware gives randomly wrong interrupts.  Rather
	 * than crashing, do something sensible.
	 */
	if (irq >= NR_IRQS)
		desc = &bad_irq_desc;
 
	irq_enter();
 
	desc_handle_irq(irq, desc);
 
	/* AT91 specific workaround */
	irq_finish(irq);
 
	irq_exit();
	set_irq_regs(old_regs);
}

根据传进来的中断号irq,选择相应的中断服务desc = irq_desc + irq。然后就传递到desc_handle_irq(irq, desc)

/*include/asm-arm\mach\irq.h*/
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc)
{
	desc->handle_irq(irq, desc);
}

可以看到这里调用了处理irq中断的服务程序,那么一开始是在哪里设置好这些中断服务程序的呢?

/*
 * kernel\irq\chip.c
 */
void
__set_irq_handler(unsigned int irq, irq_flow_handler_t handle, int is_chained,
		  const char *name)
{
	struct irq_desc *desc;
	unsigned long flags;
 
	if (irq >= NR_IRQS) {
		printk(KERN_ERR
		       "Trying to install type control for IRQ%d\n", irq);
		return;
	}
 
	desc = irq_desc + irq;
 
	if (!handle)
		handle = handle_bad_irq;
	else if (desc->chip == &no_irq_chip) {
		printk(KERN_WARNING "Trying to install %sinterrupt handler "
		       "for IRQ%d\n", is_chained ? "chained " : "", irq);
		/*
		 * Some ARM implementations install a handler for really dumb
		 * interrupt hardware without setting an irq_chip. This worked
		 * with the ARM no_irq_chip but the check in setup_irq would
		 * prevent us to setup the interrupt at all. Switch it to
		 * dummy_irq_chip for easy transition.
		 */
		desc->chip = &dummy_irq_chip;
	}
 
	spin_lock_irqsave(&desc->lock, flags);
 
	/* Uninstall? */
	if (handle == handle_bad_irq) {
		if (desc->chip != &no_irq_chip)
			mask_ack_irq(desc, irq);
		desc->status |= IRQ_DISABLED;
		desc->depth = 1;
	}
	desc->handle_irq = handle;
	desc->name = name;
 
	if (handle != handle_bad_irq && is_chained) {
		desc->status &= ~IRQ_DISABLED;
		desc->status |= IRQ_NOREQUEST | IRQ_NOPROBE;
		desc->depth = 0;
		desc->chip->unmask(irq);
	}
	spin_unlock_irqrestore(&desc->lock, flags);
}
   从上面的代码可以看出来,根据中断号,找到对应的中断服务描述结构体desc = irq_desc + irq,然后desc->handle_irq指向了相应的处理函数handle,这个是传递给__set_irq_handler函数的参数,继续看一下是谁传递handle过来的?
/*
 * include\linux\irq.h
 */
static inline void
set_irq_handler(unsigned int irq, irq_flow_handler_t handle)
{
	__set_irq_handler(irq, handle, 0, NULL);
}

继续跟踪set_irq_handler()是哪里调用的:

/*
 * arch\arm\plat-s3c24xx\irq.c
 */
void __init s3c24xx_init_irq(void)
{
	unsigned long pend;
	unsigned long last;
	int irqno;
	int i;
 
 
	此处省略很多代码...
 
 
	/* external interrupts */
 
	for (irqno = IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {
		irqdbf("registering irq %d (ext int)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_eint0t4);
		set_irq_handler(irqno, handle_edge_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}
 
	for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
		irqdbf("registering irq %d (extended s3c irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irqext_chip);
		set_irq_handler(irqno, handle_edge_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}
 
	/* register the uart interrupts */
 
	irqdbf("s3c2410: registering external interrupts\n");
 
	for (irqno = IRQ_S3CUART_RX0; irqno <= IRQ_S3CUART_ERR0; irqno++) {
		irqdbf("registering irq %d (s3c uart0 irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_uart0);
		set_irq_handler(irqno, handle_level_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}
 
	for (irqno = IRQ_S3CUART_RX1; irqno <= IRQ_S3CUART_ERR1; irqno++) {
		irqdbf("registering irq %d (s3c uart1 irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_uart1);
		set_irq_handler(irqno, handle_level_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}
 
	for (irqno = IRQ_S3CUART_RX2; irqno <= IRQ_S3CUART_ERR2; irqno++) {
		irqdbf("registering irq %d (s3c uart2 irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_uart2);
		set_irq_handler(irqno, handle_level_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}
 
	for (irqno = IRQ_TC; irqno <= IRQ_ADC; irqno++) {
		irqdbf("registering irq %d (s3c adc irq)\n", irqno);
		set_irq_chip(irqno, &s3c_irq_adc);
		set_irq_handler(irqno, handle_edge_irq);
		set_irq_flags(irqno, IRQF_VALID);
	}
 
	irqdbf("s3c2410: registered interrupt handlers\n");
   由上面可以看出来,在初始化s3c24xx单板的irq中断服务时,就会为不同的外部中断号注册相应的中断处理函数,比如外部中断号IRQ_EINT0~IRQ_EINT3,就注册了中断处理函数handle_edge_irq()。至此,我们终于跟踪到了最初那个handle_irq函数是怎么来的了,就是一开始初始化的时候注册的。

    接下来再看一下,中断服务处理函数handle_edge_irq()里面做的事情:
/*
 * kernel\irq\chip.c
 */
void fastcall
handle_edge_irq(unsigned int irq, struct irq_desc *desc)
{
 
    省略一部分代码...	
 
	/* Start handling the irq */
	desc->chip->ack(irq);
 
	/* Mark the IRQ currently in progress.*/
	desc->status |= IRQ_INPROGRESS;
 
	do {
		struct irqaction *action = desc->action;
		irqreturn_t action_ret;
 
		if (unlikely(!action)) {
			desc->chip->mask(irq);
			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->status &
			       (IRQ_PENDING | IRQ_MASKED | IRQ_DISABLED)) ==
			      (IRQ_PENDING | IRQ_MASKED))) {
			desc->chip->unmask(irq);
			desc->status &= ~IRQ_MASKED;
		}
 
		desc->status &= ~IRQ_PENDING;
		spin_unlock(&desc->lock);
		action_ret = handle_IRQ_event(irq, action);
		if (!noirqdebug)
			note_interrupt(irq, desc, action_ret);
		spin_lock(&desc->lock);
 
	} while ((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);
 
	desc->status &= ~IRQ_INPROGRESS;
out_unlock:
	spin_unlock(&desc->lock);
}

(1)调用desc->chip结构体中的函数来清中断、屏蔽或者重新使能中断

(2)然后调用handle_IRQ_event()函数去处理中断的事件:

/*
 *kernel\irq\handle.c
 */
irqreturn_t handle_IRQ_event(unsigned int irq, struct irqaction *action)
{
	此处省略一些代码...
 
	do {
		ret = action->handler(irq, action->dev_id);
		if (ret == IRQ_HANDLED)
			status |= action->flags;
		retval |= ret;
		action = action->next;
	} while (action);
 
	此处省略一些代码...
 
	return retval;
}
   可以看出来action是一个链表头,指向的是我们注册的中断处理函数,也就是我们使用request_irq()函数注册的中断处理函数,注册时我们需要带上相应的irq中断号,然后我们的处理函数就根据irq中断号,把我们的处理函数挂到不同的链表下面:

在这里插入图片描述

————————————————
版权声明:本文为CSDN博主「lee_jimmy」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/lee_jimmy/article/details/82953907

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值