STM32裸机和RTOS中的线程安全问题及STM32cubeMX中的线程安全策略

STM32线程安全问题

术语“线程” 和“多线程” 适用于裸机和基于RTOS的应用程序,线程安全问题并不只存在于基于RTOS的应用程序中;裸机应用程序中也存在这个问题,在裸机应用程序中,中断服务程序允许调用C库函数。线程安全问题可能出现在多线程应用程序中, 如其中两个线程试图操作共享内存的一个实例, 如malloc()或free()。当然一般也不会在中断中进行malloc(动态内存分配)。但是在开发阶段可能会存在有使用C库函数中的printf函数,那么就会有线程安全问题,C库函数可以进行不那么明显的调用(隐式调用)导致类似的问题。例如,printf()可以调用malloc()。

RTOS应用程序:多个任务或ISR。

在这里插入图片描述
在RTOS应用中,并发调用C库函数的情况可能有三个来源:

  1. 低优先级中断:
    ①用于对时间不敏感的操作
    ②用于RTOS的时基
    ③用于RTOS的任务切换
  2. 高优先级中断:可能在应用程序中有对执行时间敏感的操作
  3. 任务切换

裸机应用程序:主循环被ISR中断, 那么中断服务程序也被视为第二个执行线程。

在这里插入图片描述

裸机编程的时候通常会勾选Use MicroLIB,通过把printf函数重定向到串口输出的方式打印一些log,当主循环中使用printf时发生中断,在中断中也使用printf可能导致异常。这种异常在RTOS工程中更容易复现。比如使用STM32CubeMX生成FreeRTOS工程,同时创建两个优先级相同的任务,任务每隔1s使用printf函数打印log,使能抢占式调度(configUSE_PREEMPTION)和时间片轮转(configUSE_TIME_SLICING)。

 /* definition and creation of led_task */osThreadDef(led_task, led_func, osPriorityNormal, 0, 256);led_taskHandle = osThreadCreate(osThread(led_task), NULL);/* definition and creation of lcd_task */osThreadDef(lcd_task, lcd_func, osPriorityNormal, 0, 256);lcd_taskHandle = osThreadCreate(osThread(lcd_task), NULL);void led_func(void const * argument)
{const TickType_t xDelay = 1000 / portTICK_PERIOD_MS;for(;;){LED_R_TOGGLE();printf("led_func running\r\n");vTaskDelay(xDelay);}
}void lcd_func(void const * argument)
{const TickType_t xDelay = 1000 / portTICK_PERIOD_MS;for(;;){LED_R_TOGGLE();printf("lcd_func running\r\n");vTaskDelay(xDelay);}
}

理想情况下的输出应该是两个灯每隔1s翻转状态,两个任务每隔一秒输出一次,实际情况是两个灯每隔1s翻转状态,但是串口输出异常,串口输出如下:
在这里插入图片描述
可见,printf不是线程安全函数,在printf前后使用taskENTER_CRITICAL()和taskEXIT_CRITICAL()函数进行临界段代码保护,输出结果就正常。
在这里插入图片描述

FreeRTOS任务级临界段代码保护

#define taskENTER_CRITICAL()	             	portENTER_CRITICAL()
#define portENTER_CRITICAL()					vPortEnterCritical()#define taskEXIT_CRITICAL()		            	portEXIT_CRITICAL()
#define portEXIT_CRITICAL()						vPortExitCritical()

taskENTER_CRITICAL()和taskEXIT_CRITICAL()函数为任务级进入临界段代码,在进入函数 vPortEnterCritical()以后会首先关闭中断,然后给变量 uxCriticalNesting加一, uxCriticalNesting 是个全局变量,用来记录临界段嵌套次数的。函数 vPortExitCritical()是退出临界段调用的,函数每次将 uxCriticalNesting 减一,只有当 uxCriticalNesting 为 0 的时候才会调用函数 portENABLE_INTERRUPTS()使能中断。这样保证了在有多个临界段代码的时候不会因为某一个临界段代码的退出而打乱其他临界段的保护,只有所有的临界段代码都退出以后才会使能中断。最终调用的函数如下:

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();}
}

其中,portDISABLE_INTERRUPTS和portENABLE_INTERRUPTS定义如下:

#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS()					vPortSetBASEPRI( 0 )static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;__asm{/* Set BASEPRI to the max syscall priority to effect a criticalsection. */mrs ulReturn, baseprimsr basepri, ulNewBASEPRIdsbisb}return ulReturn;
}static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{__asm{/* Barrier instructions are not used as this function is only used tolower the BASEPRI value. */msr basepri, ulBASEPRI}
}

