STM32裸机和RTOS中的线程安全问题及STM32cubeMX中的线程安全策略
STM32线程安全问题
术语“线程” 和“多线程” 适用于裸机和基于RTOS的应用程序,线程安全问题并不只存在于基于RTOS的应用程序中;裸机应用程序中也存在这个问题,在裸机应用程序中,中断服务程序允许调用C库函数。线程安全问题可能出现在多线程应用程序中, 如其中两个线程试图操作共享内存的一个实例, 如malloc()或free()。当然一般也不会在中断中进行malloc(动态内存分配)。但是在开发阶段可能会存在有使用C库函数中的printf函数,那么就会有线程安全问题,C库函数可以进行不那么明显的调用(隐式调用)导致类似的问题。例如,printf()可以调用malloc()。
RTOS应用程序:多个任务或ISR。
在RTOS应用中,并发调用C库函数的情况可能有三个来源:
- 低优先级中断:
①用于对时间不敏感的操作
②用于RTOS的时基
③用于RTOS的任务切换 - 高优先级中断:可能在应用程序中有对执行时间敏感的操作
- 任务切换
裸机应用程序:主循环被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相同。
相关文章:

STM32裸机和RTOS中的线程安全问题及STM32cubeMX中的线程安全策略
STM32线程安全问题 术语“线程” 和“多线程” 适用于裸机和基于RTOS的应用程序,线程安全问题并不只存在于基于RTOS的应用程序中;裸机应用程序中也存在这个问题,在裸机应用程序中,中断服务程序允许调用C库函数。线程安全问题可能…...

图的遍历
一、深度优先遍历(DFS) 二、广度优先遍历(BFS)...

CUDA-MODE课程笔记 第8课: CUDA性能检查清单
我的课程笔记,欢迎关注:https://github.com/BBuf/how-to-optim-algorithm-in-cuda/tree/master/cuda-mode CUDA-MODE课程笔记 第8课: CUDA性能检查清单 课程笔记 这节课实际上算是CUDA-MODE 课程笔记 第一课: 如何在 PyTorch 中 profile CUDA kernels 这…...

【备战蓝桥杯青少组】第二天 奇特的砖墙
真题 第十四届省赛 编程题 第5题 工人砌了一面奇特的砖墙,该墙由N列砖组成(1≤N≤1e6),且每列砖的数量为Ki(1≤Ki≤1e4,相邻砖块之间无缝隙),每块砖的长宽高都为1。小蓝为了美化这面…...

图像处理 -- 仿射变换之Affine Transformation
仿射变换(Affine Transformation) 仿射变换是图像处理中的一种基本操作,通过线性变换和平移实现图像的几何变换。仿射变换包括旋转、缩放、平移、翻转、错切(shear)等操作。 1. 仿射变换的作用 旋转:将图…...

