中断
中断的关键要素
- 中断处理流程
- 标志位(Flag)
- 使能(Enable)
- 中断服务程序ISR
- 中断向量表
- 中断嵌套和中断优先级
- 中断潜伏期
中断 and 轮询
形象解释
轮询:一个简单的例子解释轮询就是有一个人反复的打电话,询问某人在不在这样的一种机制就可以称之为轮询。
中断:可以比喻做一个人打电话给某人,这时,另一个人接到电话,这个人给接电话的人说,请等他来了以后给我回个电话,这个时候,打电话的人就可以做其他的事,等电话铃响的时候,去接电话,把刚刚要计划做的事情完成。这样的一种机制就可以被称之为中断。
严谨解释
- 轮询
- 周期/连续的检查外部事件是否发生
- 消耗大量CPU的处理时间
- 轮询过程需要和其他功能代码相结合
- 由于CPU需要处理其他事件(可能是无关紧要的),可能丢失关键事件
- 中断
- 由硬件来判断是否发生外部事件并通知CPU
- 专用的中断服务程序来处理事件
什么是中断
- 中断是一个需要CPU立刻处理的内部/外部事件
- 内部事件:定时器定时时间到
AD变换结束
… - 外部事件:按键动作
发生外部通信
…
- 内部事件:定时器定时时间到
- 内部/外部事件请求CPU处理
- CPU停止正常流程,执行中断服务程序ISR
- ISR结束后,CPU返回正常流程
中断和轮询的特点
- 中断
- 适合处理对响应要求非常高的事件
- 适合处理持续事件非常短的事件
- 适合低功耗的应用
- 程序设计较为复杂
- 轮询
- 适合处理对于时间响应要求低的场合
- 程序设计简单
中断的允许/禁止控制
- 中断允许/禁止
- 全局中断控制
- CPU的CCR寄存器中的一个特殊位
- 在复位后,全局禁止位是置起的
- 中断发生后,全局禁止位也被置起
- 全局中断控制
中断的标志位
- IF(中断标志位)
- 每一个中断源都有对应的中断标志
- 中断标志位将引发向CPU的中断请求
- 通过读写操作可以清楚中断标志位
中断的工作流程
- 内部/外部事件请求CPU处理
- CPU停止正常流程,执行中断服务程序ISR
- ISR结束后,CPU返回正常流程
下图就是中断执行的一个流程:
中断的堆栈占用
我们知道在发生函数调用的时候,要返回函数调用之后的那段代码继续执行,也就是要保存函数的返回地址。对于中断发生这种情况来讲,函数在正常执行的过程当中,中断可能会发生在任意时刻,而在中断发生后,中断程序也执行完成之后,函数需要返回发生中断时的地址,这样的动作也被称为还原现场。运用计算机的专业术语来描述,这样的机制也被称作是保存返回地址和寄存器上下文。
对于CPU来讲,指令一条一条地运行,最终保存的信息,也就是CPU的寄存器的值,这些寄存器的值记录了我们执行了第几条指令,记录了我们堆栈用到了哪里,记录了CPU片内的寄存器使用以及存储的一些值。因此,在发生中断时,在打断的那个时间点上,保留CPU运行的所有寄存器的当前值,如果在考虑到内存状态的可恢复性,我们一旦在中断程序执行完毕后,把这些寄存器的值还原到中断发生之前的那一个瞬间,那么这一整个过程对于主程序而言就好像都没有发生过。
出于效率的考虑,CPU在运行的时候,很有可能用不到所有的寄存器,因此当发生中断的时候,CPU会有选择性的压栈部分寄存器。比如:
当ARM Cortex的CPU发生中断的时候,CPU会把如下所示的寄存器压入堆栈,R0,R1,R2,R3,R12,R14(LR),R15(PC),xPSR
这里需要注意的两点是:
- 中断的寄存器入栈由CPU硬件自动完成
- 对于具体的CPU,在中断时哪些寄存器被压入堆栈以及寄存器在堆栈中的保存顺序是在其手册中指明的。
- 如果中断的发生,寄存器发生变化的不止上述所说的这8个寄存器,那么就需要我们开发者自己手动的把发生变化的寄存器压入堆栈。
中断服务子程
- 中断服务子程(ISR)
- 在一些CPU中,中断服务子程不同于一般的C函数,函数退出时的返回汇编指令有所区别。(如RTI中断返回指令需要完成堆栈操作)
- 在ARM Cortex M0+平台上,中断服务子程与一般C语言写法没有区别,使用同样的汇编返回指令即可。(堆栈弹出由NVIC自动完成),它所采用的机制是,在中断发生的时候,会再LR(R14寄存器)里保存一个特殊值,从而在执行一个普通的函数返回指令的时候,CPU会根据LR寄存器的值从而知晓自己是一个中断函数。
- 中断服务子程ISR共同的特点:
- 是被CPU硬件自动调用的,而不是由其他程序在代码中调用。
- 在ISR执行前、后,CPU自动进行了堆栈出入等操作。
- 写成C语言的参数和返回值都应该为void;
中断向量表
- 中断向量表是一段连续的存储空间
- 在复位之后有默认的起始地址
- 每个中断在向量表中都有相应的表项,该表项的值为该中断对应的服务程序的地址(地址指针),因此即便中断服务函数不在主函数内得到调用,但是当中断发生的时候,只要把中断向量表里的内容赋值给PC指针,程序相应的就会发生跳转。
- 由程序代码确定中断向量表的每个表项
- 中断向量表的位置是可以通过改写中断向量基址寄存器重新定位的
中断优先级
- 多个中断同时出现,高优先级中断先得到响应。
- 中断优先级可以是固定的或者是编程指定的
- 固定优先级:根据中断向量表顺序(比如S12内核)
- 设定优先级:每个中断都有优先级设置位(比如ARM Cortex M0+支持4个优先级)
- 当优先级相同的中断发生,就按照先后顺序进行处理。
中断嵌套
- 中断嵌套指的是CPU在执行一个中断服务程序的过程中,可以再次相应新的中断请求。
- 在进入中断服务程序时,CPU硬件会将全局中断使能位关闭。为了实现中断嵌套,必须在ISR开始重新允许全局中断(有多优先级的情况下,选择允许到哪一个级别的中断)
- 中断嵌套的层数(ARM Cortex M0+只有4个优先级)
- 中断嵌套对于堆栈的消耗很大。(但是嵌套层数和内存开销可控)
中断嵌套时的堆栈
像上图所示,主程序执行的时候,发生中断,压栈保留一个现场执行中断函数,中断函数没有执行完,又来了一个更高优先级的中断,只能再次打断它,在保留一个CPU的现场,再占一部分堆栈去执行更高优先级的中断,而在退出的时候,依次退出和还原现场,这个过程也就是中断的嵌套。
总结
总体来说,中断的主要有以下几个需要注意的地方:
- 中断的发生需要使用到堆栈,中断发生的一刻需要保存CPU片内寄存器的值以及函数的返回地址
- 针对于ARM Cortex来讲,中断服务函数的写法就如普通的C语言函数的写法一致,函数的返回值和形参都是void。而对于CPU来讲,要区分是中断服务函数还是普通C语言函数,所具有的机制是,当发生中断时,CPU的R14(LR)寄存器会保存一个特殊的值,因此CPU会根据R14寄存器的内容从而判断该函数是否是一个中断服务函数。
- 要实现具体的中断源和某一个中断服务函数所对应,就需要中断向量表,中断向量表每一个表项保存的内容就是中断服务函数的地址,因此,当中断发生时,只需要将中断向量表里的值赋值给PC指针,中断服务函数就能够得到执行。
- 中断可以发生中断嵌套,但是如果嵌套层数过多,可能发生堆栈溢出。