freeRtos学习笔记

freeRtos临界区管理

freeRtos临界区

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

FreeRTOS 的源码中有多处临界段的地方, 临界段虽然保护了关键代码的执行不被打断, 但也会影响系统的实时性。比如此时某个任务正在调用系统 API 函数,而且此时中断正好关闭了,也就是进入到了临界区中,这个时候如果有一个紧急的中断事件被触发,这个中断就不能得到及时执行,必须等到中断开启才可以得到执行, 如果关中断时间超过了紧急中断能够容忍的限度, 危害是可想而知的。
所以,操作系统的中断在某些时候会有适当的中断延迟,因此调用中断屏蔽函数进入临界段的时候,也需快进快出。 当然 FreeRTOS 也能允许一些高优先级的中断不被屏蔽掉,能够及时做出响应,不过这些中断就不受系统管理,也不允许调用 FreeRTOS 中与中断相关的任何 API 函数接口。
FreeRTOS 源码中就有多处临界段的处理, 跟 FreeRTOS 一样, uCOS-II 和 uCOS-III 源码中都是有临界段的, 而 RTX操作系统 的源码中不存在临界段。 另外, 除了 FreeRTOS 操作系统源码所带的临界段以外,用户写应用的时候也有临界段的问题,比如以下两种:

  • 读取或者修改变量(特别是用于任务间通信的全局变量)的代码,一般来说这是最常见的临界代码。
  • 硬件资源或者调用公共函数的代码,特别是不可重入的函数(函数使用了全局变量或者局部静态变量),如果多个任务都访问这个函数,结果是可想而知的。总之, 对于临界段要做到执行时间越短越好, 否则会影响系统的实时性。

临界区处理方法

进入临界段前操作寄存器 basepri 关闭了所有小于等于宏定义 configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
所定义的中断优先级, 这样临界段代码就不会被中断干扰到, 而且实现任务切换功能的 PendSV 中断和滴答定时器中断是最低优先级中断, 所以此任务在执行临界段代码期间是不会被其它高优先级任务打断的。
退出临界段时重新操作 basepri 寄存器,即打开被关闭的中断.大于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY
所定义的中断优先级的中断不受freertos管理,在临界区仍然可以响应,但是中断服务函数中不可以调用freertos的API。

taskENTER_CRITICAL()/* 进入临界区 */
/* 临界段代码 */
taskEXIT_CRITICAL();/* 退出临界区 */

如果使用单纯的开关中断,则在以下临界区嵌套情况下,FunctionC()函数本意是要临界保护的,但是在FunctionB()中已退出临界区,和设计本意不同。

void FunctionB()
{taskENTER_CRITICAL()/* 进入临界区 *//* 临界段代码 */taskEXIT_CRITICAL();/* 退出临界区 */
}void FunctionA()
{taskENTER_CRITICAL(); /* 进入临界区 */FunctionB();          /* 临界段代码--调用函数 B */FunctionC();          /* 临界段代码--调用函数 C */taskEXIT_CRITICAL();  /* 退出临界区 */C
}

因此为了避免这种情况,在taskENTER_CRITICAL()和taskEXIT_CRITICAL()函数中存在一个全局变量,记录嵌套次数,因此需要注意进入和退出临界区函数必须要成对出现。

#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
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();}
}

一般在使用freertos时,NVIC建议配置为只有抢占优先级,中断是可以嵌套的,但是进入临界区后就关闭了freeRtos可以管理的中断,中断服务函数中还需要记录临界区嵌套次数吗?
是需要的,如果在中断服务函数中调用其他函数,其他函数中也有可能会有临界区保护,因此一样需要记录临界区嵌套次数。freertos中中断服务函数中的API一般都是FROM_ISR结尾的,临界区管理也是这样的。

UBaseType_t uxSavedInterruptStatus;
uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR(); /* 进入临界区 */
/* 临界区代码 */
taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus ); /* 退出临界区 */

注意和taskENTER_CRITICAL()和taskEXIT_CRITICAL()函数中存在一个全局变量,记录嵌套次数不同,taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus )为了提高执行速度,这里采用了另一种方式,使用一个局部变量记录进入临界区时basepri寄存器的值,在退出临界区时,恢复寄存器basepri,从而也达到了嵌套管理的目的,因此进入临界区和退出临界区函数也必须要成对出现。

例子1 调用不可重入函数需进入临界区

