版本:v1.1
摘要
此文详细介绍了中断,陷阱和异常之间的区别和联系。以及中断的各种分类,包括向量中断和非向量中断,内部中断和外部中断,软件中断和硬件中断可屏蔽中断和非可屏蔽中断,并且对向量中断和非向量中断进行了实例解析
2012-08-14
修订历史 | ||
---|---|---|
修订 1.0 | 2011-11-01 | crl |
| ||
修订 1.1 | 2012-08-14 | crl |
|
版权 © 2012 Crifan, http://crifan.com
目录
插图清单
表格清单
摘要
在中断出现之前,程序的执行,都是CPU一步步按照指令顺序执行下去的,中间即使有程序的跳转,但是CPU的执行的顺序,还是由程序中的代码的逻辑决定的。
首先要了解的背景知识是,CPU指令的执行速度和外设IO之间,前者很多都是纳秒(nm)级别的,后者很多都是毫秒(ms)级别的,所以时间量级上相差很大。
有了这个背景知识后,再去了解程序实际执行过程中,有时候会涉及到CPU要去和各种外设打交道。
比如,CPU发送完毕一个指令后,外设IO开始执行对应的动作,然后CPU就开始去读取对应的外设的状态,由于CPU指令执行很快,然后结果就是CPU执行了N次指令了,结果查询到的结果是,外设还没有执行完对应的动作。
最后的最后,好不容易等到外设执行外对应的动作了,然后才接着执行后面的指令。
此循环地,不断地去等待外设完成对应的动作,叫做轮询(Poll)。
可以发现,CPU在轮询过程中,就是在做无用功,而为了提高CPU的效率,不要让其浪费生命在无谓的轮询上,所以才会引出中断这个概念。
所谓的中断的方式,就是当CPU发送完指令,让外设开始去执行其对应的动作之后,不再像之前一样傻傻等待,不断地轮询,而是发送完指令后,让外设去干其该干的活,然后自己就返回了,继续执行其他的指令了,然后等过了会,外设干完活了,硬件上,会自动发送一个中断给CPU,通知其说,我活已经干完了,你可以过来处理后续该要处理的事情了,比如将对应的数据拷贝到对应的其他内容中等等之类的事情。
这套机制,就是对应的中断的概念,简要描述就是:
CPU对外设等设备做好对应的准备工作后,然后就继续执行其他CPU指令了,然后外设做完自己的事情之后,硬件上,会自动发送对应的中断给CPU的,而CPU发现有中断了,然后才会跳转到对应的中断服务程序,即常说的ISR(Interrupt Service Routine),做接下来的事情,比如将数据拷贝到对应的内容中等等。
关于中断的概念方面,此处不再赘述,因为这里有更加清晰易懂的解释:
摘要
说到中断,那么多很多人会想到其他几个相关的词,即异常和陷阱。
这几个概念,很多人都会搞混淆,所以,在此特别地,详细地解释一下。
首先,各自名字所对应的英文单词叫法分别是,中断是interrupt,异常是exception,陷阱是trap。
对于中断,异常和陷阱这几个词来说,其实还有其他一些相关的词:故障/错误fault,终止abort。对于这些名词,其实没有一个完全统一和精确的解释。
因此也出现,不同的厂家,不同的作者,有不同的各自的解释。
不过,相对来说,这么多名词中,最常用的,还是上面这三个:中断,异常和陷阱。
此处,对于其各自的含义,也采用大家所最常用的解释。
只是,如果你在别处看到对这些名词有不同的解释,也别太惊讶就行了。
此处的中断,一般也成为硬件中断,即外部的硬件发生了某些事件了,然后就通知到了当前CPU,CPU跳转到对应的ISR去执行了。
此处的外部的硬件中断,和当前执行的指令,是没有任何关系的。
而且往往都是外部设备,需要引起CPU的注意,比如某个按键被按下了,定时器超时了等等,才会去打断CPU,CPU才会跳转到ISR执行,然后执行完对应的ISR,再回来继续执行下一条指令。
可见,CPU此处是被动的,也不知道什么时候,属于不可预知的,就会受到外部的硬件中断,而外部的硬件中断,和当前CPU所执行的指令,没有任何关系,即属于异步关系。
异常,可以称为自动产生的陷阱,不是我们所期望产生的,而是被动的强制产生的。
我们执行的指令遇到了一些异常情况,比如(除法中出现了)除零,(指令执行时候遇到了)非法指令,内存非法访问(比如向只读的内存中写入数据)等,就会触发异常,CPU发现你程序在做非法的事情了,发现你在干坏事,那么当前要出来处理这些紧急事务了,即跳转到对应的异常处理程序中去,接下来要做的事情,可以有多种做法和选择,比如尝试修正此错误,或者简单地中止程序的运行,或者是打印出相关的错误信息(比如Windows遇到非法地址访问,会跳出对应的对话框提示你,某某地址,常见的是0地址,不能访问)。
可见,异常,对于CPU来说,也是被动的,不知道何时会发生的,但是和当前指令是有关系的,即当前执行指令出现错误的时候,才会出现异常,即是同步的。
陷阱,也常被叫做软件中断(software interrupt)。80x86系统中,有对应的INT指令,执行对应的INT指令,就会跳转到对应的函数中去执行对应的ISR了。
此处的陷阱,可以看出,是无条件执行的,而且是有意为之,是软件写的代码,故意的,让其产生对应的陷阱的。即只要执行了INT指令后,就会跳转到对应的ISR中去,而不像中断和异常那样需要满足相关的条件。
陷阱主要是用于调试的,比如用来通知调试器,某条执行被执行了,然后触发调试器做出对应的动作。
对于CPU来说,陷阱是由于执行了INT等指令而产生的,所以是(和当前指令的执行是)同步的(synchronous),对于CPU也是预知的,知道是什么时候会发生陷阱的,属于主动的,而不是被动的。
对于中断,异常和陷阱的各种关系,总结如下:
表 2.1. 中断,异常和陷阱的区别和联系
对CPU来说是 | 和当前CPU所执行的指令的关系 | CPU接下来的事情 | 程序员和用户的态度 | ||
---|---|---|---|---|---|
中断 | 被动的 | 异步的 | 没关系 | 跳转到对应的ISR | 希望有对应的中断,以使得CPU可以响应对应的中断,执行对应的ISR |
异常 | 被动的 | 同步的 | 有关系,因为就是当前指令执行出问题,才有的异常 | 跳转到对应的异常处理 | 不希望出现异常,如果出现了,那往往是指令执行出现某些错误了 |
陷阱 | 主动的 | 同步的 | 有关系,就是执行当前软中断指令,才进入的软中断 | 执行对应的软中断处理函数 | 对于想要实现调试功能的程序员,有需要此陷阱的必要,其他人不用关心此点 |
下面,用图表总结了,中断,陷阱和异常的区别:
注意 | |
---|---|
无论是中断,还是异常和陷阱,对应的处理函数,一般都可以称其为中断服务程序ISR,都只是一个函数 具体函数里面要做什么事情,是由你写程序的人决定的 比如中断中处理对应的事情,异常中自己决定如何响应出现的异常,陷阱中决定做什么事情。 |
摘要
中断,从不同的角度来区分的话,可以有不同的分类。
下面就来从不同的方面,不同的角度,不同的侧重点,来对常见的分类,做个详细的解释。
根据中断来源的方向不同,可分为内部中断和外部中断。
首先我们要搞懂,此处所说的内部中断和外部中断中的“内部”和“外部”,都是相对于CPU,或者说相对于CPU所执行的指令,来说的。
另外,广义上说,中断,就是中止,打断,即中止打断正在执行指令的CPU(而让其去执行别的ISR程序)的,都可以叫做中断。
所以,不论是狭义上的普通所说的中断,还是陷阱和异常,对于CPU执行正常的指令这个过程来说,都是会打断正常执行指令的顺序,所以都可以叫做中断。
所以,就很好理解这里的外部和内部的含义了:
针对于CPU执行指令来说,从外界过来干扰自己的,那种普通外设所产生的硬件的中断,就是外部中断;
而CPU本身执行的指令产生错误了或者本身就是执行的是INT等中断指令,那么就会产生异常或陷阱,就属于内部中断。
即对于广义上的中断的概念来说:
普通的硬件中断,属于外部中断;
陷阱和异常,属于内部中断。
根据中断的来源的性质不同,中断可分为软件中断和硬件中断。
根据前面对于中断,异常和陷阱的详细解释,所以此处也很好理解所谓的软件中断和硬件中断了。
即,从中断的来源来说,来自外部的外设所产生的中断,叫做硬件中断;
而从CPU内部所执行的(类似于80x86中的INT)指令所产生的中断,成为软件中断,是软件写的中断指令,即软件代码,所产生的中断,此处就是指陷阱。
所以,可以简单的理解为:
软件中断就是陷阱;
硬件中断就是普通的外设的中断;
中断根据如何系统如何处理和响应的方式,可以分为向量中断和非向量中断。
向量者,矢量也,即指方向,门路。
简单的说就是:
向量中断由硬件提供中断服务程序入口地址;
非向量中断由软件提供中断服务程序入口地址;
向量中断,是CPU得到此中断后,直接跳转到,对应的,固定的某个地址,该地址原先已经存放好了该中断所对应的中断服务程序ISR的地址了,即某个ISR函数的地址,所以CPU直接跳转到该位置,也就是直接跳转到对应的ISR,执行对应程序了。
对于非向量中断,是对应着多个函数,共享这个非向量中断的总入口地址,即可以理解为,多个非向量中断,其入口地址都是同一个地址,即普通的IRQ中断的那个地址,然后此地址中存放了通用的ISR函数的地址,该ISR函数中,会去经过一系列的判断,找出是具体真正的哪个外设的中断,然后再在对应的之前设置好的某个中断服务程序表中,找到对应的该中断所对应中断函数,去执行对应的真正的ISR函数。
以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,速度相对慢。