FreeRTOS_信号量之优先级翻转

news2024/11/28 19:36:30

目录

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.coloradmin.cn/o/1160102.html

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

相关文章

代码随想录第五十七天|● 392.判断子序列 ● 115.不同的子序列

392.判断子序列 题目&#xff1a; 给定字符串 s 和 t &#xff0c;判断 s 是否为 t 的子序列。 字符串的一个子序列是原始字符串删除一些&#xff08;也可以不删除&#xff09;字符而不改变剩余字符相对位置形成的新字符串。&#xff08;例如&#xff0c;"ace"是&qu…

VueX环境的搭建

一、安装VueX npm i vuex // Vue3安装该版本 npm i vuex3 // Vue2安装该版本 因为我使用的是Vue2&#xff0c;所以安装的是3版本 二、配置VueX文件 在src目录下创建store文件夹&#xff0c;再创建index.js 在index.js中配置如下代码&#xff1a; // VueX配置文件/…

Android广播BroadcastReceiver

BroadcastReceiver组件 BroadcastReceiver是Android中的一个组件&#xff0c;用于接收和处理系统广播或应用内广播。它可以监听系统事件或应用内自定义的广播&#xff0c;并在接收到广播时执行相应的操作。 广播是一种用于在应用组件之间传递消息的机制。通过发送广播&#x…

一个小技巧,显著提升大模型推理能力!加州大学提出MAF多反馈框架

作者 | 谢年年 最近&#xff0c;多篇文章《GPT-4的推理能力非常有限&#xff0c;有两篇论文为证》、《DeepMind&#xff1a;无法自我纠正推理&#xff0c;除非提前得知正确答案》指出大模型在推理任务中似乎没有自我改进的能力。即在无任何外部反馈的情况下无法通过自我纠正的形…

Vue3.0 toRef toRefs :VCA模式

简介 作用&#xff1a; 创建一个ref对象&#xff0c;其value值指向另一个对象中的某个属性 语法&#xff1a; const name toRef(person, name) 应用&#xff1a; 要将响应式对象中的某个属性单独供应给外部使用时 扩展&#xff1a; toRefs与toRef功能一致&#xff0c;但可…

会声会影2023输出文件太大怎么办

会声会影2023是一款专业的视频编辑软件&#xff0c;它由于简单易学的操作被众人所喜爱。在会声会影中编辑好的视频一般以渲染的形式导出保存&#xff0c;但是有时会出现输出文件太大的情况&#xff0c;这到底是什么原因呢&#xff1f;下面由我带大家一起来了解会声会影输出文件…

每天学习都很累,该怎么办?

中考淘汰一批人&#xff0c;高考又淘汰一批人&#xff0c;能杀进大学的&#xff0c;都知道高考的累。好不容易进了大学&#xff0c;却发现仍有打卡、作业、考试。 加上每天满满的课表&#xff0c;只是看看就让人心累。 为了奖学金或升学就业&#xff0c;又得去卷绩点、卷比赛、…

【Linux】Linux+Nginx部署项目(负载均衡动静分离)

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Linux的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.Nginx负载均衡 1.什么是负载均衡 2.实…

AIGC驱动,商业翻新,拓世法宝AI智能直播一体机,绘就商业革命新篇章

迈入数字新纪元&#xff0c;AIGC技术掀起了全球范围内的内容创作革命。这种借人工智能之力突破创造力边界的技术&#xff0c;已将内容生成的门槛降至前所未有的低点。AIGC凭借其惊艳的内容生成能力&#xff0c;收获了众人的瞩目。这不是一瞬间的激情释放&#xff0c;而是新时代…

UML类图关系

1.依赖 依赖关系由箭头表示&#xff0c;含义为A类在类中用到了B类&#xff0c;如B类作为A类的属性、参数、返回值等都属于依赖关系。 2.泛化&#xff08;继承&#xff09; 泛化用三角箭头和直线表示&#xff0c;extend。 3.实现 实现用三角箭头和虚线表示&#xff0c;在…

订水商城实战教程07-搜索

