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< intregs->INTPND = intregs->INTPND; /* run the isr */ isr_handle_array[oft](); } 可见,其中IRQ_Handle做的事情,就是去读取对应的寄存器,然后经过计算,找到真正的中断源的偏移量,然后再通过偏移量,在中断函数表中,去获得对应该中断的中断服务程序ISR。 而其中的中断函数表isr_handle_array是在程序最开始初始化时候去调用Isr_Init来初始化好的,已经见每个中断多对应的ISR函数存放了对应的位置了。 向量中断的优点是,反应速度快,有了中断,CPU直接跳转到对应的位置,去执行对应的代码了,属于速度快,但是无法扩展,由硬件设计时候觉得的,固定好了,没法改变。 而非向量中断,由于多了一层调用关系,而且在总的普通中断的入口函数中,要去读取寄存器,再去计算到底是哪个中断,所以,速度上,就相对较慢了,属于速度慢,但是扩展性较好。 简单的说就是: 硬件中断,由硬件提供ISR地址,速度较快; 软件中断,由软件计算出中断源,再去找出对应的ISR,速度相对慢。