临界段
代码的临界段也称为临界区,一旦这部分代码开始执行,则不允许任何中断打断。为确保临界段代码
的执行不被中断,在进入临界段之前须关中断,而临界段代码执行完毕后,要立即开中断。
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 时出错。