目录 1 创建数据源2 首页搜索功能3 创建搜索页面4 搭建搜索结果页面总结 上一篇我们讲解了店铺信息的展示功能&#xff0c;本篇讲解一下搜索功能。通常小程序在首页都配置了搜索的功能&#xff0c;输入关键词进行检索&#xff0c;可以在结果页上进行选购。同时还记录了用户的搜…

博客系统自动化测试项目实践

文章目录 一.测试需求分析1.功能分析2.非功能分析 二.制定测试方案&#xff08;计划 策略&#xff09;三.编写测试用例四.执行自动化测试用例五.编写测试报告六.项目总结 一.测试需求分析 1.功能分析 通过功能测试需求分析 2.非功能分析 非功能分析主要从:界面,性能,安全性,…

jmeter性能测试如何实现分布式部署

jmeter什么要做分布式部署&#xff1f; jmeter是运行在JVM虚拟机上的&#xff0c;当模拟大量并发时&#xff0c;对运行机器的性能/网络负载会很大。 此时就需要使用jmeter的分布式部署功能&#xff0c;实现多台被控机器同时并发访问被测系统。 原理图&#xff1a; 准备工作&…

小型取暖器上亚马逊美国站UL1278标准如何办理?

小型取暖器上亚马逊美国站UL1278标准如何办理&#xff1f; 小型取暖器上亚马逊美国站UL1278标准如何办理&#xff1f; 冬季马上就要来临&#xff0c;随着气温一天一天的下降&#xff0c;取暖器就是这个冬季必不可少的好物了&#xff0c;试想一下冬天一家人围着取暖器&#xf…

《TCP/IP详解 卷一:协议》第5章的IPv4数据报的Checksum(校验和)字段的计算(这里才能解开你的困惑)

首先&#xff0c;我当你看过书&#xff0c;但是比较懵。 1&#xff0c;实例说明Checksum(校验和)的计算步骤 直奔主题&#xff0c;分析一下这个Checksum&#xff08;校验和&#xff09;怎么算出来的。 先用Wireshark随便抓一个UDP或TCP包分析一下。 如上面&#xff0c;我们得…

SIP 系统容器化实践

由于SIP系统相对成熟&#xff0c;目前互联网上的SIP系统方案大多数都是基于虚拟机来实现的。 本文是基于容器化实现SIP系统的方案以及遇到的问题总结。 本文会展示两个系统的SIP实现&#xff0c;分别是智能语音机器人和CTI系统&#xff0c;不会涉及太多的业务&#xff0c;只是对…

前端实验(一)单页面应用的创建

实验目的 掌握使用vite创建vue3单页面程序命令熟悉所创建程序的组织结构熟悉单页面程序运行原理能够编写简单的单页面程序 实验内容 创建一个名为vue-demo的单页面程序编写简单的单页面程序页面运行单页面程序 实验步骤 使用vite创建单页面程序 创建项目名为目录vue-demo的…

【缓存】Spring全家桶中@CacheEvict无效情况共有以下几种

Spring全家桶中CacheEvict无效情况共有以下几种 一、背景介绍二、原因分析三、解决方案 一、背景介绍 SpringBoot中使用Cacheable注解缓存数据&#xff0c;使用CacheEvict注解删除缓存。但是在项目使用过程中&#xff0c;发现使用CacheEvict注解删除缓存无效。 拓展&#xff…

GORM:在Go中轻松管理数据库

GORM综合介绍 - Go对象关系映射库 在现代软件开发中&#xff0c;高效的数据库管理对于构建强大的应用程序至关重要。GORM是Go开发人员寻求与数据库进行交互的简化方式的宝贵工具。GORM是Go对象关系映射的缩写&#xff0c;它为Go的面向对象世界与数据库的关系世界之间提供了桥梁…

【Flink】全网最详细4W字Flink全面解析与实践(上)

本文已收录至GitHub&#xff0c;推荐阅读 &#x1f449; Java随想录 微信公众号&#xff1a;Java随想录 原创不易&#xff0c;注重版权。转载请注明原作者和原文链接 文章目录 流处理 & 批处理无界流Unbounded Streams有界流Bounded Streams Flink的特点和优势Flink VS Spa…