假设stm32中断优先级分组设置为4,那就是4位抢占优先级,没有子优先级,即0-15,因此宏configLIBRARY_LOWEST_INTERRUPT_PRIORITY定义了最低优先级为15,configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY定义为5,也就是优先级高于5(数值小于5)的中断不归FreeRTOS管理。

vPortRaiseBASEPRI函数的作用是屏蔽所有低于configMAX_SYSCALL_INTERRUPT_PRIORITY(数值大于configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY)宏的中断。

#define configPRIO_BITS         4
#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY   15
#define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
#define configMAX_SYSCALL_INTERRUPT_PRIORITY 	( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

FreeRTOS中断级临界段代码保护

#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR()		ulPortRaiseBASEPRI()#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x)	vPortSetBASEPRI(x)

taskENTER_CRITICAL_FROM_ISR()和taskEXIT_CRITICAL_FROM_ISR( x )函数为中断级进入临界段代码,可以看到是没有嵌套处理,直接操作BASEPRI寄存器实现。

STM32cubeMX中的线程安全策略

使用STM32cubeMX生成工程时,可选的线程安全策略有五种:
在这里插入图片描述

如果选择Default,裸机应用会自动选择策略2,RTOS应用会自动选择策略4。

对于单核项目,策略1会额外生成stm32_lock.h、 armlib_lock_glue.c和stm32 _lock_user.h三个文件;策略2/3/4/5会额外生成stm32_lock.h、 armlib_lock_glue.c两个文件;对于多核项目,每个核引用相同的文件(stm32_lock.h和armlib_lock_glue.c), 每个核使用一个单独的文件(stm32_lock_user.h)。

  • 策略1为处理线程安全的自定义解决方案,这时候需要自己实现临界区锁。
  • 策略2适用于裸机系统,策略2允许从中断使用锁。这个实现通过禁用所有中断来确保线程安全, 例如, 调用malloc()期间。如果ISR调用malloc(), 它会获取一个锁, 完成执行, 然后释放锁。就从ISR重新进入而言, 应用程序是安全的, 并且共享数据不会损坏。 然而, 这种策略的副作用是中断被延迟。
    在这里插入图片描述
  • 策略3适用于裸机系统,策略3拒绝使用中断锁,实现假设单线程执行, 并拒绝任何从ISR上下文中获取锁的尝试,不会延迟中断。 但是使用这种策略, 不可能从ISR上下文中获得锁。 因此, 当C库函数试图从ISR上下文中获取锁时, 该尝试将被拒绝, CPU将卡在Error_Handler()中。下图左边:main()调用malloc()。malloc()执行被中断,但ISR不调用malloc();因此,不会尝试从ISR上下文中获取锁。 没有数据损坏,因为malloc()可以在从ISR上下文返回后完成临界区。下图右边:main()调用malloc()。 malloc()执行被中断,ISR调用malloc();会尝试从ISR上下文中获取锁,那么应用程序挂起在Error_Handler()中。 这样做的目的是向开发人员发出一个明确的信号, 即C库不能以这种方式使用。 通过让开发人员意识到在ISR上下文中使用C库函数的危险。
    在这里插入图片描述
  • 策略4适用于RTOS应用,策略4使用FreeRTOS锁实现。 这个实现通过在调用malloc()期间进入RTOS ISR临界区来确保线程安全。这意味着线程安全是通过禁用低优先级中断和任务切换来实现的。通过宏taskENTER_CRITICAL_FROM_ISR在调用malloc()期间进入具有RTOS ISR能力的临界区来确保线程安全。 taskENTER_CRITICAL_FROM_ISR宏的实现略有不同, 具体取决于项目所针对的Cortex‑M核心。 当获得锁时,malloc()进入临界区,因此低优先级中断和任务切换被禁用。这个实现,默认情况下,支持两级嵌套锁定。嵌套级别的数量可通过STM32_LOCK_MAX_NESTED_LEVELS宏配置。每增加一个嵌套等级,额外增加4字节的RAM开销。
typedef struct
{uint32_t basepri[STM32_LOCK_MAX_NESTED_LEVELS];uint8_t nesting_level;
} LockingData_t;

