12、信号量
12.1 信号量简介
可以理解为:信号发送的是状态,队列发送的则是数据。
信号量是一种解决同步问题的机制,可以实现对共享资源的有序访问
队列与信号量的区别:
12.2 二值信号量
二值信号量的本质是一个队列长度为1的队列,该队列只有空和满两种情况。
二值信号量通常用于互斥访问或任务同步,与互斥信号量比较类似,但是二值信号量有可能会导致优先级反转的问题,所以二值信号量更适合用于同步!
使用二值信号量的过程:创建二值信号量->释放二值信号量->获取二值信号量
与二值信号量相关的API函数:
创建二值信号量函数:SemaphoreHandle_t xSemaphoreCreateBinary( void )
该函数的本质就是调用创建队列的函数xQueueGenericCreate();,只不过第三个参数不一样。
从上图可以看出,其调用的函数与创建队列函数完全相同。
释放二值信号量函数:BaseType_t xSemaphoreGive( xSemaphore )
获取二值信号量函数:BaseType_t xSemaphoreTake( xSemaphore, xBlockTime )
12.3 二值信号量编程实战
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 精英F103开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 1
TaskHandle_t Start_Task_Handler;
void start_task( void * pvParameters );
/* Task1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_STACK_SIZE 128
#define TASK1_PRIORITY 2
TaskHandle_t Task1_Handler;
void task1( void * pvParameters );
/* Task2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 3
TaskHandle_t Task2_Handler;
void task2( void * pvParameters );
/******************************************************************************************************/
QueueHandle_t Semphoer_Handler;
/*
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
Semphoer_Handler = xSemaphoreCreateBinary();
if(Semphoer_Handler != NULL)
{
printf("二值信号量创建成功!\r\n");
}
else
{
printf("二值信号量创建失败!\r\n");
}
xTaskCreate(( TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIORITY,
(TaskHandle_t * ) &Start_Task_Handler );
vTaskStartScheduler();//开启任务调度器
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
/*创建任务1*/
xTaskCreate(( TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK1_PRIORITY,
(TaskHandle_t * ) &Task1_Handler );
/*创建任务2*/
xTaskCreate(( TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK2_PRIORITY,
(TaskHandle_t * ) &Task2_Handler );
vTaskDelete(NULL);//删除开始任务
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/*任务一,释放二值信号量*/
void task1( void * pvParameters )
{
uint8_t Key = 0;
BaseType_t Err;
while(1)
{
Key = key_scan(0);
if( Key == KEY0_PRES )
{
if(Semphoer_Handler != NULL)
{
Err = xSemaphoreGive( Semphoer_Handler );
if(Err == pdPASS)
{
printf("二值信号量释放成功!\r\n");
}
else
{
printf("二值信号量释放失败!\r\n");
}
}
}
vTaskDelay(10);
}
}
/*任务二,获取二值信号量*/
void task2( void * pvParameters )
{
BaseType_t Err;
while(1)
{
Err = xSemaphoreTake(Semphoer_Handler,portMAX_DELAY);
if(Err == pdTRUE)
{
printf("二值信号量获取成功!\r\n");
}
else
{
printf("二值信号量获取失败!\r\n");
}
}
}
12.4 计数型信号量
计数型信号量相当于队列长度大于1 的队列,因此计数型信号量能够容纳多个资源
计数型信号量主要有两个适用场合:
1、事件计数:每次事件发生后,事件处理函数中释放计数型信号量(计数值+1),其他任务获取信号量(计数值-1),这种场合一般在创建时将初始计数值设置为0。
2、资源管理:信号量表示有效的资源数目。任务必须先获取信号量(信号量计数值-1 )才能获取资源控制权。当计数值减为零时表示没有的资源。当任务使用完资源后,必须释放信号量(信号量计数值+1)。信号量创建时计数值应等于最大资源数目。
计数型信号量相关的API函数:
计数型信号量动态创建API函数:
获取信号量计数值API函数:
12.5 计数型信号量编程实战
freertos_demo.c:
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 精英F103开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 1
TaskHandle_t Start_Task_Handler;
void start_task( void * pvParameters );
/* Task1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_STACK_SIZE 128
#define TASK1_PRIORITY 2
TaskHandle_t Task1_Handler;
void task1( void * pvParameters );
/* Task2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 3
TaskHandle_t Task2_Handler;
void task2( void * pvParameters );
/******************************************************************************************************/
QueueHandle_t Count_Semphoer_Handler;
/*
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
Count_Semphoer_Handler = xSemaphoreCreateCounting(100,0); /*创建计数型信号量*/
if(Count_Semphoer_Handler != NULL)
{
printf("计数型信号量创建成功!\r\n");
}
else
{
printf("计数型信号量创建失败!\r\n");
}
xTaskCreate(( TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIORITY,
(TaskHandle_t * ) &Start_Task_Handler );
vTaskStartScheduler();//开启任务调度器
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
/*创建任务1*/
xTaskCreate(( TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK1_PRIORITY,
(TaskHandle_t * ) &Task1_Handler );
/*创建任务2*/
xTaskCreate(( TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK2_PRIORITY,
(TaskHandle_t * ) &Task2_Handler );
vTaskDelete(NULL);//删除开始任务
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/*任务一,释放计数型信号量*/
void task1( void * pvParameters )
{
uint8_t Key = 0;
while(1)
{
Key = key_scan(0);
if( Key == KEY0_PRES )
{
if(Count_Semphoer_Handler != NULL)
{
xSemaphoreGive( Count_Semphoer_Handler ); /*释放信号量*/
}
}
vTaskDelay(10);
}
}
/*任务二,获取计数型信号量*/
void task2( void * pvParameters )
{
BaseType_t Err = 0;
UBaseType_t Count_Value;
while(1)
{
Err = xSemaphoreTake(Count_Semphoer_Handler,portMAX_DELAY);
if(Err == pdTRUE)
{
Count_Value = uxSemaphoreGetCount( Count_Semphoer_Handler );
printf("计数型信号量计数值:%d\r\n",Count_Value);
}
vTaskDelay(1000);
}
}
12.6 优先级翻转
什么是优先级翻转?
优先级翻转:高优先级的任务反而慢执行,低优先级的任务反而优先执行
优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果。
举个例子:
假设存在三个任务H、M、L,他们的优先级依次从高到低,此时有一个二值信号量Q,首先该二值信号量被L获取, 当H由阻塞态进入就绪态时,H抢占L,任务H运行,而任务H此时同样需要获取这个二值信号量Q,但此时Q以及被任务L获取,那么任务H就会因为获取不到Q而进入阻塞态,此时,任务M进入就绪态并抢占任务L,由于任务M的优先级较高,任务M运行,并且由于优先级高于他的任务H一直阻塞,任务M得以完整的运行,直到任务M运行完毕,任务L才能继续运行,而只有任务L释放信号量后,任务H才能运行。通过这种方式,实现了低优先级任务先于高优先级任务运行的效果,这就是任务优先级翻转。
12.7 优先级翻转编程实战
freertos_demo.c:
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 精英F103开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 1
TaskHandle_t Start_Task_Handler;
void start_task( void * pvParameters );
/* Task1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_STACK_SIZE 128
#define TASK1_PRIORITY 2
TaskHandle_t Low_Task_Handler;
void Low_Task( void * pvParameters );
/* Task2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 3
TaskHandle_t Middle_Task_Handler;
void Middle_Task( void * pvParameters );
/* Task3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_STACK_SIZE 128
#define TASK3_PRIORITY 4
TaskHandle_t High_Task_Handler;
void High_Task( void * pvParameters );
/******************************************************************************************************/
QueueHandle_t Semphoer_Handler;
/*
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
Semphoer_Handler = xSemaphoreCreateBinary();
if(Semphoer_Handler != NULL)
{
printf("二值信号量创建成功!\r\n");
}
xSemaphoreGive( Semphoer_Handler ); /*释放一次二值信号量,方便获取*/
xTaskCreate(( TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIORITY,
(TaskHandle_t * ) &Start_Task_Handler );
vTaskStartScheduler();//开启任务调度器
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
/*创建任务1*/
xTaskCreate(( TaskFunction_t ) Low_Task,
(char * ) "Low_Task",
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK1_PRIORITY,
(TaskHandle_t * ) &Low_Task_Handler );
/*创建任务2*/
xTaskCreate(( TaskFunction_t ) Middle_Task,
(char * ) "Middle_Task",
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK2_PRIORITY,
(TaskHandle_t * ) &Middle_Task_Handler );
/*创建任务3*/
xTaskCreate(( TaskFunction_t ) High_Task,
(char * ) "High_Task",
(configSTACK_DEPTH_TYPE) TASK3_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK3_PRIORITY,
(TaskHandle_t * ) &High_Task_Handler );
vTaskDelete(NULL);//删除开始任务
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/*任务一,低优先级任务*/
void Low_Task( void * pvParameters )
{
while(1)
{
printf("Low_Task获取信号量!\r\n");
xSemaphoreTake(Semphoer_Handler,portMAX_DELAY);
printf("Low_Task正在运行!\r\n");
delay_ms(3000);
printf("Low_Task释放信号量!\r\n");
xSemaphoreGive(Semphoer_Handler);
vTaskDelay(1000);
}
}
/*任务二,中优先级任务*/
void Middle_Task( void * pvParameters )
{
while(1)
{
printf("中优先级任务正在运行!\r\n");
vTaskDelay(1000);
}
}
/*任务三,高优先级任务*/
void High_Task( void * pvParameters )
{
while(1)
{
printf("High_Task获取信号量!\r\n");
xSemaphoreTake(Semphoer_Handler,portMAX_DELAY);
printf("High_Task正在运行!\r\n");
delay_ms(1000);
printf("High_Task释放信号量!\r\n");
xSemaphoreGive(Semphoer_Handler);
vTaskDelay(1000);
}
}
实验现象:
12.8 互斥信号量
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中!
优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
互斥信号量相关的API函数:
使用互斥信号量:首先将宏configUSE_MUTEXES置一
使用流程:创建互斥信号量 ->(task)获取信号量 ->(give)释放信号量
注意:创建互斥信号量时,会主动释放一次信号量
12.9 互斥信号量编程实战
freertos_demo.c:
/**
****************************************************************************************************
* @file freertos.c
* @author 正点原子团队(ALIENTEK)
* @version V1.4
* @date 2022-01-04
* @brief FreeRTOS 移植实验
* @license Copyright (c) 2020-2032, 广州市星翼电子科技有限公司
****************************************************************************************************
* @attention
*
* 实验平台:正点原子 精英F103开发板
* 在线视频:www.yuanzige.com
* 技术论坛:www.openedv.com
* 公司网址:www.alientek.com
* 购买地址:openedv.taobao.com
*
****************************************************************************************************
*/
#include "freertos_demo.h"
#include "./SYSTEM/usart/usart.h"
#include "./BSP/LED/led.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
#include "./MALLOC/malloc.h"
/*FreeRTOS*********************************************************************************************/
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_STACK_SIZE 128
#define START_TASK_PRIORITY 1
TaskHandle_t Start_Task_Handler;
void start_task( void * pvParameters );
/* Task1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_STACK_SIZE 128
#define TASK1_PRIORITY 2
TaskHandle_t Low_Task_Handler;
void Low_Task( void * pvParameters );
/* Task2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_STACK_SIZE 128
#define TASK2_PRIORITY 3
TaskHandle_t Middle_Task_Handler;
void Middle_Task( void * pvParameters );
/* Task3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_STACK_SIZE 128
#define TASK3_PRIORITY 4
TaskHandle_t High_Task_Handler;
void High_Task( void * pvParameters );
/******************************************************************************************************/
QueueHandle_t Mutex_Semphoer_Handler;
/*
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
Mutex_Semphoer_Handler = xSemaphoreCreateMutex(); /*创建互斥信号量,并且主动释放一次信号量*/
if(Mutex_Semphoer_Handler != NULL)
{
printf("互斥信号量创建成功!\r\n");
}
xTaskCreate(( TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIORITY,
(TaskHandle_t * ) &Start_Task_Handler );
vTaskStartScheduler();//开启任务调度器
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
/*创建任务1*/
xTaskCreate(( TaskFunction_t ) Low_Task,
(char * ) "Low_Task",
(configSTACK_DEPTH_TYPE) TASK1_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK1_PRIORITY,
(TaskHandle_t * ) &Low_Task_Handler );
/*创建任务2*/
xTaskCreate(( TaskFunction_t ) Middle_Task,
(char * ) "Middle_Task",
(configSTACK_DEPTH_TYPE) TASK2_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK2_PRIORITY,
(TaskHandle_t * ) &Middle_Task_Handler );
/*创建任务3*/
xTaskCreate(( TaskFunction_t ) High_Task,
(char * ) "High_Task",
(configSTACK_DEPTH_TYPE) TASK3_STACK_SIZE,
(void * ) NULL,
( UBaseType_t ) TASK3_PRIORITY,
(TaskHandle_t * ) &High_Task_Handler );
vTaskDelete(NULL);//删除开始任务
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/*任务一,低优先级任务*/
void Low_Task( void * pvParameters )
{
while(1)
{
printf("Low_Task获取信号量!\r\n");
xSemaphoreTake(Mutex_Semphoer_Handler,portMAX_DELAY);
printf("Low_Task正在运行!\r\n");
delay_ms(3000);
printf("Low_Task释放信号量!\r\n");
xSemaphoreGive(Mutex_Semphoer_Handler);
vTaskDelay(1000);
}
}
/*任务二,中优先级任务*/
void Middle_Task( void * pvParameters )
{
while(1)
{
printf("中优先级任务正在运行!\r\n");
vTaskDelay(1000);
}
}
/*任务三,高优先级任务*/
void High_Task( void * pvParameters )
{
while(1)
{
printf("High_Task获取信号量!\r\n");
xSemaphoreTake(Mutex_Semphoer_Handler,portMAX_DELAY);
printf("High_Task正在运行!\r\n");
delay_ms(1000);
printf("High_Task释放信号量!\r\n");
xSemaphoreGive(Mutex_Semphoer_Handler);
vTaskDelay(1000);
}
}
实验现象:
对比之前的优先级翻转实验现象,可以很明显看出当执行High_Task获取信号量后,由于将Low_Task的优先级拉高,中优先级任务无法抢占Low_Task。