在RTOS中验证互斥量有效解决优先级反转现象

news2024/11/24 14:34:00

        我们在stm32f103c8t6单片机上验证RTOS互斥量有效解决优先级反转现象,利用stm32cube进行RTOS的配置。在选择TIM2当做RTOS的时钟,裸机的时钟源默认是 SysTick,但是开启 FreeRTOS 后,FreeRTOS会占用 SysTick (用来生成1ms 定时,用于任务调度),所以需要为其他总线提供另外的时钟源。

验证的功能比较简单,选择V1 版本的内核完全够用。

一、验证的思路及需要使用的函数及互斥量的说明

1.验证思路

做两组实验进行对比,一组用二值型信号量,另一组用互斥型信号量,看一下有优先级反转和无优先级反转的情况区别

2.需用函数

二值型信号量

建立二值信号量SemaphoreHandle_t xSemaphoreCreateBinary( void )

返回值: 成功,返回对应二值信号量的句柄; 失败,返回 NULL

获取二值信号量BaseType_t xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait );

xSemaphore:要获取的信号量句柄

xTicksToWait:超时时间,0 表示不超时,portMAX_DELAY表示卡死等待;

返回值: 成功,返回 pdPASS ; 失败,返回 errQUEUE_FULL 。

释放二值信号量BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore )

xSemaphore:要释放的信号量句柄

返回值: 成功,返回 pdPASS ; 失败,返回 errQUEUE_FULL 。

互斥型信号量

建立互斥型信号量SemaphoreHandle_t xSemaphoreCreateMutex( void )

返回值: 成功,返回对应互斥量的句柄; 失败,返回 NULL

互斥型信号量信号的获取与释放所用的函数同二值型信号量

一样,只不过函数的参数不一样,即句柄不一样

3.互斥量的说明

互斥型信号量可以有效解决优先级反转现象。可发生优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任 务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务 会将低优先级任务的优先级提升到与自己相同的优先级。 优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响。

听不懂没关系,我给你讲故事。

现在有High,Middle,Low三个人,我们分别用H,M,L来代替。其中H的优先级最高,M其次,L最低。H的工作就是在整洁的床上睡觉,M的工作就是闲的没事在H醒来之后打酱油,L的工作就是负责给H整理床铺。

下面我就用这个故事情节给大家说一下有无翻转优先级的区别

有翻转优先级的时候

按照优先级的排序,故事开始应该是这样发展的。H开始睡觉,睡了一会,H醒了走人。M过来打一会酱油,走了。接着L开始为H铺床,这个回合结束。按道理说H应该要睡觉了,但是L却没有把床铺好,因此H无法睡觉,只能等着不工作。这个时候L应该早点把床整理好,让H睡觉。但是M偏偏就是这么浪,嫌弃L整理的时间太长,自己的优先级又比L高,H又不工作,没人管它,M就欺负L,让L无法工作,自己又去打酱油。他打酱油了,L就无法工作了。那么L就只能等着M浪完在工作,别看此时H等级高,也只能等着M浪完,L把床叠好后在休息。

发现了吗?在使用二值信号量并没有使用互斥信号量的时候,本来应该正常工作的L却因M的优先级比自己高,而不能工作。在我们的执行流程中,就相当于低优先级的工作时间还没结束,便被翻转为中优先级,这就是典型的优先级翻转。

二、有优先级防转时STM32cube的配置

SYS

RCC

RTOS版本选择

在Times and Semapores里

选择Binary semaphores(二值信号),生成二值信号配置如下图所示

在Task and Queues里

配置三个任务

分别是TaskHigh,TaskMiddle,TaskLow,

对应的优先级分别是osPriorityAboveNormal, osPriorityNormal, osPriorityBelowNormal,

对应的入口函数分别是StartTaskHigh,StartTaskMiddle,StartTaskLow

三、有优先级防转时的代码

usart.c

#include "stdio.h"

int fputc(int ch, FILE *f)

{

unsigned char temp[1]={ch};

HAL_UART_Transmit(&huart1,temp,1,0xffff);

return ch;

}

同时打开“魔术棒”,勾选Use MicroLIB,点击OK。这样就可以进行串口打印了。

freertos.c

#include "FreeRTOS.h"

#include "task.h"

#include "main.h"

#include "cmsis_os.h"

/* Private includes ----------------------------------------------------------*/

/* USER CODE BEGIN Includes */

#include "stdio.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/

/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/

/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/

/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN Variables */

/* USER CODE END Variables */

osThreadId TaskHighHandle;

osThreadId TaskMiddleHandle;

osThreadId TaskLowHandle;

osSemaphoreId myBinarySemHandle;