在这里插入图片描述
然而, 策略4高优先级中断也是不安全的(数值小于configMAX_SYSCALL_INTERRUPT_PRIORITY宏的中断)。高优先级中断仍然可能发生, 代价是不安全的并发C库函数调用。
在这里插入图片描述

  • 策略5适用于RTOS应用,策略5拒绝使用中断锁,该实现通过暂停所有任务来确保线程安全, 例如, 在调用malloc()期间。通过在malloc()调用期间暂停所有任务来确保线程安全,但使中断处于启用状态。当由malloc()获得锁时,在锁被释放之前,任务切换不会发生。然而,中断是允许切换执行的。如果试图从ISR上下文中获取锁,则应用程序将被Error_Handler()捕获并挂起。因此,使开发人员意识到C库函数的危险使用,那么应用程序在ISR上下文中也被认为是安全的。
    在这里插入图片描述

FreeRTOS中动态内存分配

FreeRTOS中动态内存分配使用pvPortMalloc()和vPortFree()函数,这两个函数在操作内存前后分别使用vTaskSuspendAll()和xTaskResumeAll()函数来暂停和恢复所有任务,和上述策略5相同。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/51981.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python实现水果忍者(开源)

一、整体介绍&#xff1a; 1.1 前言&#xff1a; 游戏代码基于Python制作经典游戏案例-水果忍者做出一些改动&#xff0c;优化并增加了一些功能。作为自己Python阶段学习的结束作品&#xff0c;文章最后有源码链接。 1.2 Python主要知识&#xff1a; &#xff08;1&#xf…

Python爬虫——爬取某网站的视频

爬取视频 本次爬取&#xff0c;还是运用的是requests方法 首先进入此网站中&#xff0c;选取你想要爬取的视频&#xff0c;进入视频播放页面&#xff0c;按F12&#xff0c;将网络中的名称栏向上拉找到第一个并点击&#xff0c;可以在标头中&#xff0c;找到后续我们想要的一些…

qt-15综合实例(电子时钟)-多态重写鼠标单击和移动事件

综合实例-电子时钟 知识点digiclock.hdigiclock.cppmain.cpp运行图 知识点 setWindowOpacity(0.5);//设置窗体透明度 QTimer* Timer new QTimer(this);//新建一个定时器 connect(Timer,SIGNAL(timeout()),this,SLOT(ShowTime())); Timer->start(1000);//启动定时器 digic…

稚晖君发布5款全能人形机器人,开源创新,全能应用

8月18日&#xff0c;智元机器人举行“智元远征 商用启航” 2024年度新品发布会&#xff0c;智元联合创始人彭志辉主持并发布了“远征”与“灵犀”两大系列共五款商用人形机器人新品——远征A2、远征A2-W、远征A2-Max、灵犀X1及灵犀X1-W&#xff0c;并展示了在机器人动力、感知、…

猫头虎分享:练习提示词Prompt有什么好方法?提高Prompt水平和质量

猫头虎是谁&#xff1f; 大家好&#xff0c;我是 猫头虎&#xff0c;别名猫头虎博主&#xff0c;擅长的技术领域包括云原生、前端、后端、运维和AI。我的博客主要分享技术教程、bug解决思路、开发工具教程、前沿科技资讯、产品评测图文、产品使用体验图文、产品优点推广文稿、…

深扒大模型微调密码 - 从入门到技术小白都能看懂的神操作

朋友们&#xff0c;你们有没有听说过"大模型"和"微调"这两个概念呢&#xff1f;别着急,我们今天就来好好聊一聊! 想象一下,你有一个非常勤奋的小助理,它会尽最大努力帮你完成各种任务。不过有时候,它的知识储备和能力肯定有限,所以你得适时给它一些专门的…

树莓派5 笔记25:第一次启动与配置树莓派5_8G

今日继续学习树莓派5 8G&#xff1a;&#xff08;Raspberry Pi&#xff0c;简称RPi或RasPi&#xff09; 本人所用树莓派4B 装载的系统与版本如下: 版本可用命令 (lsb_release -a) 查询: Opencv 与 python 版本如下&#xff1a; 今日购得了树莓派5_8G版本&#xff0c;性能是同运…

springboot航班进出港管理系统--论文源码调试讲解

第2章 开发环境与技术 本章节对开发航班进出港管理系统管理系统需要搭建的开发环境&#xff0c;还有航班进出港管理系统管理系统开发中使用的编程技术等进行阐述。 2.1 Java语言 Java语言是当今为止依然在编程语言行业具有生命力的常青树之一。Java语言最原始的诞生&#xff…

SQL每日一练-0815

