目录
计数型信号量
计数型信号量相关API函数
计数型信号量实验源码
优先级翻转简介
优先级翻转实验源码
互斥信号量
互斥信号量相关API函数
互斥信号量实验源码
计数型信号量
计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的
计数型信号量适用场合:
事件计数
当每次事件发生后,在事件处理函数中释放计数型信号量(计数值+1) ,其他任务会获取计数型信号量(计数值-1),这种场合一般在创建时将初始计数值设置为 0
资源管理
信号量表示有效的资源数目。任务必须先获取信号量(信号量计数值-1)才能获取资源控制权。当计数值减为零时表示没有的资源。当任务使用完资源后,必须释放信号量(信号量计数值+1)。信号量创建时计数值应等于最大资源数目
计数型信号量相关API函数
使用计数型信号量的过程:创建计数型信号量 → 释放信号量 → 获取信号量
计数型信号量的释放和获取与二值信号量相同!
此函数用于创建一个计数型信号量。
#definexSemaphoreCreateCounting( uxMaxCount , uxInitialCount ) \
xQueueCreateCountingSemaphore( ( uxMaxCount), ( uxlnitialCount ))
此函数用于获取信号量当前计数值大小
#defineuxSemaphoreGetCount( xSemaphore) \
uxQueueMessagesWaiting( (QueueHandle_t) (xSemaphore ))
计数型信号量实验源码
将设计三个任务: start_task、task1、task2
start_task :用来创建task1和task2任务
Task1:用于按键扫描,当检测到按键KEY0被按下时,释放计数型信号量
task2:每过一秒获取一次计数型信号量,当成功获取后打印信号量计数值
/**
******************************************************************************
* @file : user_mian.h
* @brief : V1.00
******************************************************************************
* @attention
*
******************************************************************************
*/
/* Include 包含---------------------------------------------------------------*/
#include "stm32f10x.h"
#include <stdbool.h>
#include "user_gpio.h"
#include "user_delay.h"
#include "user_rcc_config.h"
#include "user_uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "user_key.h"
/* Typedef 类型----------------------------------------------------------------*/
/* Define 定义----------------------------------------------------------------*/
/* Macro 宏------------------------------------------------------------------*/
/*二值信号量句柄*/
QueueHandle_t count_semphore_handle;
/* Variables 变量--------------------------------------------------------------*/
/* Constants 常量--------------------------------------------------------------*/
/* Function 函数--------------------------------------------------------------*/
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define TASK1_PRIO 3
//任务堆栈大小
#define TASK1_STK_SIZE 100
//任务句柄
TaskHandle_t Task1_Handler;
//任务函数
void task1(void *pvParameters);
//任务优先级
#define TASK2_PRIO 3
//任务堆栈大小
#define TASK2_STK_SIZE 100
//任务句柄
TaskHandle_t Task2_Handler;
//任务函数
void task2(void *pvParameters);
int main(void)
{
/*配置系统中断分组为4位抢占*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/*延时函数初始化*/
delay_init();
/*RCC配置*/
Rcc_config();
/*GPIO初始化*/
Gpio_Init();
/*USART1初始化*/
Uart1_Init(9600);
/*创建计数型信号量最大值100,初始化值为0*/
count_semphore_handle = xSemaphoreCreateCounting(100,0);
if(count_semphore_handle != NULL)
{
printf("计数型信号量创建成功初始值为0\r\n\r\n");
}
/*创建开始任务*/
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(); //开启任务调度
}
/*!
\brief 开始任务函数
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建任务1
xTaskCreate((TaskFunction_t )task1,
(const char* )"task1",
(uint16_t )TASK1_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK1_PRIO,
(TaskHandle_t* )&Task1_Handler);
//创建任务2
xTaskCreate((TaskFunction_t )task2,
(const char* )"task2",
(uint16_t )TASK2_STK_SIZE,
(void* )NULL,
(UBaseType_t )TASK2_PRIO,
(TaskHandle_t* )&Task2_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*!
\brief task1释放计数型信号量
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void task1(void *pvParameters)
{
uint8_t key = 0;
while(1)
{
/*获取按键值*/
key = Key_Scan(0);
if(key == KEY0_PRES)
{
if(count_semphore_handle != NULL)
{
if(xSemaphoreGive(count_semphore_handle))
{
taskENTER_CRITICAL(); //进入临界区
printf("计数型信号量释放成功当前值为%d\r\n\r\n",(int)uxSemaphoreGetCount(count_semphore_handle));
taskEXIT_CRITICAL(); //退出临界区
}
}
}
vTaskDelay(10);
}
}
/*!
\brief task2获取计数型信号量
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void task2(void *pvParameters)
{
BaseType_t err;
while(1)
{
/*获取信号量死等,进入阻塞态*/
err = xSemaphoreTake(count_semphore_handle,portMAX_DELAY);
if(err == pdTRUE)
{
taskENTER_CRITICAL(); //进入临界区
printf("信号量的计数值为:%d\r\n\r\n",(int)uxSemaphoreGetCount(count_semphore_handle));
taskEXIT_CRITICAL(); //退出临界区
}
vTaskDelay(1000);
}
}
/************************************************************** END OF FILE ****/
优先级翻转简介
优先级翻转:高优先级的任务反而慢执行,低优先级的任务反而优先执行
优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果。
在使用二值信号量的时候,经常会遇到优先级翻转的问题。
举例:
任务H 优先级最高 任务M优先级中等 任务L优先级最低
假设任务L正在运行获取了信号量,其他2任务在阻塞状态,此时任务H就绪抢占了任务L,任务H也是获取信号量,发现信号量没有了进入阻塞态,继续执行任务L(优先级翻转了),然后任务M就绪抢占了任务L,任务M执行完后,进入阻塞态,任务H一直在等信号量所以一直在阻塞态,任务L继续运行,直到释放了信号量后,任何H才会从阻塞态变成就绪态执行。
高优先级任务被低优先级任务阻塞,导致高优先级任务迟迟得不到调度,但其他中等优先级的任务却能抢到CPU资源。从现象上看,就像是中优先级的任务比高优先级任务具有更高的优先权(即优先级翻转)
优先级翻转实验源码
在使用二值信号量的时候会存在优先级翻转的问题,本实验通过模拟的方式实现优先级翻转,观察优先级翻转对抢占式内核的影响。
将设计四个任务:start_task、high_task、middle_task, low_task
start_task:用来创建其它任务
high_task:高优先级任务,会获取二值信号量,获取成功以后打印提示信息,处理完后释放信号量
middle_task:中等优先级任务,简单的应用任务
low_task:低优先级任务,同高优先级一样的操作,不同的是低优先级任务占用信号量的时间久一点
/**
******************************************************************************
* @file : user_mian.h
* @brief : V1.00
******************************************************************************
* @attention
*
******************************************************************************
*/
/* Include 包含---------------------------------------------------------------*/
#include "stm32f10x.h"
#include <stdbool.h>
#include "user_gpio.h"
#include "user_delay.h"
#include "user_rcc_config.h"
#include "user_uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "user_key.h"
/* Typedef 类型----------------------------------------------------------------*/
/* Define 定义----------------------------------------------------------------*/
/* Macro 宏------------------------------------------------------------------*/
/*二值信号量句柄*/
QueueHandle_t semphore_handle;
/* Variables 变量--------------------------------------------------------------*/
/* Constants 常量--------------------------------------------------------------*/
/* Function 函数--------------------------------------------------------------*/
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define HIGH_PRIO 4
//任务堆栈大小
#define HIGH_STK_SIZE 100
//任务句柄
TaskHandle_t HIGH_Handler;
//任务函数
void high_task(void *pvParameters);
//任务优先级
#define MIDDLE_PRIO 3
//任务堆栈大小
#define MIDDLE_STK_SIZE 100
//任务句柄
TaskHandle_t MIDDLE_Handler;
//任务函数
void middle_task(void *pvParameters);
//任务优先级
#define LOW_PRIO 2
//任务堆栈大小
#define LOW_STK_SIZE 100
//任务句柄
TaskHandle_t LOW_Handler;
//任务函数
void low_task(void *pvParameters);
int main(void)
{
/*配置系统中断分组为4位抢占*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/*延时函数初始化*/
delay_init();
/*RCC配置*/
Rcc_config();
/*GPIO初始化*/
Gpio_Init();
/*USART1初始化*/
Uart1_Init(9600);
/*创建二值信号量*/
semphore_handle = xSemaphoreCreateBinary();
if(semphore_handle == NULL)
{
printf("二值信号量创建不成功\r\n\r\n");
}else
{
printf("二值信号量创建成功\r\n\r\n");
}
/*二值释放信号量*/
xSemaphoreGive(semphore_handle);
/*创建开始任务*/
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(); //开启任务调度
}
/*!
\brief 开始任务函数
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建高优先级任务
xTaskCreate((TaskFunction_t )high_task,
(const char* )"high_task",
(uint16_t )HIGH_STK_SIZE,
(void* )NULL,
(UBaseType_t )HIGH_PRIO,
(TaskHandle_t* )&HIGH_Handler);
//创建中优先级任务
xTaskCreate((TaskFunction_t )middle_task,
(const char* )"middle_task",
(uint16_t )MIDDLE_STK_SIZE,
(void* )NULL,
(UBaseType_t )MIDDLE_PRIO,
(TaskHandle_t* )&MIDDLE_Handler);
//创建低优先级任务
xTaskCreate((TaskFunction_t )low_task,
(const char* )"low_task",
(uint16_t )LOW_STK_SIZE,
(void* )NULL,
(UBaseType_t )LOW_PRIO,
(TaskHandle_t* )&LOW_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*!
\brief 高优先级任务
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void high_task(void *pvParameters)
{
while(1)
{
taskENTER_CRITICAL(); //进入临界区
printf("high_task获取信号量\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
/*获取二值信号量,并死等方式*/
xSemaphoreTake(semphore_handle,portMAX_DELAY);
taskENTER_CRITICAL(); //进入临界区
printf("high_task正在运行\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
delay_xms(1000);
/*释放二值信号量*/
taskENTER_CRITICAL(); //进入临界区
printf("high_task释放信号量\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
xSemaphoreGive(semphore_handle);
vTaskDelay(10);
}
}
/*!
\brief 中优先级任务
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void middle_task(void *pvParameters)
{
while(1)
{
taskENTER_CRITICAL(); //进入临界区
printf("middle_task正在运行\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
vTaskDelay(1000);
}
}
/*!
\brief 低优先级任务
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void low_task(void *pvParameters)
{
while(1)
{
taskENTER_CRITICAL(); //进入临界区
printf("low_task获取信号量\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
/*获取二值信号量,并死等方式*/
xSemaphoreTake(semphore_handle,portMAX_DELAY);
taskENTER_CRITICAL(); //进入临界区
printf("low_task正在运行\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
delay_xms(3000);
/*释放二值信号量*/
taskENTER_CRITICAL(); //进入临界区
printf("low_task释放信号量\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
xSemaphoreGive(semphore_handle);
vTaskDelay(1000);
}
}
/************************************************************** END OF FILE ****/
互斥信号量
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中!
优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
此时任务H的阻塞时间仅仅是任务L的执行时间,将优先级翻转的危害降到了最低
优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响
注意:互斥信号量不能用于中断服务函数中,原因如下:
- 互斥信号量有任务优先级继承的机制,但是中断不是任务,没有任务优先级,所以互斥信号量只能用与任务中,不能用于中断服务函数。
- 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
互斥信号量相关API函数
使用互斥信号量:首先将宏configUSE_MUTEXES置一
使用流程:创建互斥信号量→ (task)获取信号量→ (give)释放信号量
创建互斥信号量函数
互斥信号量的释放和获取函数与二值信号量相同!只不过互斥信号量不支持中断中调用
注意:创建互斥信号量时,会主动释放一次信号量
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX)
此函数用于创建互斥信号量
互斥信号量实验源码
在优先级翻转实验的基础,加入互斥信号量,解决优先级翻转问题
/**
******************************************************************************
* @file : user_mian.h
* @brief : V1.00
******************************************************************************
* @attention
*
******************************************************************************
*/
/* Include 包含---------------------------------------------------------------*/
#include "stm32f10x.h"
#include <stdbool.h>
#include "user_gpio.h"
#include "user_delay.h"
#include "user_rcc_config.h"
#include "user_uart.h"
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "user_key.h"
/* Typedef 类型----------------------------------------------------------------*/
/* Define 定义----------------------------------------------------------------*/
/* Macro 宏------------------------------------------------------------------*/
/*二值信号量句柄*/
QueueHandle_t mutex_semphore_handle;
/* Variables 变量--------------------------------------------------------------*/
/* Constants 常量--------------------------------------------------------------*/
/* Function 函数--------------------------------------------------------------*/
//任务优先级
#define START_TASK_PRIO 1
//任务堆栈大小
#define START_STK_SIZE 128
//任务句柄
TaskHandle_t StartTask_Handler;
//任务函数
void start_task(void *pvParameters);
//任务优先级
#define HIGH_PRIO 4
//任务堆栈大小
#define HIGH_STK_SIZE 100
//任务句柄
TaskHandle_t HIGH_Handler;
//任务函数
void high_task(void *pvParameters);
//任务优先级
#define MIDDLE_PRIO 3
//任务堆栈大小
#define MIDDLE_STK_SIZE 100
//任务句柄
TaskHandle_t MIDDLE_Handler;
//任务函数
void middle_task(void *pvParameters);
//任务优先级
#define LOW_PRIO 2
//任务堆栈大小
#define LOW_STK_SIZE 100
//任务句柄
TaskHandle_t LOW_Handler;
//任务函数
void low_task(void *pvParameters);
int main(void)
{
/*配置系统中断分组为4位抢占*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
/*延时函数初始化*/
delay_init();
/*RCC配置*/
Rcc_config();
/*GPIO初始化*/
Gpio_Init();
/*USART1初始化*/
Uart1_Init(9600);
/*创建互斥信号量,默认释放一次*/
mutex_semphore_handle = xSemaphoreCreateMutex();
if(mutex_semphore_handle == NULL)
{
printf("互斥信号量创建不成功\r\n\r\n");
}else
{
printf("互斥信号量创建成功\r\n\r\n");
}
/*创建开始任务*/
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(); //开启任务调度
}
/*!
\brief 开始任务函数
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建高优先级任务
xTaskCreate((TaskFunction_t )high_task,
(const char* )"high_task",
(uint16_t )HIGH_STK_SIZE,
(void* )NULL,
(UBaseType_t )HIGH_PRIO,
(TaskHandle_t* )&HIGH_Handler);
//创建中优先级任务
xTaskCreate((TaskFunction_t )middle_task,
(const char* )"middle_task",
(uint16_t )MIDDLE_STK_SIZE,
(void* )NULL,
(UBaseType_t )MIDDLE_PRIO,
(TaskHandle_t* )&MIDDLE_Handler);
//创建低优先级任务
xTaskCreate((TaskFunction_t )low_task,
(const char* )"low_task",
(uint16_t )LOW_STK_SIZE,
(void* )NULL,
(UBaseType_t )LOW_PRIO,
(TaskHandle_t* )&LOW_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
/*!
\brief 高优先级任务
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void high_task(void *pvParameters)
{
while(1)
{
taskENTER_CRITICAL(); //进入临界区
printf("high_task获取信号量\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
/*获取二值信号量,并死等方式*/
xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY);
taskENTER_CRITICAL(); //进入临界区
printf("high_task正在运行\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
delay_xms(1000);
/*释放二值信号量*/
taskENTER_CRITICAL(); //进入临界区
printf("high_task释放信号量\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
xSemaphoreGive(mutex_semphore_handle);
vTaskDelay(10);
}
}
/*!
\brief 中优先级任务
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void middle_task(void *pvParameters)
{
while(1)
{
taskENTER_CRITICAL(); //进入临界区
printf("middle_task正在运行\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
vTaskDelay(1000);
}
}
/*!
\brief 低优先级任务
\param[in] 传递形参,创建任务时用户自己传入
\param[out] none
\retval none
*/
void low_task(void *pvParameters)
{
while(1)
{
taskENTER_CRITICAL(); //进入临界区
printf("low_task获取信号量\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
/*获取二值信号量,并死等方式*/
xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY);
taskENTER_CRITICAL(); //进入临界区
printf("low_task正在运行\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
delay_xms(3000);
/*释放二值信号量*/
taskENTER_CRITICAL(); //进入临界区
printf("low_task释放信号量\r\n\r\n");
taskEXIT_CRITICAL(); //退出临界区
xSemaphoreGive(mutex_semphore_handle);
vTaskDelay(1000);
}
}
/************************************************************** END OF FILE ****/