/* Private function prototypes -----------------------------------------------*/

/* USER CODE BEGIN FunctionPrototypes */

/* USER CODE END FunctionPrototypes */

void StartTaskHigh(void const * argument);

void StartTaskMiddle(void const * argument);

void StartTaskLow(void const * argument);

void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */

/* GetIdleTaskMemory prototype (linked to static allocation support) */

void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize );

/* USER CODE BEGIN GET_IDLE_TASK_MEMORY */

static StaticTask_t xIdleTaskTCBBuffer;

static StackType_t xIdleStack[configMINIMAL_STACK_SIZE];

void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize )

{

  *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer;

  *ppxIdleTaskStackBuffer = &xIdleStack[0];

  *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE;

  /* place for user code */

}

/* USER CODE END GET_IDLE_TASK_MEMORY */

/**

  * @brief  FreeRTOS initialization

  * @param  None

  * @retval None

  */

void MX_FREERTOS_Init(void) {

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* USER CODE BEGIN RTOS_MUTEX */

  /* add mutexes, ... */

  /* USER CODE END RTOS_MUTEX */

  /* Create the semaphores(s) */

  /* definition and creation of myBinarySem */

  osSemaphoreDef(myBinarySem);

  myBinarySemHandle = osSemaphoreCreate(osSemaphore(myBinarySem), 1);

  /* USER CODE BEGIN RTOS_SEMAPHORES */

  /* add semaphores, ... */

  /* USER CODE END RTOS_SEMAPHORES */

  /* USER CODE BEGIN RTOS_TIMERS */

  /* start timers, add new ones, ... */

  /* USER CODE END RTOS_TIMERS */

  /* USER CODE BEGIN RTOS_QUEUES */

  /* add queues, ... */

  /* USER CODE END RTOS_QUEUES */

  /* Create the thread(s) */

  /* definition and creation of TaskHigh */

  osThreadDef(TaskHigh, StartTaskHigh, osPriorityAboveNormal, 0, 128);

  TaskHighHandle = osThreadCreate(osThread(TaskHigh), NULL);

  /* definition and creation of TaskMiddle */

  osThreadDef(TaskMiddle, StartTaskMiddle, osPriorityNormal, 0, 128);

  TaskMiddleHandle = osThreadCreate(osThread(TaskMiddle), NULL);

  /* definition and creation of TaskLow */

  osThreadDef(TaskLow, StartTaskLow, osPriorityBelowNormal, 0, 128);

  TaskLowHandle = osThreadCreate(osThread(TaskLow), NULL);

  /* USER CODE BEGIN RTOS_THREADS */

  /* add threads, ... */

  /* USER CODE END RTOS_THREADS */

}

/* USER CODE BEGIN Header_StartTaskHigh */

/**

  * @brief  Function implementing the TaskHigh thread.

  * @param  argument: Not used

  * @retval None

  */

/* USER CODE END Header_StartTaskHigh */

void StartTaskHigh(void const * argument)

{

  /* USER CODE BEGIN StartTaskHigh */

  /* Infinite loop */

  for(;;)

  {

     xSemaphoreTake(myBinarySemHandle, portMAX_DELAY);

   printf("TaskH:我要睡觉了。\r\n");

   HAL_Delay(1000);

//osDelay(1000);

   printf("TaskH:我醒了,我走了。\r\n");

   xSemaphoreGive(myBinarySemHandle);

              osDelay(1000);

  }

  /* USER CODE END StartTaskHigh */

}

/* USER CODE BEGIN Header_StartTaskMiddle */

/**

* @brief Function implementing the TaskMiddle thread.

* @param argument: Not used

* @retval None

*/

/* USER CODE END Header_StartTaskMiddle */

void StartTaskMiddle(void const * argument)

{

  /* USER CODE BEGIN StartTaskMiddle */

  /* Infinite loop */

  for(;;)

  {

   

   printf("TaskM:啦啦啦,我来打酱油了。\r\n");

   osDelay(1000);

  }

  /* USER CODE END StartTaskMiddle */

}

/* USER CODE BEGIN Header_StartTaskLow */

/**

* @brief Function implementing the TaskLow thread.

* @param argument: Not used

* @retval None

*/

/* USER CODE END Header_StartTaskLow */

void StartTaskLow(void const * argument)

{

  /* USER CODE BEGIN StartTaskLow */

  /* Infinite loop */

  for(;;)

  {

     xSemaphoreTake(myBinarySemHandle, portMAX_DELAY);

   printf("TaskL:我要整理床了。\r\n");

   HAL_Delay(3000);

//osDelay(1000);

   printf("TaskL:床铺整理好了。\r\n");

   xSemaphoreGive(myBinarySemHandle);

              osDelay(1000);

  }

  /* USER CODE END StartTaskLow */

}