/*!* @brief    LED闪烁任务** @param    pvParame ** @return   无** @note     ** @see      */
void vTaskLED(void* pvParame)
{while(1){HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);/* 进入临界区 */taskENTER_CRITICAL();printf("LED TASK IS RUNING\r\n");/* 退出临界区 */taskEXIT_CRITICAL();vTaskDelay(50);}
}/*!* @brief    printf打印任务** @param    pvParame ** @return   无** @note     ** @see      */
void vTaskPrintf(void* pvParame)
{while(1){/* 进入临界区 */taskENTER_CRITICAL();printf("Printf task is runing\r\n");printf("Printf task is runing\r\n");printf("Printf task is runing\r\n");printf("Printf task is runing\r\n");printf("Printf task is runing\r\n");/* 退出临界区 */taskEXIT_CRITICAL();vTaskDelay(100);}
}/* 任务句柄 */
static TaskHandle_t ledTaskHandle;
static TaskHandle_t printfTaskHandle;/*** @brief  The application entry point.* @retval int*/
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_DMA_Init();MX_USART1_UART_Init();MX_CRC_Init();MX_RTC_Init();USART1->SR;/* 初始化 Event Recorder 组件 方便MDK调试 */EvrFreeRTOSSetup(1);BaseType_t err;/* 创建LED闪烁任务 */err = xTaskCreate(vTaskLED, "LED TASK", 128, 0, 10, &ledTaskHandle);if(pdTRUE != err){}/* 创建printf打印任务 */err = xTaskCreate(vTaskPrintf, "PRINTF TASK", 128, 0, 9, &printfTaskHandle);if(err != pdTRUE){}/* 启动任务调度器 */vTaskStartScheduler();while (1){}}

上述例子中创建了两个任务,LED闪烁和printf打印任务,两个任务都调用了printf函数,printf函数为不可重入函数,如果不对printf进行临界段保护,则打印结果就会如下所示错乱。

Printf task iLED TASK IS RUNING
s runing
LED TASK IS RUNING
LED TASK IS RUNING
Printf task is runing
Printf task is runing
Printf task is runing
Printf task is runing
Printf task is runing

正常打印结果

LED TASK IS RUNING
Printf task is runing
Printf task is runing
Printf task is runing
Printf task is runing
Printf task is runing
LED TASK IS RUNING

不可重入函数

不可重入函数即 使用了全局变量或局部静态变量并对该变量进行了写操作的函数都为不可重入函数,假设一个函数使用了局部静态变量,任务A调用了这个函数修改了该局部静态变量,接着任务B运行,任务B也调用了这个函数修改了该局部静态变量,当任务A重新运行时,该局部静态变量的值已经发送了变化,如果该函数需要根据该局部静态变量的值进行一些处理,则可能会造成任务A异常。

保护全局变量进入临界区

原子操作

在上一个世纪,人们认为原子是组成物质的最小颗粒 ,在计算机领域引用了这个术语,原子操作即不可分割的,在执行完毕之前不会被任何其它任务或事件中断

在cortex-M中,32位的变量读和写操作都是原子操作,也就是只要一条指令就可以了。如果对全局变量都是原子操作,还进什么临界区啊!
读和写都是原子操作,但是在程序中经常会出现先读后写的情况,两个原子操作加一起就不是原子操作了。

上述为stm32f103 在mdk -o3 中一个变量自减操作,会编译成4行汇编,如果第二行汇编执行完后,进入了中断,在中断中修改了这个变量,然后退出中断后,接着执行后两行汇编,就会导致中断中的修改无效。在裸机情况下,为了避免该情况,也应该关中断进临界区(有些小伙伴就说,自己从来都没进临界区,代码不照样跑的好好的。虽然上述情况出现的概率很小,但是不是不会发生的,要尽量避免)。裸机的情况下都需要进入临界区,使用RTOS时就更需要注意了,RTOS任务间也会进行抢占,上述情况的发生概率一般会比裸机高许多。

为什么对全局变量进行临界区保护?除了上面的原因,最为常见的是逻辑上,我们认为是原子的,但是实际用代码表达时,却必须用多条语句变成了非原子操作。例如在任务A中逐字符接收数据并放入缓冲区,缓冲区满后对之前数据进行覆盖;在任务B中检查接收缓冲区满后将数据全部发送出去。任务B将全部数据发送出去在逻辑上就是一个原子操作,但是在代码中却需要很多条语句,如果不进入临界区,发送一半时,缓冲区又通过任务A接收到了很多数据,覆盖了老数据,就会造成任务B发送了一半老数据又发送了一半新数据。

《安富莱 STM32-V6 开发板 FreeRTOS 教程》
本文参考 freertos官方文档 https://freertos.org/a00110.html

