3.3.1. 向量中断和非向量中断实例解析

以ARM系统为例,物理地址最开始的几个地址,存放的是对应的几个最常见的向量中断,如数据中止异常,软中断,预取指错误异常等,还有一个是其他非向量中断的总入口IRQ。

此部分内容,可以参考Uboot中的ARM的初始化部分start.S中的代码来解释:

其相关代码如下:

_start:	b       reset
	ldr	pc, _undefined_instruction
	ldr	pc, _software_interrupt
	ldr	pc, _prefetch_abort
	ldr	pc, _data_abort
	ldr	pc, _not_used
	ldr	pc, _irq
	ldr	pc, _fiq

_undefined_instruction:	.word undefined_instruction
_software_interrupt:	.word software_interrupt
_prefetch_abort:	.word prefetch_abort
_data_abort:		.word data_abort
_not_used:		.word not_used
_irq:			.word irq
_fiq:			.word fiq
。。。
/*
 * exception handlers
 */
	.align  5
undefined_instruction:
	get_bad_stack
	bad_save_user_regs
	bl 	do_undefined_instruction

	.align	5
software_interrupt:
	get_bad_stack
	bad_save_user_regs
	bl 	do_software_interrupt

	.align	5
prefetch_abort:
	get_bad_stack
	bad_save_user_regs
	bl 	do_prefetch_abort

	.align	5
data_abort:
	get_bad_stack
	bad_save_user_regs
	bl 	do_data_abort

	.align	5
not_used:
	get_bad_stack
	bad_save_user_regs
	bl 	do_not_used
。。。

	.align	5
irq:
	sub	lr, lr, #4			        @ the return address
	ldr	sp, IRQ_STACK_START	        @ the stack for irq
	stmdb	sp!, 	{ r0-r12,lr }	@ save registers
	
	ldr	lr,	=int_return		        @ set the return addr
	ldr	pc, =IRQ_Handle		        @ call the isr
int_return:
	ldmia	sp!, 	{ r0-r12,pc }^	@ return from interrupt

	.align	5
fiq:
	get_fiq_stack
	/* someone ought to write a more effiction fiq_save_user_regs */
	irq_save_user_regs
	bl 	do_fiq
	irq_restore_user_regs
        

可看出,物理内存最开始的存放的内容是:

地址0x0: reset整个系统

地址0x04:放了一个指令,该指令是将_undefined_instruction存入PC,即实现PC跳转到_undefined_instruction的地址中去;

地址0x08:同理,PC跳转到_software_interrupt

地址0x0C:同理,PC跳转到_prefetch_abort

地址0x10:同理,PC跳转到_data_abort

地址0x14:同理,PC跳转到_not_used

地址0x18:同理,PC跳转到_irq

地址0x1C:同理,PC跳转到_fiq

其中,对于_undefined_instruction,很明显,就是我们之前所解释的异常,即指令执行出了对应的问题了,PC会直接跳转到此处的0x04的地址,然后该地址中,就是把PC跳转到对应的_undefined_instructio的位置,去执行对应的异常处理。

其他的_software_interrupt和_data_abort等,都是同样道理,不多解释。

而上述这些异常或_software_interrupt,就都是所谓的中断向量,都是由硬件架构决定的,固定好的了地址,作为软件开发人员,只要把对应的指令写好,到时候发生对应的异常,系统自动会跳转到此处的地址,实现对应的PC的跳转,去做对应的处理。

而对于0x18处的_irq,就是我们所说的所有的非向量中断的总的入口地址,即系统发现有中断了,此处发现是普通的中断,那么就会跳转到0x18的地址这里,然后执行的是:

PC跳转到_irq,而_irq地址所对应的内容是:保存对应的当前的环境,即上下文,然后执行“ldr pc, =IRQ_Handle”,即跳转到IRQ_Handle函数中去。

而以TQ2440的S3C2410为例,其代码为:

interrupts.c (opt\embedsky\u-boot-1.1.6\cpu\arm920t\s3c24x0)


void Isr_Init(void)
{
    int i = 0;
    intregs = S3C24X0_GetBase_INTERRUPT();

    for (i = 0; i < sizeof(isr_handle_array) / sizeof(isr_handle_array[0]); i++ )
    {
        isr_handle_array[i] = Dummy_isr;
    }

    intregs->INTMOD=0x0;                // All=IRQ mode
    intregs->INTMSK=BIT_ALLMSK;         // All interrupt is masked.

    //pISR_URXD0=(unsigned)Uart0_RxInt;
    //rINTMSK=~(BIT_URXD0);             //enable UART0 RX Default value=0xffffffff

    isr_handle_array[ISR_TIMER4_OFT] = IsrTimer4;
    isr_handle_array[ISR_WDT_OFT]  = IsrWatchdog;

#ifdef CONFIG_USB_DEVICE
    isr_handle_array[ISR_USBD_OFT] = IsrUsbd;
    isr_handle_array[ISR_DMA2_OFT] = IsrDma2;
    ClearPending(BIT_DMA2);
    ClearPending(BIT_USBD);
#endif
}

void IRQ_Handle(void)
{
    unsigned long oft = intregs->INTOFFSET;
    S3C24X0_GPIO * const gpio = S3C24X0_GetBase_GPIO();

//  printk("IRQ_Handle: %d\n", oft);

    if( oft == 4 ) gpio->EINTPEND = 1<<7;       
    intregs->SRCPND = 1<<oft;
    intregs->INTPND = intregs->INTPND;

    /* run the isr */
    isr_handle_array[oft]();
}

        

可见,其中IRQ_Handle做的事情,就是去读取对应的寄存器,然后经过计算,找到真正的中断源的偏移量,然后再通过偏移量,在中断函数表中,去获得对应该中断的中断服务程序ISR。

而其中的中断函数表isr_handle_array是在程序最开始初始化时候去调用Isr_Init来初始化好的,已经见每个中断多对应的ISR函数存放了对应的位置了。

向量中断的优点是,反应速度快,有了中断,CPU直接跳转到对应的位置,去执行对应的代码了,属于速度快,但是无法扩展,由硬件设计时候觉得的,固定好了,没法改变。

而非向量中断,由于多了一层调用关系,而且在总的普通中断的入口函数中,要去读取寄存器,再去计算到底是哪个中断,所以,速度上,就相对较慢了,属于速度慢,但是扩展性较好。

简单的说就是:

硬件中断,由硬件提供ISR地址,速度较快;

软件中断,由软件计算出中断源,再去找出对应的ISR,速度相对慢。