运行的结果如下图所示,和我们的故事是吻合的

无翻转优先级的时候

按照优先级的排序,故事开始应该是这样发展的。H开始睡觉,睡了一会,H醒了走人。M过来打一会酱油,走了。接着L开始为H铺床,这个回合结束。按道理说H应该要睡觉了,但是L却没有把床铺好,因此H无法睡觉,只能等着不工作。这个时候L应该早点把床整理好,让H睡觉。但是M偏偏就是这么浪,嫌弃L整理的时间太长,自己的优先级又比L高,H又不工作,没人管它,M就想着欺负L,让L无法工作,但这一次,虽然H不工作却很严肃的对M说:“L现在在为我工作,看见L就相当看见我,它的优先级和我一样高”。这时候,就发生了优先级继承,M不敢惹L,L依旧可以工作,直到把床铺好,让H上床休息。L铺床和H上床之间是没有M横插一杠的。

发现了吗?在使用互斥信号量的时候,本来应该正常工作的L可以正常工作,不会因为M的优先级比自己高,而不能工作。在我们的执行流程中,就相当于低优先级的工作时间结束后才自动转到别的优先级,这就是没有发生优先级翻转。

四、无优先级防转时STM32cube的配置

首先把二值信号量删除,接着创建互斥量,其余的不变

五、无优先级防转时的代码

把xSemaphoreGive(myBinarySemHandle)和xSemaphoreTake(myBinarySemHandle)里面的参数换成myMutexHandle就可以了。

下图是无优先级防转时运行的情况,可以看出L在运行时并没有遭到M的打断,发生了优先级继承,和我们的故事情况一样。

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

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

相关文章

数组去重及去除指定值,每一个对象添加属性值

1、数组去重ES6写法 Set() // 数组去重 let arr [1,2,4,6,3,2,6,7,7,2,9,0,1,5] arr [...new Set(arr)] console.log(arr); 2、数组去除指定值 filter() // 数组去除指定值 let arr [1,2,4,6,3,2,6,7,7,2,9,0,1,5] const num 7 arr arr.filter(item>item!num) cons…

Go集成elasticsearch8极简demo,光速入门

Go集成elasticsearch8极简demo,光速入门 配置go环境创件go mod工程代码实现配置go环境 编辑器添加goproxy GO111MODULE=on;GOPROXY=https://mirrors.wps.cn/go/,https://goproxy.cn,direct;GOSUMDB=off创件go mod工程 mkdir demo cd demo go mod init demo代码实现 在demo…

测试用例的修改更新

测试用例的修改更新是指测试过程中由于用户需求的改变,或者测试过程中发现有新的需求产生,使得测试用例需要进行修改。修改更新测试用例不仅是一种测试技术,更是一种质量保证的方法。但修改和更新测试用例的技术要点在于: 1、执行…

设计模式之创建型设计模式(一):单例模式 原型模式

单例模式 Singleton 1、什么是单例模式 在软件设计中,单例模式是一种创建型设计模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。 这意味着无论何时需要该类的实例,都可以获得相同的实例,而不会创建…

API接口能力不足?Bug处理慢?Lazada开放平台API商品接入

7月30日正式发布的Lazada开放平台2.0(Lazada Open Platform 2.0),从商品API、订单API、IM(即时通信) API、营销工具等几大方向,带来全新升级的API体系,共新增47个接口、优化19个接口&#xff0c…

JVM-9-Class类文件的结构

Java技术能够一直保持着非常良好的向后兼容性,Class文件结构的稳定功不可没。 Class文件是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中。 Class文件格式采用一种类似于C语言结构体的伪结构来存储数据&#xff0c…

详细教程 - 从零开发 鸿蒙harmonyOS应用 第八节——鸿蒙操作系统中的文件读写操作封装

一、引言 鸿蒙操作系统是华为自主研发的全场景操作系统。在这篇博客中,我们将探讨如何在鸿蒙操作系统中实现文件读写操作的封装。 二、文件读写操作 在鸿蒙操作系统中,文件读写操作是一个常见的需求。下面是一个简单的文件读写操作的封装示例&#xff1…

数据分析场景下,企业大模型选型的思路与建议

来源/作者:爱分析 随着大模型带来能力突破,让AI与数据分析相互结合,使分析结果更好支撑业务,促进企业内部数据价值释放,成为了当下企业用户尤为关注的话题。本次分享主要围绕数据分析场景下大模型底座的选型思路&#…

