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

news2024/11/13 15:14:58

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 critical
		section. */
		mrs ulReturn, basepri
		msr basepri, ulNewBASEPRI
		dsb
		isb
	}

	return ulReturn;
}

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
	__asm
	{
		/* Barrier instructions are not used as this function is only used to
		lower 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.coloradmin.cn/o/2054722.html

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

相关文章

『Z-Workshop』 The Graph workshop mini hackathon活动

Community Meetup In Hangzhou ZJUBCA 2024 求是 创新 概述 / OVERVIEW The Graph作为一个去中心化的查询协议&#xff0c;为区块链数据的索引和查询提供了强大的支持。我们希望通过这场黑客松&#xff0c;激发大家对区块链技术更深层次的探索和应用&#xff0c;共同推动这一…

第七十四:前端实现点击页面某个菜单跳转到对应的锚点功能

1.用js来实现 scrollIntoView方法 先定义个id或者class随意&#xff0c;因为我是循环好几个小模块所以用动态的来实现 点击的时候传对应的类名进行滑动 document.getElementById(item.variableCode).scrollIntoView({behavior:“smooth”}); 加上behavior:“smooth” 进行平…

为Linux/centos虚拟机已有硬盘扩容

为Linux已有硬盘扩容 旧盘扩容的大体流程与关键命令&#xff1a; 为虚拟机磁盘加容量&#xff1b; 为磁盘新容量分区&#xff08;fdisk&#xff09;&#xff1b; 将分区信息写入内核&#xff08;partx&#xff09;&#xff1b; 为分区创建物理卷&#xff08;pvcreate&#xff0…

电脑如何录屏?三款电脑录屏工具分享

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

Qt qss以及对话框

字体对话框的实现 #include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }void Widget::on_fontbtn_clicked() {bool ok;QFont ret…

开源的向量数据库Milvus

Milvus是一款开源的向量数据库&#xff0c;专为处理向量搜索任务而设计&#xff0c;尤其擅长处理大规模向量数据的相似度检索。 官网地址&#xff1a;https://milvus.io/ 以下是关于Milvus的详细介绍&#xff1a; 一、基本概念 向量数据库&#xff1a;Milvus是一款云原生向量…

pyinstaller打包vnpy项目

因为我写的软件主要是自己用&#xff0c;很少有打包的习惯&#xff0c;直接源代码部署&#xff0c;导致打包&#xff0c;以下记录一下给一个朋友做的&#xff0c;对vnpy的改写&#xff0c;实现实时读取信号文件&#xff0c;发现文件中信号改变就做出相应的交易动作&#xff0c;…

离线安装 MySQL 数据库系统并实现远程登录

MySQL 5.7.25 的安装包&#xff0c;存放在 /opt/software 目录下 详细步骤 一、离线安装 MySQL 数据库系统1. 解压安装包2. 安装 MySQL 组件3. 登录 MySQL&#xff08;1&#xff09;初始化 MySQL 的数据库&#xff08;2&#xff09;启动 MySQL 服务&#xff08;3&#xff09;登…

书生大模型学习笔记9 - LMDeploy 量化部署

LMDeploy 量化部署 InternLM 2.5 20b量化前部署W4A16 模型量化量化模型部署streamlit web InternLM 2.5 20b量化前部署 lmdeploy serve api_server \/root/learning/InternLM/XTuner/merged_20b \--model-format hf \--quant-policy 0 \--cache-max-entry-count 0.01\--server…

创建干净虚拟环境对YOLOV8进行打包

背景 基于Yolov8写了一个视频检测小demo&#xff0c;考虑后续要到项目上使用&#xff0c;所以研究了一下打包流程。使用的工具是Pyinstaller&#xff0c;在原有的环境下面打包发现&#xff0c;大小有6个多G。所以想再弄一个干净的环境&#xff0c;只安装需要的模块&#xff0c…

前端基础4

本节内容&#xff1a; 1.CSS的弹性布局&#xff0c;也称Flex布局 2.Vue2的生命周期 一、Flex布局 弹性布局是前端页面布局最常用的方式之一&#xff0c;通常使用四个属性。 1.创建盒子 先创建一个盒子并为其添加一些样式可以更直观的体验弹性布局&#xff0c;代码如下&#…

xcode如何编译python

1、找到Python位置&#xff1a; 终端输入&#xff1a;which python 获取python的安装位置&#xff0c;一般为/usr/bin/python。获取到这个路径方便使用它来搭建python的编译环境。 2、在Xcode中创建python程序 打开Xcode&#xff0c;新建工程&#xff08;ShiftCommandN&…

前端基础知识(一些基本标签的用法)

前端&#xff1a;html,css,javascript html:超文本结构化标记语言 使用角度 1、供显示的元素 2、供收集信息的元素 css层叠联样式单 选择器&#xff08;给谁做样式&#xff09; 盒子模型&#xff08;了解html元素内部结构&#xff09; 布局&#xff08;把元素进行摆布&a…

C语言 | Leetcode C语言题解之第352题将数据流变为多个不想交区间

题目&#xff1a; 题解&#xff1a; typedef struct SummaryRanges{int left,right;struct SummaryRanges *pre,*next; } SummaryRanges;/** Initialize your data structure here. */SummaryRanges* summaryRangesCreate() {SummaryRanges *head malloc(sizeof(SummaryRange…

nlohmann json库的使用Demo

目录 1 简介 2 构建 JSON 对象 2.1 集合 2.2 字典 2.3 组合 3 解析 JSON 对象 4 演示Demo 4.1 开发环境 4.2 功能介绍 4.3 下载地址 1 简介 nlohmann/json 是一个方便易用的 C JSON 库&#xff0c;可以实现将 JSON 数据与 C 对象相互转换的功能&#xff0c;支持常见的…

Linux驱动开发—设备模型框架 kset和 kobject 详解

文章目录 什么是设备模型&#xff1f;设备模型的主要组成部分设备模型的关键功能设备模型的实现结构设备模型的重要性 kset和 kobject介绍1. kobject2. kset3. kobject 和 kset 的关系4. 应用场景 kobject中parent概念1. parent 字段的作用2. parent 字段的使用示例3. sysfs 中…

算法的学习笔记—对称的二叉树(牛客JZ28)

&#x1f600;前言 在算法的世界中&#xff0c;二叉树是一个极其重要的数据结构。它不仅广泛应用于各种算法的设计中&#xff0c;也是面试中常见的考察点之一。今天&#xff0c;我们将深入探讨一个经典的二叉树问题——对称的二叉树&#xff0c;并且会展示如何通过Java代码来解…

stripe Element 如何使用

这里要准备好几个东西&#xff1a; 一个支付成功过后的回调 还有一个下单的接口 一旦进入这个下单界面&#xff0c;就要去调下单的接口的&#xff0c;用 post, 这个 接口你自己写&#xff0c;可以写在后端中&#xff0c;也可以放到 nextjs 的 api 中。 首先说的是这个下单…

去中心化的新时代:Web3技术的全球影响

随着技术的不断演进&#xff0c;Web3正引领互联网的去中心化新时代。相较于传统的Web1和Web2&#xff0c;Web3通过去中心化、区块链和智能合约等技术&#xff0c;正在重塑网络的运作方式。这一变革不仅提升了网络的安全性和透明度&#xff0c;也对全球经济、社会和文化产生了深…

品牌出海新策略:携手TikTok达人,合作孵化IP实现双赢

在当今数字化时代&#xff0c;TikTok达人的IP孵化作为一种创新的合作模式&#xff0c;正逐渐成为品牌出海的新兴策略。通过与有潜力的TikTok达人合作&#xff0c;共同孵化新的IP&#xff0c;品牌不仅能够突破传统营销的局限&#xff0c;还能实现与达人共同成长的双赢局面。本文…