FreeRTOS_信号量之优先级翻转

目录

1. 优先级翻转

2. 优先级翻转实验

2.1 实验目的

2.2 实验设计

2.3 实验程序

2.4 现象


1. 优先级翻转

        在使用二值信号量的时候会遇到一个很常见的问题——优先级翻转。优先级翻转在可剥夺内核中是非常常见的,在实时系统中不允许出现这种现象,这样会破坏任务的预期顺序,可能会导致严重的后果!!!

(1)、任务 H 和任务 M 处于挂起状态,等待某一事件的发生,任务 L 正在运行。

(2)、某一时刻任务 L 想要访问共享资源,在此之前它必须先获得对应该资源的信号量。

(3)、任务 L 获得信号量并开始使用该共享资源。

(4)、由于任务 H 的优先级高,它等待的事件发生后便剥夺了任务 L 的 CPU 使用权。

(5)、任务 H 开始运行。

(6)、任务 H 运行过程中也要使用任务 L 正在使用着的资源,由于该资源的信号量还被任务 L 占用着,任务 H 只能进入挂起状态,等待任务 L 释放该信号量。

(7)、任务 L 继续运行。

(8)、由于任务 M 的优先级高于任务 L,当任务 M 等待的事件发生后,任务 M 剥夺了任务 L 的 CPU 使用权。

(9)、任务 M 处理应该处理的事。

(10)、任务 M 执行完毕后,将 CPU 的使用权归还给任务 L。

(11)、任务 L 继续运行。

(12)、最终任务 L 完成所有的工作并释放了信号量,到此为止,由于实时内核知道有一个高优先级的任务在等待这个信号量,故内核做任务切换。

(13)、任务 H 得到该信号量并接着运行。

        综上所述,任务 H 的优先级实际上降到了任务 L 的优先级水平。因为任务 H 要一直等待直到任务 L 释放其占有的那个共享资源。由于任务 M 剥夺了任务 L 的 CPU 使用权,使得任务 H 的情况更加恶化,这样就相当于任务 M 的优先级高于了任务 H,导致优先级翻转。

2. 优先级翻转实验

2.1 实验目的

        在使用二值信号量的时候会存在优先级翻转的问题,本实验通过模拟的方式实现优先级翻转,观察优先级翻转对抢占式内核的影响。

2.2 实验设计

        本实验设计了四个任务:start_task、high_task、middle_task、low_task,这四个任务的任务功能如下:

        start_task:用来创建其他的三个任务。

        high_task:高优先级任务,会获取二值信号量,获取成功以后会进行相应的处理,处理完成以后就会释放二值信号量。

        middle_task:中等优先级任务,一个简单的应用任务。

        low_task:低优先级任务,和高优先级任务一样,会获取二值信号量,获取成功以后会进行相应的处理,不过不同之处在于低优先级的任务占用二值信号量的时间要久一点(软件模拟占用)。

        实验中创建了一个二值信号量 BinarySemaphore,高优先级和低优先级这两个任务会使用这个二值信号量。

2.3 实验程序

