先解释几个名词的含义:
中断号:中断号也叫中断类型号,或者中断请求号。
中断是指在CPU运行期间,被CPU内部或外部事件所打断、暂停当前程序的执行而转去执行一段特定的处理内部或外部时间程序的过程。
外部设备进行I/O操作时,会随机产生中断请求信号。这个信号中会有特定的标志,使计算机能够判断是哪个设备提出中断请求,这个信号就叫做中断号。
中断有两种类型:硬中断和软中断(下半段机制)
硬中断
由与系统相连的外设(比如网卡、硬盘)自动产生的。主要是用来通知操作系统系统外设状态的变化。比如当网卡收到数据包的时候,就会发出一个中断。我们通常所说的中断指的是硬中断(hardirq
)。软中断
为了满足实时系统的要求,中断处理应该是越快越好。linux
为了实现这个特点,当中断发生的时候,硬中断处理那些短时间就可以完成的工作,而将那些处理事件比较长的工作,放到中断之后来完成,也就是软中断(softirq
)来完成。或者不采用软中断,使用 小任务机制tasklet、工作队列机制
硬中断和软中断的区别
1、软中断是执行中断指令产生的,而硬中断是由外设引发的。
2、硬中断的中断号是由中断控制器提供的,软中断的中断号由指令直接指出,无需使用中断控制器。
3、硬中断是可屏蔽的,软中断不可屏蔽。
4、硬中断处理程序要确保它能快速地完成任务,这样程序执行时才不会等待较长时间,称为上半部。
5、软中断处理硬中断未完成的工作,是一种推后执行的机制,属于下半部。
中断处理函数:中断是嵌入式系统中重要的组成部分,当中断发生以后,需要调用此函数进行相关的处理。当一个函数被定义为ISR
的时候,编译器会自动为该函数增加中断服务程序所需要的中断现场入栈和出栈代码。
ISR程序编写的要求
- 不能返回值;
- 不能向
ISR
传递参数; ISR
应该尽可能的短小精悍;printf(char * lpFormatString,…)
以及浮点运算等函数会带来 函数重入 和性能问题,不能在ISR
中采用。
为什么不能有返回值?
中断服务函数的调用是硬件级别的,当中断产生,pc指针强制跳转到对应的中断服务函数入口,进入中断具有随机性,并不是某段代码对其进行调用,那么如果有返回值它的返回值返回给谁?显然这个返回值毫无意义,如果有返回值,它必定需要进行压栈操作,这样一来何时出栈怎么出栈将变得无法解决。
不能向ISR传递参数?
同理,也是由于这样会破坏栈的原因,因为函数传递参数必定会要求压栈出栈操作,由于进入中断服务函数的随机行,谁给它传递参数都成问题
像STM32
等,因为中断都是些硬件自动调用,没有程序去给他传递参数,也没有硬件去接收参数。 那其它函数怎么传参数给它呢?一般都是通过全局变量方式。不过为了避免中断函数重入,我们可以在进入中断服务函数之后将中断关闭,处理完后再开中断。
ISR应尽可能的短小精悍?
如果某个中断频繁产生,而它对应的ISR相当的耗时,那么对中断的响应就会无限的延迟,会丢掉很多的中断请求
printf(char * lpFormatString,…)函数会带来重入和性能问题,不能在ISR中采用。
这就涉及到一个中断嵌套问题,由于printf
之类的glibc
函数采用的是缓冲机制,这个缓冲区是共享的,相当于一个全局变量,第一层中断来时,它向缓冲里面写入一些部分内容,恰好这时来了个优先级更高的中断,它同样调用了printf
,也向缓冲里面写入一些内容,这样缓冲区的内容就错乱了。
面试题
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(" Area = %f", area);
return area;
}
这个函数有太多的错误了,以至让人不知从何说起了:
- 1、
ISR
不能返回一个值。 - 2、
ISR
不能传递参数。 - 3、在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在
ISR
中做浮点运算。此外,ISR
应该是短而有效率的,在ISR
中做浮点运算是不明智的。 - 4、与第三点一脉相承,
printf()
经常有重入和性能上的问题。
什么是函数的重入?
所谓重入,就是指某段子程序还没有执行完,就因为中断或者是多任务操作系统的调度原因,导致该子程序在一个新的寄存器上下文中被执行
引发函数重入的原因:中断、任务调度、递归
函数可重入的条件
一个可重入的函数简单来说就是可以被中断的函数,也就是说,可以在这个函数执行的任何时刻中断它,转入OS调度下去执行另外一段代码,而返回控制时不会出现什么错误。
满足下列条件的函数多数是不可重入的:
1、函数体内使用了全局变量(可能产生线程安全问题);
2、函数体内使用了静态的数据结构(可能产生线程安全问题)
3、函数体内调用了malloc()或者free()函数;
4、函数体内调用了标准I/O函数;标准io库很多实现都以不可重入的方式使用全局数据结构
5、函数体内进行了浮点运算。许多的处理器/编译器中,浮点一般都是不可重入的 (浮点运算大多6、使用协处理器或者软件模拟来实现
保证函数可重入的方法
1、使用局部变量
2、不调用不可重入函数
3、如果确实需要访问全局变量(包括static),一定要注意实施互斥手段:如关中断、信号量(即P、V操作)等手段对其加以保护。
备注:线程通讯(锁):(1)信号量(2)读写锁(3)条件变量(4)互斥锁(5)自旋锁
函数递归:函数自己直接或间接调用自己
函数递归的注意点
函数递归也是一种重入
避免递归的深度很深时,会造成函数栈的溢出。
中断处理函数与回调函数的区别与联系?
回调函数的原理是使用函数指针实现类似“软中断”的概念。
比如在上层的两个函数A和B,把自己的函数指针传给了C,C通过调用A和B的函数指针达到“当做了什么,通知上层来调用A或者B”的目的。从更底层的角度上,代码之间都是在一段程序里面或者可以理解为代码段的跳转。通过标准的call ret就可以实现的。
中断处理函数:
首先,要了解CPU的底层处理机制。CPU对中断,错误的处理有三种:
1,错误(fault),这种处理方式会跳到错误的处理程序中,当从错误的处理程序返回,会重新执行当前的指令(再执行一遍出错的那条指令)
2,陷阱(trap),也会跳到陷阱的处理函数中,当从陷阱函数中返回,执行下一条指令。
3,异常(abort),异常终止当前程序。
其实对于中断,类似于trap的过程。表面看来,他和回调函数都是一样的概念,都是,发生中断--->跳到中断处理函数里面--->回到中断点下一条--->清中断,
中断与回调的区别和联系:
1,中断可能实现不同优先级代码的跳转。比如我发生了软中断,比如接到一个信号,我就要跳到相对应中断号的中断服务函数里面执行,实现信号处理函数跳转的,是一个内核级的代码段。
2,有些中断,是通过回调实现的,比如windows的视频采集,就是一个帧中断,但是你注册给上层的是一个回调。
3,关于中断的可重入,这个和回调不同,具体这又是一个话题了。
4,应用场合不同。
5、中断是一种行为,特指用户态程序主动或被动地陷入内核态的动作。回调是一种约定俗成的风格,指传递一个处理函数给托管程序,从而实现托管框架内的自定义行为。某种意义上,内核也属于托管程序,因此可以设置中断向量表指向回调函数。
中断向量表:
中断向量表就是中断向量的列表。中断向量表在内存中保存,其中存放着 中断源所对应的中断处理程序的入口地址。
由于CPU随时都可能检测到中断信息,也就是说,CPU 随时都可能执行中断处理程序,所以中断处理程序必须一直存储在内存某段空间之中。而中断处理程序的入口地址,即中断向量,必须存储在对应的中断向量表表项中。
CPU的设计者必须在中断信息和其处理程序的入口地址之间建立某种联系,使得CPU根据中断信息可以找到要执行的处理程序。中断信息中包含有标识中断源的类型码。根据CPU的设计,中断类型码的作用就是用来定位中断处理程序。中断向量表就是从0号单元开始,用来保存各个中断程序入口地址的一段内存单元,其大小为1k(不同系统不同)。