上传xml文件进行跨站脚本攻

文件路径或者URL&#xff1a;xxxxx <?xml version"1.0" encoding"iso-8859-1"?> <xsl:stylesheet version"1.0" xmlns:xsl"http://www.w3.org/1999/XSL/Transform"> <xsl:template match"/"> <html…

部署threestudio | stable zero123

选择我在autodl的stable-zero123这个镜像&#xff0c;或者直接选这个基础环境 开机后切换到conda的base环境 这里注意一点就是目前stable-zero123这个镜像还没解决的问题&#xff0c;就是没法使用xformers&#xff0c;所以如果重新配置&#xff0c;在这里就要先安装指定版本的…

liunx下用C++使用freetype库在opencv上打中文字

1、/visualizer.cpp:11:10: fatal error: ft2build.h: 没有那个文件或目录 11 | #include <ft2build.h> freetype安装问题&#xff0c;要把文件拉到根目录&#xff0c;不然找不到文件 2、编译失败找不到定义 /usr/bin/ld: CMakeFiles/interactive_face_detection_de…

树莓派Ubuntu系统安装OpenCV教程

硬件&#xff1a;树莓派4B 2G 内存卡32G 软件&#xff1a;系统是ubuntu-mate-20.04.1-desktop-arm64raspi.img 步骤如下&#xff1a;一、更新系统软件包列表和安装基本工具 sudo apt-get update sudo apt-get upgrade sudo apt-get install build-essential cmake git二、安装…

【Linux】Linux运维基础

Linux简介&#xff1a; Linux是一个开源的操作系统内核&#xff0c;最初由Linus Torvalds创建。它通常与GNU工具一起使用&#xff0c;以创建一个完整的操作系统。Linux操作系统有许多基于内核的发行版&#xff0c;如Ubuntu、CentOS、Debian等&#xff0c;每个发行版都有其独特的…

解决docker alpine /bin/sh: ./main: not found

解决docker alpine /bin/sh: ./main: not found golang中编译之后的二进制文件部署在alpine镜像中出现了not found问题解决这种情况是因为动态链接库位置错误导致的&#xff0c;alpine镜像使用的是musl libc而不是gun libc。因而动态链接库的位置不一致。在基础镜像内执行&…

13. 从零用Rust编写正反向代理, HTTP中的压缩gzip,deflate,brotli算法

wmproxy wmproxy是由Rust编写&#xff0c;已实现http/https代理&#xff0c;socks5代理&#xff0c; 反向代理&#xff0c;静态文件服务器&#xff0c;内网穿透&#xff0c;配置热更新等&#xff0c; 后续将实现websocket代理等&#xff0c;同时会将实现过程分享出来&#xff…

IDEA版SSM入门到实战(Maven+MyBatis+Spring+SpringMVC) -Spring的AOP前奏

第一章 AOP前奏 1.1 代理模式 代理模式&#xff1a;我们需要做一件事情&#xff0c;又不期望自己亲力亲为&#xff0c;此时&#xff0c;可以找一个代理【中介】 我们【目标对象】与中介【代理对象】不能相互转换&#xff0c;因为是“兄弟”关系 1.2 为什么需要代理【程序中…

MQTT中的遗嘱和证明(Last Will and Testament, LWT)

在MQTT中&#xff0c;遗嘱和证明&#xff08;Last Will and Testament, LWT&#xff09;是一个很有用的功能&#xff0c;允许客户端指定一条消息&#xff0c;当非预期的断开连接发生时&#xff0c;由代理&#xff08;broker&#xff09;代替它们自动发布出去。提供了一个可靠通…

Redis原理之网络模型笔记

目录 1. 阻塞IO 2. 非堵塞IO 3. IO多路复用 ​3.1 select 3.2 poll 3.3 epoll 4. 信号驱动IO 5. 异步IO 6. Redis是单线程还是多线程 Redis采用单线程模型&#xff0c;这意味着一个Redis服务器在任何时刻都只会处理一个请求。Redis的网络模型涉及到阻塞I/O&#xff08;Blo…

基于双目RGB图像和图像深度信息的三维室内场景建模matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 双目视觉原理 4.2 深度信息获取 4.3 表面重建 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 .....................................…

快速处理EDI数据映射:知行EDI Profiler 操作指南

一个完整的EDI项目通常由建立传输通道&#xff0c;处理数据映射以及集成内部业务系统三部分组成。对用户而言&#xff0c;基于知行之桥EDI系统进行自主实施最大的挑战便是处理数据映射。EDI报文读不懂&#xff0c;映射关系太复杂……这些问题给企业造成困扰的同时也阻挡了自主实…