今日SQL题难度&#xff1a;&#x1f31f;☆☆☆☆☆☆☆☆☆ 1、题目要求 计算每个产品类别在每个月的总销售额和总销量。找出每个月销售额最高的产品类别&#xff0c;显示类别名称、销售月份、总销售额和总销量。 2、表和虚拟数据 现有两个表&#xff1a;Products 和…

牛客网习题——通过C++实现

一、目标 实现下面4道练习题增强C代码能力。 1.求123...n_牛客题霸_牛客网 (nowcoder.com) 2.计算日期到天数转换_牛客题霸_牛客网 (nowcoder.com) 3.日期差值_牛客题霸_牛客网 (nowcoder.com) 4.打印日期_牛客题霸_牛客网 (nowcoder.com) 二、对目标的实现 1.求123...n_…

[机器学习]--KNN算法(K邻近算法)

KNN (K-Nearest Neihbor,KNN)K近邻是机器学习算法中理论最简单,最好理解的算法,是一个 非常适合入门的算法,拥有如下特性: 思想极度简单,应用数学知识少(近乎为零),对于很多不擅长数学的小伙伴十分友好虽然算法简单,但效果也不错 KNN算法原理 上图是每一个点都是一个肿瘤病例…

Sakana.ai 迈向完全自动化的开放式科学发现

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

从零开始搭建k8s集群详细步骤

声明&#xff1a;本文仅作为个人记录学习k8s过程的笔记。 节点规划&#xff1a; 两台节点为阿里云ECS云服务器&#xff0c;操作系统为centos7.9&#xff0c;master为2v4GB,node为2v2GB,硬盘空间均为40GB。&#xff08;节点基础配置不低于2V2GB&#xff09; 主机名节点ip角色部…

Docker最佳实践进阶(一):Dockerfile介绍使用

大家好&#xff0c;上一个系列我们使用docker安装了一系列的基础服务&#xff0c;但在实际开发过程中这样一个个的安装以及繁杂命令不仅仅浪费时间&#xff0c;更是容易遗忘&#xff0c;下面我们进行Docker的进阶教程&#xff0c;帮助我们更快速的部署和演示项目。 一、什么是…

力扣面试经典算法150题:找出字符串中第一个匹配项的下标

找出字符串中第一个匹配项的下标 今天的题目是力扣面试经典150题中的数组的简单题: 找出字符串中第一个匹配项的下标 题目链接&#xff1a;https://leetcode.cn/problems/find-the-index-of-the-first-occurrence-in-a-string/description/?envTypestudy-plan-v2&envIdto…

docker compose部署rabbitmq集群,并使用haproxy负载均衡

一、创建rabbitmq的data目录 mkdir data mkdir data/rabbit1 mkdir data/rabbit2 mkdir data/rabbit3 二、创建.erlang.cookie文件&#xff08;集群cookie用&#xff09; echo "secretcookie" > .erlang.cookie 三、创建haproxy.cfg配置文件 global log stdout fo…

深度学习基础—正则化

正则化&#xff1a;解决模型过拟合的手段&#xff0c;本质就是减小模型参数取值&#xff0c;从而使模型更简单。常用范数如下&#xff1a; 使用最多的是L2范数正则项&#xff0c;因此加入正则项的损失函数变为&#xff1a; 使用梯度下降法的权重调整公式&#xff1a; 推导后得到…

项目实战:Qt+Opencv相机标定工具v1.3.0(支持打开摄像头、视频文件和网络地址,支持标定过程查看、删除和动态评价误差率,支持追加标定等等)

若该文为原创文章&#xff0c;转载请注明出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/141334834 长沙红胖子Qt&#xff08;长沙创微智科&#xff09;博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、Op…

二十二、状态模式

文章目录 1 基本介绍2 案例2.1 Season 接口2.2 Spring 类2.3 Summer 类2.4 Autumn 类2.5 Winter 类2.6 Person 类2.7 Client 类2.8 Client 类的运行结果2.9 总结 3 各角色之间的关系3.1 角色3.1.1 State ( 状态 )3.1.2 ConcreteState ( 具体的状态 )3.1.3 Context ( 上下文 )3.…

Airtest 的使用

Airtest 介绍 Airtest Project 是网易游戏推出的一款自动化测试框架&#xff0c;其项目由以下几个部分构成 Airtest : 一个跨平台的&#xff0c;基于图像识别的 UI 自动化测试框架&#xff0c;适用于游戏和 App &#xff0c; 支持 Windows, Android 和 iOS 平台&#xff0c…