Nuxt3【项目配置】nuxt.config.ts
按环境添加配置 export default defineNuxtConfig({// 生产环境的配置$production: {routeRules: {/**: { isr: true }}},// 开发环境的配置$development: {//} })运行时的配置 runtimeConfig export default defineNuxtConfig({runtimeConfig: {// 只在服务器端可用的私有键ap…...

中智讯“2024高校人工智能边缘应用项目实战师资培训班”圆满举办
7月24日——7月28日,中智讯“2024高校人工智能&边缘应用项目实战师资培训班”在昆明理工大学成功举办。本次培训由昆明理工大学人工智能产业学院主办,中智讯(武汉)科技有限公司承办。来自昆明理工大学、西南林业大学、云南民族…...

IIS发布打包后文件
1.打开IIS软件 2 添加网站, 自定义网站名称-选择要放置的资源路径-选择IP地址 3.打开放置的资源目录放置打包后文件 4.选择浏览 搜索不到IIS可进行一下操作 控制面板-程序和功能-启用或关闭windows功能-勾选IIS...

四个自定义 SHAP 图
超越 Python 包,创建 SHAP 值的定制可视化 SHAP 值是了解模型如何进行预测的绝佳工具。SHAP 包提供了许多可视化效果,使这个过程更加简单。话虽如此,我们不必完全依赖这个包。我们可以通过创建自己的 SHAP 图来进一步了解模型的工作原理。在本…...

为什么使用HTTPS?
HTTPS现在是所有Web活动的首选协议,因为它是用户保护敏感信息的最安全方式。 HTTPS不仅对请求用户信息的网站至关重要。除了用户直接发送的信息外,攻击者还可以从不安全的连接中跟踪行为和身份数据。 HTTP为网站所有者带来的好处除了数据安全之外&…...

软件设计-系统架构师(五十五)
1在网络规划中,政府内外网之间应该部署网络安全防护设备。在下图部署的设备A是(),对设备A作用描述错误的是()。 问题1 A IDS B 防火墙 C 网闸 D UTM 问题2 A 双主机系统,即使外网被黑客攻击…...

三分钟学会线缆电流估算
今天带你用三分钟的时间,学会电源线承受电流估算,不浪费时间,直接开始吧。 工作温度30℃,长期连续90%负载下的载流量 1.5平方毫米――14A 2.5平方毫米――26A 4平方毫米――32A 6平方毫米――47A 16平方毫米――92A 25平方毫米――120A 3…...

Snipaste 的一款替代工具 PixPin,支持 gif 截图、长截图和 OCR 文字识别,功能不是一点点强!
Snipaste 的一款替代工具 PixPin,支持 gif 截图、长截图和 OCR 文字识别,功能不是一点点强! PixPin 的名字来源于“Pixel Pin”,简单来说是一个截图、贴图的工具,但是 PixPin 以截图和贴图两大功能为核心做了大量的优…...

Oracle基础教程
体系结构 数据库 一个操作系统仅有一个数据库 实例 拥有一系列后台进程和存储结构,一个数据库可拥有一个或多个实例,一般只有1个实例 数据文件(.dbf/.ora) 数据文件是数据库的物理存储单元,一个表空间由一个或多…...

电脑如何录屏?三款电脑录屏工具分享
电脑如何录屏?作为一个经常需要录制电脑屏幕大职场人,不是为了制作教程、记录会议,就是偶尔想自己做个游戏解说视频。市面上的录屏软件琳琅满目,经过一番尝试和比较,我选出了三款我个人认为表现不错的软件,…...

idea2024建立maven web项目servlet 6.0
(1) 下载好tomcat 10.1.28 打开tomcat.apache.org官网下载 (2)配置好maven (3)idea 2024打开,建立项目 选择maven java项目 (4)在项目src/main/下 建立webapp/WEB-INF目录,在…...

游戏开放式新手引导框架设计
强制性引导:只能点某个按钮 优:程序简单 缺: 玩家体验差 开放式引导:不强制点 优:玩家体验好 缺: 程序复杂 需求分析: 1.开放式引导,引导是到达某个条件后进行一系列行为(…...

【Hot100】LeetCode—189. 轮转数组
目录 1- 思路自定义 reverse 翻转函数 2- 实现⭐189. 轮转数组——题解思路 3- ACM 实现 原题链接:189. 轮转数组 1- 思路 自定义 reverse 翻转函数 2- 实现 ⭐189. 轮转数组——题解思路 class Solution {public void rotate(int[] nums, int k) {k % nums.lengt…...

javaweb学习之HTML(一)
推荐学习使用网站 w3school 在线教程 认识HTML HTML(HyperText Markup Language)是超文本标记语言,它是一个用于创建网页和网页应用程序的标准标记语言。HTML文档由一系列的元素(elements)组成,这些元素通…...

项目实战:Qt+Opencv相机标定工具v1.3.0(支持打开摄像头、视频文件和网络地址,支持标定过程查看、删除和动态评价误差率,支持追加标定等等)
若该文为原创文章,转载请注明出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/141334834 长沙红胖子Qt(长沙创微智科)博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、Op…...

【数据结构】汇总八、排序算法
排序Sort 【注意】本章是 排序 的知识点汇总,全文1万多字,含有大量代码和图片,建议点赞收藏(doge.png)!! 【注意】在这一章,记录就是数据的意思。 排序可视化网站: D…...

Java-分割list并执行多线程任务的工具类
要创建一个用于分割列表并执行多线程任务的工具类,你可以使用 Java 的 ExecutorService 和 ThreadPoolExecutor 来实现。下面是一个详细的示例,展示了如何创建这样一个工具类。 步骤 1: 创建线程池 首先,创建一个线程池来执行任务。 步骤 2: 分割列表 接着,定义一个方…...

Springboot-从服务器获取一个输入流,转成视频文件存到oss
要在Spring Boot应用中从服务器获取一个输入流,然后将该流转换为视频文件并存储到阿里云 OSS中,你可以遵循以下步骤: 设置阿里云OSS客户端:首先,你需要配置阿里云OSS客户端,以便能够上传文件到OSS。 获取输入流:使用HTTP客户端(如RestTemplate或WebClient)从服务器…...

[Meachines] [Easy] Bastion SMB未授权访问+VHD虚拟硬盘挂载+注册表获取NTLM哈希+mRemoteNG远程管理工具权限提升
信息收集 IP AddressOpening Ports10.10.10.134TCP:22, 135, 139, 445, 5985, 47001, 49664, 49665, 49666, 49667, 49668, 49669, 49670 $ nmap -p- 10.10.10.134 --min-rate 1000 -sC -sV PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH fo…...

STM32标准库学习笔记-9.DMA 直接存储器存取
参考教程:【STM32入门教程-2023版 细致讲解 中文字幕】 DMA(Direct Memory Access) DMA(Direct Memory Access)直接存储器存取DMA可以提供外设和存储器或者存储器和存储器之间的高速数据传输,无须CPU干预…...

ubuntu VCS+verdi安装遇到的一些问题
主体流程:https://blog.51cto.com/u_15346322/4995147 我的是Ubuntu22.4 安装目录问题 执行 ./installer -gui 只能安装到home 或者 sudo ./setup.sh -install_as_root 能安装到/usr/ 目录 运行 vcs 出现 bin/sh: Illegal option -h sudo rm -f /bin/sh sudo…...

使用Poi-tl对word模板生成动态报告
一、pom依赖问题: <dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.12.2</version> </dependency> 使用 poi-tl 的 1.12.2版本,如果使用了poi依赖&#x…...

day45-dynamic programming-part12-8.16
tasks for today: 1. 115.不同的子序列 2. 583.两个字符串选的删除操作 3. 72.编辑距离 4. 总结编辑两个序列关系的问题 ------------------------------------------------------------------- 1. 115.不同的子序列 In this practice, it is necessary to compare with t…...

C# String的方法
目录 #region 知识点九 字符串切割 #region 知识点一 字符串指定位置获取 #region 知识点二 字符串拼接 #region 知识点三 正向查找字符位置 #region 知识点四 反向查找指定字符串位置 #region 知识点五 移除指定位置后的字符 #region 知识点六 替换指定字符串 #region 知识点七…...

Oracle RAC vs Clusterware vs ASM
Oracle RAC vs Clusterware vs ASM Oracle RACCache FusionRAC后台进程自动负载管理DBA管理工具Oracle ClusterwareCRS组件HAS组件管理工具Oracle ASMASM实例ASM磁盘组镜像和故障组ASM磁盘ASM文件Oracle RAC RAC即Real Application Clusters,是一种Oracle高可用部署架构。Orac…...