1.临界资源
- 在 FreeRTOS 中,临界资源是指在访问共享资源时需要保持原子性的部分,即必须完整运行,不能被打断的代码段。在多任务环境下,当多个线程需要同时访问同一个共享资源时,如果不加以限制,可能会导致数据损坏或不一致的情况。
常用的临界资源:
- 1、外设:通信需要严格时序,如IIC、SPI等。
- 2、系统:系统自身需求
- 3、用户:用户需求
对于临界段要做到执行时间越短越好,否则会影响系统的实时性。
2. 临界资源保护
临界段只有在出现任务调度和系统中断时才会被打断。
所以我们可以通过屏蔽中断和暂停任务调度来实现临界资源保护。
- 若有两个任务都可以使用临界资源,那就在任一任务访问临界资源前,先禁止任务调度。
- 若有任务和多个优先级不同的中断都要访问临界资源,则在访问临界资源前,先关闭中断。
3. 屏蔽中断
屏蔽中断有两套宏:任务中屏蔽中断和ISR中屏蔽中断:
/*任务中使用*/
taskENTER_CRITICAL();/*用于进入临界区,即禁用中断并锁定共享资源*/
taskEXIT_CRITICAL();/*用于退出临界区,即释放锁定的共享资源并重新启用中断*/
L
/*ISR中使用*/
taskENTER_CRITICAL_FROM_ISR();
taskEXIT_CRITICAL_FROM_ISR();
2.1 在任务中屏蔽中断
在任务中屏蔽中断的示例代码如下:
int a;
void add_func(int val)
{
/* 在任务中,当前时刻中断是使能的
* 执行下面这句代码后,屏蔽中断
*/
taskENTER_CRITICAL();
/* 访问临界资源 */
a += val;
/* 重新使能中断 */
taskEXIT_CRITICAL();
}
- taskENTER_CRITICAL()宏用于进入临界区,它会将当前任务的优先级设置为最高,禁止所有中断,保证在临界区的代码不会被其它任务或中断打断。
- taskEXIT_CRITICAL()宏用于退出临界区,它会将任务优先级恢复至原来的值,并允许中断,让其它任务或中断可以继续执行。
- 这套
taskENTER_CRITICA()/taskEXIT_CRITICAL()
宏,是可以递归使用的,它的内部会记录嵌套的深度,只有嵌套深度变为0时,调用taskEXIT_CRITICAL()
才会重新使能中断。
2.2 在ISR中屏蔽中断
要使用含有"FROM_ISR"后缀的宏,示例代码如下:
void a_fuc( void )
{
UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
/* 访问临界资源 */
a++;
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
}
void vAnInterruptServiceRoutine( void )
{
/* 用来记录当前中断是否使能 */
UBaseType_t uxSavedInterruptStatus;
/* 在ISR中,当前时刻中断可能是使能的,也可能是禁止的
* 所以要记录当前状态, 后面要恢复为原先的状态
* 执行这句代码后,屏蔽中断
*/
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
/* 访问临界资源 */
b++;
a_fuc();
/* 恢复中断状态 */
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
/* 现在,当前ISR可以被更高优先级的中断打断了 */
}
使用taskENTER_CRITICA()/taskEXIT_CRITICAL()
来访问临界资源是很粗鲁的方法:
- 中断无法正常运行
- 任务调度无法进行
- 所以,之间的代码要尽可能快速地执行
4. 暂停调度器
上面说了,直接进行中断屏蔽是非常粗鲁的,所以我们尽量不使用它。当临界资源只是有两个任务去对它进行访问时,我们可以只暂时屏蔽任务调度,这样仅能保护临界资源还能不妨碍中断的正常执行。
挂起和恢复任务调度器的函数如下:
/* 暂停调度器 */
void vTaskSuspendAll( void );
/* 恢复调度器
* 返回值: pdTRUE表示在暂定期间有更高优先级的任务就绪了
* 可以不理会这个返回值
*/
BaseType_t xTaskResumeAll( void );
示例代码如下:
int a;
void xxx_func(int val)
{
vTaskSuspendAll();
/* 访问临界资源 */
a = val;
xTaskResumeAll();
}
这套vTaskSuspendAll()/xTaskResumeAll()
宏,是可以递归使用的,它的内部会记录嵌套的深度,只有嵌套深度变为0时,xTaskResumeAll()
才会重新使能调度器。
5、总结
上面两种进行临界区保护的方法各有利弊:
保护方法 | 优点 | 缺点 |
---|---|---|
屏蔽中断 | 简单易用、可靠性高、可嵌套性 | 影响系统(中断)响应 |
暂停任务调度 | 中断还可以正常响应 | 需配合调度器、易造成优先级反转、可能被中断影响 |
具体使用哪个还需要根据临界区代码的具体形式和所处位置来决定。