以下转载自安富莱电子:
临界段代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。 FreeRTOS 临界段相关知识补充FreeRTOS 的源码中有多处临界段的地方, 临界段虽然保护了关键代码的执行不被打断, 但也会影响系统的实时性。比如此时某个任务正在调用系统 API 函数,而且此时中断正好关闭了,也就是进入到了临界区中,这个时候如果有一个紧急的中断事件被触发,这个中断就不能得到及时执行,必须等到中断开启才可以得到执行, 如果关中断时间超过了紧急中断能够容忍的限度, 危害是可想而知的。
FreeRTOS 源码中就有多处临界段的处理,跟 FreeRTOS 一样,uCOS-II 和 uCOS-III 源码中都是有临界段的,而 RTX 的源码中不存在临界段。 另外,除了 FreeRTOS 操作系统源码所带的临界段以外,用户写应用的时候也有临界段的问题,比如以下两种: 读取或者修改变量(特别是用于任务间通信的全局变量)的代码,一般来说这是最常见的临界代码。 调用公共函数的代码,特别是不可重入的函数,如果多个任务都访问这个函数,结果是可想而知的。总之,对于临界段要做到执行时间越短越好,否则会影响系统的实时性。
任务代码临界段处理FreeRTOS 任务代码中临界段的进入和退出主要是通过操作寄存器 basepri 实现的。进入临界段前操作寄存器 basepri 关闭了所有小于等于宏定义 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY所定义的中断优先级,这样临界段代码就不会被中断干扰到,而且实现任务切换功能的 PendSV 中断和滴答定时器中断是最低优先级中断,所以此任务在执行临界段代码期间是不会被其它高优先级任务打断的。退出临界段时重新操作 basepri 寄存器,即打开被关闭的中断(这里我们不考虑不受 FreeRTOS 管理的更高优先级中断)。 FreeRTOS 进入和退出临界段的函数如下:
#define taskENTER_CRITICAL() portENTER_CRITICAL()#define taskEXIT_CRITICAL() portEXIT_CRITICAL()上面这两个函数是供用户调用的,其中函数 taskENTER_CRITICAL 是进入临界段,函数taskEXIT_CRITICAL 是退出临界段。 进一步跟踪宏定义的实现如下:#define portENTER_CRITICAL() vPortEnterCritical()#define portEXIT_CRITICAL() vPortExitCritical()再进一步跟踪宏定义的实现如下:
通过上面的两个函数 vPortEnterCritical 和 vPortExitCritical 可以看出,进入临界段和退出临界段是通过函数调用开关中断函数 portENABLE_INTERRUPTS 和 portDISABLE_INTERRUPTS 实现的。 细心的读者还会发现上面的这两个函数都对变量 uxCriticalNesting 进行了操作。这个变量比较重要,用于临界段的嵌套计数。初学的同学也许会问这里直接的开关中断不就可以了吗,为什么还要做一个嵌套计数呢?主要是因为直接的开关中断方式不支持在开关中断之间的代码里再次执行开关中断的嵌套处理,假如当前我们 的代码是关闭中断的,嵌套了一个含有开关中断的临界区代码后,退出时中断就成开的了,这样就出问题了。 通过嵌套计数就有效地防止了用户嵌套调用函数 taskENTER_CRITICAL 和 taskEXIT_CRITICAL 时出错。
通过上面的源码实现可以看出,FreeRTOS 的开关全局中断是通过操作寄存器 basepri 实现的,关于这个寄存器,我们已经在前面进行了详细的讲解,这里不再赘述。 使用的时候一定要保证成对使用 。
中断服务程序临界段处理与任务代码里临界段的处理方式类似,中断服务程序里面临界段的处理也有一对开关中断函数。#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )进一步跟踪宏定义的实现如下:#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)再进一步跟踪宏定义的实现如下:
通过上面的源码可以看出,中断服务程序里面的临界段代码的开关中断也是通过寄存器 basepri 实现的。初学的同学也许会问,这里怎么没有中断嵌套计数了呢?是的,这里换了另外一种实现方法,通过保存和恢复寄存器 basepri 的数值就可以实现嵌套使用。如果大家研究过 uCOS-II 或者 III 的源码,跟这里的实现方式是一样的,具体看下面的使用举例。
使用举例:使用的时候一定要保证成对使用
开关中断的实现FreeRTOS 也专门提供了一组开关中断函数,实现比较简单,其实就是前面 15.2 小节里面临界段进入和退出函数的精简版本,主要区别是不支持中断嵌套。 具体实现如下:#define taskDISABLE_INTERRUPTS() portDISABLE_INTERRUPTS()#define taskENABLE_INTERRUPTS() portENABLE_INTERRUPTS()进一步跟踪宏定义的实现如下:#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI( 0 )
函数 vPortRaiseBASEPRI 和 vPortSetBASEPRI 的源码实现如下:
从上面的源码可以看出, FreeRTOS 的全局中断开关是通过操作寄存器 basepri 实现的,关于这个寄存器,我们已经在前面进行了详细的讲解,这里不再赘述。
使用的时候一定要保证成对使用
BSP 板级支持包中开关中断的特别处理前面为大家讲解了 FreeRTOS 临界段的处理方法和开关中断方法,加上了 FreeRTOS 操作系统后,我们实际编写的外设驱动又该怎么修改呢?因为外设驱动编写时,有些地方有用到开关中断操作,这里以此教程配套的 STM32F103,F407 和 F429 开发板为例进行说明,这三种开发板的外设驱动的编写架构都是统一的,用户只需将 bsp.h 文件里面的宏定义:
#define USE_FreeRTOS 1将中断开关设置改成了条件编译的形式,这样在使用裸机或者使用 FreeRTOS 时,切换自如。此宏定义配置为 1 表示使用 FreeRTOS 的开关中断 API 函数,配置为 0 表示使用裸机的方式开关中断。 采用 taskENTER_CRITICAL()和 taskEXIT_CRITICAL()实现开关中断因为 BSP 驱动包的源码基本没有在中断里面进行开关中断,都是在中断以外,所以开关中断是采用的任务代码里面临界段的处理函数,而且支持嵌套调用。大家写的工程代码也可以采用类似的方案,方便裸机和 FreeRTOS 的切换,或者采用其它适合自己的方案。 另外要注意,因为 FreeRTOS 存在不受其控制的更高优先级中断,用户需要根据实际情况进行特别处理,可以不采用 FreeRTOS 的开关中断函数,而是直接使用__set_PRIMASK 实现全局中断的开关。