#include "stm32f4xx.h"  
#include "FreeRTOS.h" //这里注意必须先引用FreeRTOS的头文件,然后再引用task.h
#include "task.h"     //存在一个先后的关系
#include "LED.h"
#include "LCD.h"
#include "Key.h"
#include "usart.h"
#include "delay.h"
#include "string.h"
#include "beep.h"
#include "malloc.h"
#include "timer.h"
#include "queue.h"
#include "semphr.h"//任务优先级
#define START_TASK_PRIO     1       //用于创建其他三个任务
//任务堆栈大小
#define START_STK_SIZE      256
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);//任务优先级
#define LOW_TASK_PRIO     2       //低优先级任务,会获取二值信号量,获取成功以后进行相应的处理,占用二值信号量的时间要久一点
//任务堆栈大小 
#define LOW_STK_SIZE      256
//任务句柄
TaskHandle_t LowTask_Handler;
//任务函数
void low_task(void *pvParameters);//任务优先级
#define MIDDLE_TASK_PRIO     3       //中等优先级任务,一个简单的应用任务
//任务堆栈大小
#define MIDDLE_STK_SIZE      256
//任务句柄
TaskHandle_t MiddleTask_Handler;
//任务函数
void middle_task(void *pvParameters);//任务优先级
#define HIGH_TASK_PRIO     4       //高优先级任务,会获取二值信号量,获取成功以后会进行相应的处理,处理完成以后会释放二值信号量
//任务堆栈大小
#define HIGH_STK_SIZE      256
//任务句柄
TaskHandle_t HighTask_Handler;
//任务函数
void high_task(void *pvParameters);//二值信号量句柄
SemaphoreHandle_t BinarySemaphore;   //二值信号量//LCD刷屏时使用的颜色
int lcd_discolor[14]={	WHITE, BLACK, BLUE,  BRED,      GRED,  GBLUE, RED,   MAGENTA,       	 GREEN, CYAN,  YELLOW,BROWN, 			BRRED, GRAY };int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);  delay_init(168);uart_init(115200);LED_Init();KEY_Init();BEEP_Init();LCD_Init();my_mem_init(SRAMIN);        //初始化内部内存池POINT_COLOR=RED;LCD_ShowString(30,10,200,16,16,"ATK STM32F407");LCD_ShowString(30,30,200,16,16,"FreeRTOS Example");LCD_ShowString(30,50,200,16,16,"Priority Overturn");LCD_ShowString(30,70,200,16,16,"ATM@ALIENTEK");LCD_ShowString(30,90,200,16,16,"2023/10/08");//创建开始任务xTaskCreate((TaskFunction_t)start_task,         //任务函数(const char*   )"start_task",       //任务名称(uint16_t      )START_STK_SIZE,     //任务堆栈大小(void*         )NULL,               //传递给任务函数的参数(UBaseType_t   )START_TASK_PRIO,    //任务优先级(TaskHandle_t* )&StartTask_Handler);//任务句柄vTaskStartScheduler();          //开启任务调度
}//开始任务任务函数
void start_task(void *pvParameters)
{taskENTER_CRITICAL();       //进入临界区//创建二值信号量//默认创建的二值信号量是无效的,这里需要先调用函数 xSemaphoreGive释放一次二值信号量。//否则任务high_task和low_task都获取不到信号量BinarySemaphore=xSemaphoreCreateBinary();  //创建二值信号量函数,返回创建成功的二值信号量句柄//二值信号量创建成功以后先释放一下,因为默认创建的二值信号量是空的,释放二值信号量,先让这个长度为1的队列有效if(BinarySemaphore!=NULL)  //如果创建成功的二值信号量不为空,那么将二值信号量释放一下其他任务才能使用{xSemaphoreGive(BinarySemaphore);  //释放二值信号量函数}//创建高优先级任务xTaskCreate((TaskFunction_t)high_task,         //任务函数(const char*   )"high_task",       //任务名称(uint16_t      )HIGH_STK_SIZE,     //任务堆栈大小(void*         )NULL,               //传递给任务函数的参数(UBaseType_t   )HIGH_TASK_PRIO,    //任务优先级(TaskHandle_t* )&HighTask_Handler);//任务句柄//创建中等优先级任务xTaskCreate((TaskFunction_t)middle_task,         //任务函数(const char*   )"middle_task",       //任务名称(uint16_t      )MIDDLE_STK_SIZE,     //任务堆栈大小(void*         )NULL,               //传递给任务函数的参数(UBaseType_t   )MIDDLE_TASK_PRIO,    //任务优先级(TaskHandle_t* )&MiddleTask_Handler);//任务句柄//创建低优先级任务xTaskCreate((TaskFunction_t)low_task,         //任务函数(const char*   )"low_task",       //任务名称(uint16_t      )LOW_STK_SIZE,     //任务堆栈大小(void*         )NULL,               //传递给任务函数的参数(UBaseType_t   )LOW_TASK_PRIO,    //任务优先级(TaskHandle_t* )&LowTask_Handler);//任务句柄vTaskDelete(StartTask_Handler);taskEXIT_CRITICAL();            //退出临界区
}//高优先级任务任务函数
void high_task(void *pvParameters)
{u8 num;POINT_COLOR=BLACK;LCD_DrawRectangle(5,110,115,314);   //画一个矩形LCD_DrawLine(5,130,115,130);        //画线POINT_COLOR=BLUE;LCD_ShowString(6,111,110,16,16,"High Task");while(1){vTaskDelay(500);   //延时500ms,也就是500个时钟节拍num++;printf("high task Pend Semaphore\r\n");xSemaphoreTake(BinarySemaphore,portMAX_DELAY);    //获取二值信号量//获取二值信号量的阻塞时间设置为无限等待,既然这个任务可以获取二值信号量,那么总有一个时刻可以获取到二值信号量//否则程序就会卡在这里printf("high task Running!\r\n");   //获取到二值信号量,高优先级任务开始运行LCD_Fill(6,131,114,313,lcd_discolor[num%14]);   //填充区域LED1=!LED1;xSemaphoreGive(BinarySemaphore);           //释放二值信号量,当高优先级任务获取二值信号量完成相应的处理之后,就会释放掉信号量vTaskDelay(500);                //延时500ms,也就是500个时钟节拍}
}//中等优先级任务的任务函数
void middle_task(void *pvParameters)
{u8 num;POINT_COLOR=BLACK;LCD_DrawRectangle(125,110,234,314);     //画一个矩形LCD_DrawLine(125,130,234,130);      //画线POINT_COLOR=BLUE;LCD_ShowString(126,111,110,16,16,"Middle Task");while(1){num++;printf("middle task Running!\r\n");LCD_Fill(126,131,233,313,lcd_discolor[13-num%14]);  //倒过来填充区域LED0=!LED0;vTaskDelay(1000);       //延时1s,也就是1000个时钟节拍}
}//低优先级任务的任务函数
//低优先级任务占用二值信号量的时间更长
void low_task(void *pvParameters)
{static u32 times;while(1){xSemaphoreTake(BinarySemaphore,portMAX_DELAY);  //获取二值信号量printf("low task Running!\r\n");            for(times=0;times<20000000;times++)         //模拟低优先级占用二值信号量{taskYIELD();            //发起任务调度//这也就保证了低优先级任务占用二值信号量的时间更长//因为我一旦发起了任务调度,低优先级抢占的这个二值信号量是不能被高优先级的任务所抢占的}xSemaphoreGive(BinarySemaphore);           //释放二值信号量vTaskDelay(1000);       //延时1s,也就是1000个时钟节拍}
}

2.4 现象

LCD ID:5510 
middle task Running! 
low task Running!                         (1) 
high task Pend Sem                         (2) middle task Running!                         (3) 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! 
middle task Running! high task Running!                                 (4) 
middle task Running! 
high task Pend Sem 

(1)low_task 任务获得到二值信号量 BinarySemaphore 开始运行!

(2)high_task 获取信号量 BinarySemaphore,但是此时信号量 BinarySemaphore 被任务 low_task 占用着,因此 high_task 就要一直等待,直到 low_task 任务释放信号量 BinarySemaphore。

(3)由于 high_task 没有获取到信号量 BinarySemaphore,只能一直等待,所以一直在运行 middle task Running! ,给人的感觉是 middle_task 的任务优先级高于 high_task。但是事实上 high_task 任务的任务优先级是高于 middle_task 的,这就是任务优先级翻转!!!

(4)high_task 任务因为获取到了信号量 BinarySemaphore 而运行!!!

        当一个低优先级任务和一个高优先级任务同时使用一个信号量时,并且系统中还有其他中等优先级任务时。如果低优先级任务获取到了信号量,那么高优先级任务就会处于等待状态,但是,中等优先级任务可以打断低优先级任务而先于高优先级任务运行!,这就是任务级翻转现象!!!

        整个运行过程是这样的!!!

        首先高优先级任务会延时500个时钟节拍,所以中优先级任务首先开始运行,这也就是串口助手首先打印 middle task Running!的原因,中等优先级任务while循环一次之后,时间片轮转,低优先级任务会获取二值信号量开始运行(这里解释一下为什么不是中等优先级任务运行完之后为什么不是高优先级任务先运行,这是因为低优先级任务会首先获取二值信号量,所以首先开始运行,高优先级任务还在延迟),串口助手首先打印 low task Running!开启任务调度,此时高优先级任务延时时间到了之后,串口助手首先打印 high task Pend Sem!,但是由于二值信号量还在低优先级任务中,所以无法开始运行,此时中优先级任务抢占CPU的使用权,开始一直打印 middle task Running!等到低优先级任务的循环结束,跳出for循环,释放二值信号量,此时高优先级任务获取二值信号量,高优先级任务开始运行!

        既然任务级翻转是个不好的现象,那么如何避免这个现象————此时引出互斥信号量的概念!

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

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

相关文章

西门子PLC ModbusTcp通信访问网关后从站(SCL语言轮询状态机FB)

西门子PLC的ModbusTcp通信在专栏已有系列文章介绍,所不同的是每个项目的通信需求都略有不同,今天我们以访问网关后的三个从站数据来举例,给出轮询的推荐写法,这里我们利用SCL语音进行编程,方便大家导入导出到自己的项目里使用,相关文章链接大家可以参考下面地址: SMART…

1深度学习李宏毅

目录 机器学习三件事&#xff1a;分类&#xff0c;预测和结构化生成 2、一般会有经常提到什么是标签label&#xff0c;label就是预测值&#xff0c;在机器学习领域的残差就是e和loss​编辑3、一些计算loss的方法&#xff1a;​编辑​编辑 4、可以设置不同的b和w从而控制loss的…

ubuntu PX4 vscode stlink debug设置

硬件 stlink holybro debug板 pixhawk4 安装openocd 官方文档&#xff0c;但是第一步安装建议从源码安装&#xff0c;bug少很多 github链接 编译安装&#xff0c;参考 ./bootstrap (when building from the git repository)./configure [options]makesudo make install安装后…

lua-resty-request库写入爬虫ip实现数据抓取

根据提供的引用内容&#xff0c;正确的库名称应该是lua-resty-http&#xff0c;而不是lua-resty-request。使用lua-resty-http库可以方便地进行爬虫&#xff0c;需要先安装OpenResty和lua-resty-http库&#xff0c;并将其引入到Lua脚本中。然后&#xff0c;可以使用lua-resty-h…

Windows公网远程连接MongoDB数据库【无公网IP】

文章目录 前言1. 安装数据库2. 内网穿透2.1 安装cpolar内网穿透2.2 创建隧道映射2.3 测试随机公网地址远程连接 3. 配置固定TCP端口地址3.1 保留一个固定的公网TCP端口地址3.2 配置固定公网TCP端口地址3.3 测试固定地址公网远程访问 前言 MongoDB是一个基于分布式文件存储的数…

2023年【道路运输企业主要负责人】考试技巧及道路运输企业主要负责人复审模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年【道路运输企业主要负责人】考试技巧及道路运输企业主要负责人复审模拟考试&#xff0c;包含道路运输企业主要负责人考试技巧答案和解析及道路运输企业主要负责人复审模拟考试练习。安全生产模拟考试一点通结合…

网络安全漏洞管理与修复: 深入研究漏洞管理流程,包括漏洞扫描、评估、修复和验证。

网络安全是当今数字时代的重要议题&#xff0c;随着技术的不断发展&#xff0c;网络攻击和漏洞问题也日益复杂。在这篇文章中&#xff0c;我们将深入研究网络安全漏洞管理与修复的流程&#xff0c;包括漏洞扫描、评估、修复和验证。通过理解和实施这一流程&#xff0c;组织可以…

Django实战项目-学习任务系统-自定义URL拦截器

接着上期代码框架&#xff0c;6个主要功能基本实现&#xff0c;剩下的就是细节点的完善优化了。 首先增加URL拦截器&#xff0c;你不会希望没有登录用户就可以进入用户主页各种功能的&#xff0c;所以增加URL拦截器可以解决这个问题。 Django框架本身也有URL拦截器&#xff0…

前端项目 index.html 中发请求 fetch

想要在前端项目 index.html文件中向后端发起请求&#xff0c;但是引入axios报错&#xff08;我这边会报错&#xff09;&#xff0c;可以使用fetch。 //window.location.origin----获取域名&#xff0c;包括协议、主机号、端口号fetch(window.location.origin "/api/pla…

server2012 通过防火墙开启局域网内限定IP进行远程桌面连接

我这里需要被远程桌面的电脑系统版本为windows server2012 1、打开允许远程连接设置 2、开启防火墙 3、设置允许“远程桌面应用”通过防火墙 勾选”远程桌面“ 3、入站规则设置 高级设置→入站规则→远程桌面-用户模式(TCP-In) 进入远程桌面属性的作用域——>远程IP地址—…

So-vits-SVC4.1

So-vits-SVC官方项目地址&#xff1a;https://github.com/svc-develop-team/so-vits-svc 中文版&#xff1a;https://github.com/SUC-DriverOld/so-vits-svc-Chinese-Detaild-Documents 教程&#xff1a;https://www.bilibili.com/video/BV1Hr4y197Cy/ 音频处理 1.转mp4/mp…

一台服务器安装两个mysql、重置数据库用于测试使用

文章目录 一、切数据库数据存储文件夹已经存在数据库数据文件夹新建数据库数据文件夹 二、安装第二个mysql安装新数据库初始化数据库数据启动数据库关闭数据库 三、mysqld_multi单机多实例部署参考文档 一、切数据库数据存储文件夹 这个方法可以让你不用安装新的数据库&#x…