freertos实时操作系统临界段保护开关中断及进入退出的方法
今天小编给大家分享一下freertos实时操作系统临界段保护开关中断及进入退出的方法的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
中断的基础知识
嵌套:
嵌套向量中断控制器 NVIC(Nested Vectored Interrupt Controller与内核是紧耦合的。提供如下的功能:可嵌套中断支持、向量中断支持、动态优先级调整支持、中断延迟大大缩短、 中断可屏蔽。
所有的外部中断和绝大多数系统异常均支持可嵌套中断。异常都可以被赋予不同的优先级,当前优先级被存储在 xPSR的专用字段中。当一个异常发生时,硬件自动比较该异常的优先级和当前的异常优先级,如果发现该异常的优先级更高,处理器就会中断当前的中断服务例程(或者是普通程序),而服务新来的异常(立即抢占)。
如果优先级组设置使得中断嵌套层次很深,要确认主堆栈空间足够用。 异常服务程序总是使用MSP,主堆栈的容量应是嵌套最深时需要的量。
优先级:
CM3 支持中断嵌套,使得高优先级异常会抢占(preempt)低优先级异常。
有3个系统异常:复位,NMI 以及硬 fault,它们有固定的优先级,并且优先级号是负数,高于所有其它异常。所有其它异常的优先级都是可编程的(但不能编程为负数)。
CM3 支持3个固定的高优先级和多达256级的可编程优先级,并且支持128级抢占。但是大多数CM3芯片实际上支持的优先级数会更少如8级、16级、32级等。
裁掉表达优先级的几个低端有效位,从而让优先级数减少。如果使用更多的位来表达优先级,优先级数增加,需要的门也更多,带来更多的成本和功耗。
使用3个位来表达优先级,优先级配置寄存器的结构如下图所示,能够使用的8个优先级为:0x00(最高),0x20,0x40,0x60,0x80, 0xA0,0xC0,0xE0。
为了使抢占机能变得更可控,CM3 把 256 级优先级按位分成高低两段,分别是抢占优先级和亚优先级。抢占优先级决定了抢占行为。当抢占优先级相同的异常有不止一个悬起时,就优先响应亚优先级最高的异常(亚优先级处理内务)。
优先级分组规定:亚优先级至少是1个位。所以抢占优先级最多是7个位,最多只有 128 级抢占的现象。
下图是只使用 3 个位来表达优先级,从bit5处分组,得到4级抢占优先级,每个抢占优先级的内部有2个亚优先级。
下图是3 位优先级,从比特1处分组,(虽然[4:0]未使用,却允许从它们中分组)。
应用程序中断及复位控制寄存器(AIRCR)(地址:0xE000_ED00)如下。
中断的悬起与解悬:
中断发生时,正在处理同级或高优先级异常,或者被掩蔽,则中断不能立即得到响应。此时中断被悬起。
可以通过中断设置悬起寄存器(SETPEND)、中断悬起清除寄存器(CLRPEND)读取中断的悬起状态,还可以写它们来手工悬起中断。
咬尾中断Tail‐Chaining:
处理器在响应某异常时,如果又发生其优先级高的异常,当前异常被阻塞,转而执行优先级高的异常。那么异常执行返回后,系统处理悬起的异常时,如果先POP再把POP出的再PUSH回去,这就是浪费CPU时间。
所以CM3不POP这些寄存器,而是继续使用上一个异常已经PUSH好的成果。如下图所示。
晚到的高优先级异常:
入栈的阶段,尚未执行其服务例程时,如果此时收到了高优先级异常的请求,入栈后,将执行高优先级异常的服务例程。如果高优先级异常来得太晚,以至于已经执行了前一个异常的指令,则按普通的抢占处理,这会需要更多的处理器时间和额外32字节的堆栈空间。高优先级异常执行完毕后,以咬尾中断方式执行之前被抢占的异常。
cortex-m里面开中断、关中断指令
临界段:一段在执行的时候不能被中断的代码段(必须完整运行、不能被打断的代码段)。一般是对全局变量操作时候,用到临界段。当一个任务在访问某个全局变量时,如果被其他中断打断,改变了该全局变量,再回到上个任务时,全局变量已经不是当时的它了,这种情况可能会导致不可意料的后果。
临界段被打断的情况:系统调度(最终也是产生PendSV中断);外部中断。
freertos进入临界段代码时需要关闭中断,处理完临界段代码再打开中断。
首先看下面的代码。
__asm void prvStartFirstTask( void ){PRESERVE8ldr r0, =0xE000ED08ldr r0, [r0]ldr r0, [r0]msr msp, r0cpsie icpsie fdsbisbsvc 0nopnop}__asm void vPortSVCHandler( void ){extern pxCurrentTCB;PRESERVE8ldrr3, =pxCurrentTCBldr r1, [r3]ldr r0, [r1]ldmia r0!, {r4-r11}msr psp, r0isbmov r0, #0 msrbasepri, r0 orr r14, #0xdbx r14}__asm void xPortPendSVHandler( void ){extern pxCurrentTCB;extern vTaskSwitchContext;PRESERVE8mrs r0, pspisbldrr3, =pxCurrentTCBldrr2, [r3] stmdb r0!, {r4-r11}str r0, [r2] stmdb sp!, {r3, r14} mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0dsbisbbl vTaskSwitchContext mov r0, #0 msr basepri, r0ldmia sp!, {r3, r14} ldr r1, [r3]ldr r0, [r1] ldmia r0!, {r4-r11}msr psp, r0isbbx r14nop}
cpsie icpsie fmsrbasepri, r0
cpsie icpsie fmsrbasepri, r0
为了快速地开关中断,CM3 专门设置了 CPS 指令,有 4 种用法。
CPSID I ;PRIMASK=1, ;关中断CPSIE I ;PRIMASK=0, ;开中断CPSID F ;FAULTMASK=1, ;关异常CPSIE F ;FAULTMASK=0 ;开异常
可以看到,上面指令还是控制的PRIMASK和FAULTMASK寄存器。
如下图所示,可以通过CPS 指令打开全局中断或者关闭全局中断。
basepri是中断屏蔽寄存器,下面这个设置,优先级大于等于11的中断都将被屏蔽。相当于关中断进入临界段。
mov r0, #configMAX_SYSCALL_INTERRUPT_PRIORITY msr basepri, r0191转成二进制就是11000000,高四位就是1100*/
下面这个代码:优先级高于0的中断被屏蔽,相当于是开中断退出临界段。
mov r0, #0 msr basepri, r0
关中断和开中断
下面这个代码,带返回值的意思是:往BASEPRI写入新的值的时候,先将BASEPRI的值保存起来,更新完BASEPRI的值的时候,将之前保存好的BASEPRI的值返回,返回的值作为形参传入开中断函数。
不带返回值的意思是:在往 BASEPRI 写入新的值的时候,不用先将 BASEPRI 的值保存起来, 不用管当前的中断状态是怎么样的,既然不用管当前的中断状态,也就意味着这样的函数不能在中断里面调用。
#define portDISABLE_INTERRUPTS()vPortRaiseBASEPRI()#define portENABLE_INTERRUPTS()vPortSetBASEPRI( 0 )#define portSET_INTERRUPT_MASK_FROM_ISR()ulPortRaiseBASEPRI()#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)vPortSetBASEPRI(x)#define configMAX_SYSCALL_INTERRUPT_PRIORITY 191 static portFORCE_INLINE void vPortRaiseBASEPRI( void ){uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;static portFORCE_INLINE void vPortRaiseBASEPRI( void ){uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{msr basepri, ulNewBASEPRIdsbisb}}static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void ){uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{mrs ulReturn, baseprimsr basepri, ulNewBASEPRIdsbisb}return ulReturn;}static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI ){__asm{msr basepri, ulBASEPRI}}
进入临界段和退出临界段
对于不带中断保护情况,vPortEnterCritical函数里面的uxCriticalNesting是一个全局变量,记录临界段嵌套次数,vPortExitCritical函数每次将uxCriticalNesting减一,只有当uxCriticalNesting = 0才会调用portENABLE_INTERRUPTS函数使能中断。这样的话,在有多个临界段代码的时候,不会因为某一个临界段代码的退出而打断其他临界段的保护,只有所有的临界段代码都退出后,才会使能中断。
带中断保护的,主要就是往BASEPRI写入新的值的时候,先将BASEPRI的值保存起来,更新完BASEPRI的值的时候,将之前保存好的BASEPRI的值返回,返回的值作为形参传入开中断函数。
#define taskENTER_CRITICAL() portENTER_CRITICAL()#define taskEXIT_CRITICAL() portEXIT_CRITICAL()#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )#define portENTER_CRITICAL()vPortEnterCritical()#define portEXIT_CRITICAL()vPortExitCritical()#define portSET_INTERRUPT_MASK_FROM_ISR()ulPortRaiseBASEPRI()#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)vPortSetBASEPRI(x)void vPortEnterCritical( void ){ portDISABLE_INTERRUPTS();uxCriticalNesting++;if( uxCriticalNesting == 1 ){configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );}}void vPortExitCritical( void ){configASSERT( uxCriticalNesting );uxCriticalNesting--;if( uxCriticalNesting == 0 ){ portENABLE_INTERRUPTS();}}static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void ){uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{mrs ulReturn, baseprimsr basepri, ulNewBASEPRIdsbisb}return ulReturn;}static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI ){__asm{msr basepri, ulBASEPRI}}
{ uint32_t ulReturn; ulReturn = taskENTER_CRITICAL_FROM_ISR(); taskEXIT_CRITICAL_FROM_ISR( ulReturn );}{ taskENTER_CRITICAL(); taskEXIT_CRITICAL();}
以上就是“freertos实时操作系统临界段保护开关中断及进入退出的方法”这篇文章的所有内容,感谢各位的阅读!相信大家阅读完这篇文章都有很大的收获,小编每天都会为大家更新不同的知识,如果还想学习更多的知识,请关注编程网行业资讯频道。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341