freeRtos学习笔(3)临界区管理相关推荐

  1. freeRtos学习笔(4)消息队列

    freeRtos学习笔记 freeRtos消息队列 为什么要用消息队列 消息队列可以在任务与任务间,中断与任务间传递信息.为什么不用全局数组?全局数组也可以传递信息,但是和消息队列相比,消息队列有一下 ...

  2. freeRtos学习笔(2)任务管理

    freeRtos学习笔记 freeRtos任务管理 freeRtos任务状态 freeRtos中任务有四种状态:就绪态.运行态.杜塞态.挂起态. 图 16-1(1): 创建任务→就绪态(Ready): ...

  3. freeRtos学习笔(1)内核剪裁

    freeRtos学习笔记 freeRtos内核剪裁 #define configCPU_CLOCK_HZ 系统主频 #define configTICK_RATE_HZ 时钟节拍 #define co ...

  4. FreeRTOS学习记录 01--中断管理

    文章目录 0 前言 1 Cortex-M 中断管理 1.1 中断配置 1.2 优先级分组配置 1.3 FreeRTOS中断 PendSv和Systick中断优先级配置 2 FreeRTOS的临界段代码 ...

  5. 【ESP32+freeRTOS学习笔记-开篇前言】

    目录 前言的前言 RTOS的选择 开发与实践环境 参考资料 笔记的形式 专题文章的链接(持续更新中......) 前言的前言 单片机的开发,也有两年多了,之前一直是做一些简单应用,因此以裸机开发的方式 ...

  6. FreeRTOS学习记录 05--任务调度器开启和切换

    文章目录 0 前言 1 任务调度器的开启 1.1 如何启动第一个任务的 2 任务的切换 2.1 PendSV 异常 2.2 两个事件引起PendSV 异常 2.3 PendSV 的中断服务函数 在这里 ...

  7. freeRTOS学习 — 消息邮箱

    1.freeRTOS中的消息邮箱 freeRTOS实现的消息邮箱是基于任务通知方式而实现的. 采用这种方式有什么优势呢? 从官方给出的测试报告中有说明到,唤醒由于信号量和事件标志组而处于阻塞态的任务, ...

  8. FreeRTOS学习笔记【二】——FreeRTOS 移植

    上一章中我们初步的了解了一下 FreeRTOS,本章就正式踏上 FreeRTOS 的学习之路, 首先 肯定是把 FreeRTOS 移植到我们所使用的平台上, 这里以 ALIENTEK 的 STM32F ...

  9. 1、野火freertos学习笔记

    野火freertos学习笔记 1.任务 1.1 栈 1.2 任务的切换 taskYIELD(); 1.3 临界段 2.空闲任务 3.任务优先级 4.任务延时的表现 5.时间片 5.1抢占式.协做式 6 ...

最新文章

  1. ssh免密码登录的原理
  2. BZOJ2525 [Poi2011]Dynamite 【二分 + 贪心】
  3. Spring Cloud Alibaba - 19 Nacos Config配置中心加载不同微服务的通用配置的两种方式
  4. 洛谷P2670扫雷游戏题解
  5. c程序设计语言 hello,Hello, World!
  6. Multi-Architecture镜像制作指南已到,请查收!
  7. 全球最伟大社交软件!微信入选“现代百大设计最佳产品”:排名超Facebook
  8. python四大器_Python编程四大神兽:迭代器、生成器、闭包和装饰器
  9. 中考计算机易错知识点,中考语文常见的易错考点23个
  10. iis服务器跳转网页怎么设置,使用IIS管理器实现域名跳转
  11. 2009年第一天上班,祝大家工作顺利!
  12. python爬取天眼查数据(未破解图片验证及ajax版)
  13. 【WordPress报错】cURL error 52: Empty reply from server(http_request_failed)
  14. 2022年API安全研究报告
  15. 微博爬虫/数据分析/可视化
  16. android studio 视屏播放器 MediaController
  17. 在国企加班996..面试拿到offer却又是外包公司,我该怎么办?
  18. 我们可以从Alexa语音助手的错误中学到什么:用户对话界面的设计性挑战
  19. html表格列拖拽,table表格列顺序拖拽和列宽度拖拽
  20. iOS 开发 二维码扫描详解

热门文章

  1. Android Studio项目结构
  2. 《windows核心编程》 17章 内存映射文件
  3. 《那些年啊,那些事——一个程序员的奋斗史》——38
  4. f(f(x)) = -x
  5. 充电类型一二次检测过程及充电类型
  6. WINCE6.0在应用程序中调用控制面板的应用
  7. jmeter在linux上运行
  8. 全球首个无人驾驶政策颁布,各大巨头并不完全买账
  9. PHP导出MySQL数据字典
  10. mongo忘记密码并删除用户