1 计数型信号量
计数型信号量相当于队列长度大于1的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的。
计数型信号量适用场合:
1)事件计数:当每次事件发生后,在事假处理函数中释放计数型信号量(计数值+1),其他任务会获取计数型信号量(计数值-1),这种场合一般在创建时将初始计数值设置为0
2)资源管理:信号量表示有效的资源数目。任务必须先获取信号量(信号量计数值-1)才能获取资源控制权。当计数值减为0时表示没有资源。当任务使用完资源后,必须释放信号量(信号量计数值+1)。信号量创建时计数值应等于最大资源数目。
使用计数型信号量的过程:
创建计数型信号量->释放信号量->获取信号量
函数 | 描述 |
xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量 |
xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 |
uxSemaphoreGetCount() | 获取信号量的计数值 |
计数型信号量的释放和获取与二值信号量相同!
A.使用动态方法创建计数型信号量
#define xSemaphoreCreateCounting( uxMaxCount , uxInitialCount )
xQueueCreateCountingSemaphore( ( uxMaxCount ) , ( uxInitialCount ) )
//形参uxMaxCount为计数值的最大值限定
//形参uxInitialCount为计数值的初始值
//返回值为NULL则表示创建计数型信号量失败,
//返回值为其它值则表示创建成功并且返回计数型信号量的句柄
B.获取信号量当前计数值大小
#define uxSemaphoreGetCount( xSemaphore )
uxQueueMessagesWaiting( ( QueueHandle_t ) ( xSemaphore ) )
//形参xSemaphore表示信号量句柄
//返回值为整数则表示当前信号量的计数值大小
1.1计数型信号量实验
实验设计:设计三个任务:start_task、task1、task2
三个任务的功能如下:
start_task:用来创建task1和task2任务
task1:用于按键扫描,当检测到按键KEY0被按下时,释放计数型信号量
task2:每过一秒获取一次计数型信号量,当成功获取后打印信号量计数值
#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 "semphr.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
//返回值类型
QueueHandle_t count_semphore_handler;
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
//动态创建计数型信号量,并设置上限值和初始值
count_semphore_handler = xSemaphoreCreateCounting(100,0);
if(count_semphore_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_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
xTaskCreate((TaskFunction_t ) task1,
(char * ) "task1",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &task1_handler );
xTaskCreate((TaskFunction_t ) task2,
(char * ) "task2",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(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(count_semphore_handler != NULL)
{
xSemaphoreGive(count_semphore_handler);//按下KEY0释放信号量+1
}
}
vTaskDelay(10);
}
}
/* 任务二,获取计数型信号量*/
void task2( void * pvParameters )
{
BaseType_t err = 0;
while(1)
{
err = xSemaphoreTake(count_semphore_handler,portMAX_DELAY);//获取信号量并死等
if(err = pdTRUE)
{
printf("信号量的计数值为:%d\r\n",(int)uxSemaphoreGetCount(count_semphore_handler));
}
vTaskDelay(1000);
}
}
2 优先级翻转
优先级翻转:高优先级的任务反而会慢执行,低优先级的任务反而会优先执行
优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果
在使用二值信号量时,会经常遇到优先级翻转问题。
假设有三个任务:分别为任务L、任务M、任务H
其中任务H的任务优先级最高且获取二值信号量,获取成功以后打印提示信息,处理完后释放信号量;任务M 为中等优先级,处理简单的打印任务; 任务L的任务优先等级最低,其任务和任务H的相同,不同的是任务L占用信号量的时间久一点。
首先任务L获取二值信号量,任务H优先级高于任务L并且任务H处于就绪态,因此任务H抢占任务L并运行,直到任务H进入阻塞态释放信号量。
然后,信号量释放后被就绪态任务L占用,因此任务L运行,由于任务M处于就绪态且任务M任务优先级高于任务L,因此任务M抢占CPU使用权,任务M运行,直到任务M运行结束。
最后,由于信号量被任务L获取,因此需要等待任务L运行结束后,任务L释放信号量,任务H才能够获取信号量,接着刚才的阻塞态继续运行。
从上述中看出,高优先级任务被低优先级任务阻塞,导致高优先级任务吃吃得不到调度。但其他中等优先级的任务却能抢到CPU资源。从现象上看,就像是中优先级任务比高优先级任务具有更高的优先权(即优先级翻转)。
2.1 优先级翻转实验
实验设计:将设计四个任务:start_task、high_task、middle_task、low_task
start_task:用来创建其他任务
high_task:高优先级任务,会获取二值信号量,获取成功后打印提示信息,处理完后释放信号量
middle_task:中等优先级任务,简单的应用任务
low_task:低优先级任务,同高优先级一样的操作,不同的是低优先级任务占用信号量的时间久一点
#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 "semphr.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t low_task_handler;
void low_task( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t middle_task_handler;
void middle_task ( void * pvParameters );
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t high_task_handler;
void high_task ( void * pvParameters );
//返回值类型
QueueHandle_t semphore_handler;
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
//动态创建二值信号量
semphore_handler = xSemaphoreCreateBinary();
if(semphore_handler != NULL)
{
printf("二值信号量创建成功!\r\n");
}
//释放二值信号量
xQueueGenericSend(semphore_handler);
xTaskCreate((TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
xTaskCreate((TaskFunction_t ) low_task,
(char * ) "low_task",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &low_task_handler );
xTaskCreate((TaskFunction_t ) middle_task,
(char * ) "middle_task",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &middle_task_handler );
xTaskCreate((TaskFunction_t ) high_task,
(char * ) "high_task",
(configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK3_PRIO,
(TaskHandle_t * ) &high_task_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/* 任务一,低优先级任务*/
void low_task( void * pvParameters )
{
while(1)
{
printf("low_task获取信号量!!!\r\n");
xSemaphoreTake(semphore_handler,portMAX_DELAY);
printf("low_task正在运行!!!!!\r\n");
delay_ms(3000); //3ms
printf("low_task释放信号量!!!!!\r\n");
xSemaphoreGive(semphore_handler);
vTaskDelay(1000);
}
}
/* 任务二,中优先级任务*/
void middle_task( void * pvParameters )
{
while(1)
{
printf("middle_task正在运行。\r\n");
vTaskDelay(1000);
}
}
/* 任务三,高优先级任务*/
void high_task( void * pvParameters )
{
while(1)
{
printf("high_task获取信号量\r\n");
xSemaphoreTake(semphore_handler,portMAX_DELAY);
printf("high_task正在运行\r\n");
delay_ms(1000); //1ms
printf("high_task释放信号量\r\n");
xSemaphoreGive(semphore_handler);
vTaskDelay(1000);
}
}
3 互斥信号量
互斥信号量其实就是一个拥有优先级继承的二值信号量,在同步应用中二值信号量最适合。互斥信号量适用于那些需要互斥访问点应用中
优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时,如果此时有个高优先级的任务也尝试获取这互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
如上图所示,当信号量被任务L占用,任务H被阻塞,同时任务H将任务L设置为同等优先级,等待任务L运行结束不会被任务M占用,因此也不会产生优先级翻转。
此时任务H的阻塞时间仅仅是任务L的执行时间,将优先级翻转的危害降到了最低。
注意:
1)优先级继承并不能完全消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响。
2)互斥信号量不能用于中断服务函数中,原因包括:
A 互斥信号量有任务优先级继承的机制,但是中断不是任务,没有任务优先级,所以互斥信号量只能用于任务中,不能用于中断服务函数中。
B 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞状态
3.1互斥信号量相关API函数
使用互斥信号量:首先将宏configUSE_MUTEXES置1
使用流程:创建互斥信号量——>获取信号量——>释放信号量
1)创建互斥信号量函数:
函数 | 描述 |
xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量 |
xSemaphoreCreateMutxStatic() | 使用静态方法创建互斥信号量 |
互斥信号量的释放和获取与二值信号量相同,只不过互斥信号量不支持中断调用
注意:创建互斥信号量时,会主动释放一次信号量
3.2 互斥信号量实验
将优先级翻转所用到的信号量函数,修改成互斥信号量即可,通过串口打印提示信息
#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 "semphr.h"
/******************************************************************************************************/
/*FreeRTOS配置*/
/* START_TASK 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define START_TASK_PRIO 1
#define START_TASK_STACK_SIZE 128
TaskHandle_t start_task_handler;
void start_task( void * pvParameters );
/* TASK1 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t low_task_handler;
void low_task( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 3
#define TASK2_STACK_SIZE 128
TaskHandle_t middle_task_handler;
void middle_task ( void * pvParameters );
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t high_task_handler;
void high_task ( void * pvParameters );
//返回值类型
QueueHandle_t semphore_handler;
/******************************************************************************************************/
/**
* @brief FreeRTOS例程入口函数
* @param 无
* @retval 无
*/
void freertos_demo(void)
{
//动态创建二值信号量
semphore_handler = xSemaphoreCreateBinary();
if(semphore_handler != NULL)
{
printf("二值信号量创建成功!\r\n");
}
//释放二值信号量
xQueueGenericSend(semphore_handler);
xTaskCreate((TaskFunction_t ) start_task,
(char * ) "start_task",
(configSTACK_DEPTH_TYPE ) START_TASK_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) START_TASK_PRIO,
(TaskHandle_t * ) &start_task_handler );
vTaskStartScheduler();
}
void start_task( void * pvParameters )
{
taskENTER_CRITICAL(); /* 进入临界区 */
xTaskCreate((TaskFunction_t ) low_task,
(char * ) "low_task",
(configSTACK_DEPTH_TYPE ) TASK1_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK1_PRIO,
(TaskHandle_t * ) &low_task_handler );
xTaskCreate((TaskFunction_t ) middle_task,
(char * ) "middle_task",
(configSTACK_DEPTH_TYPE ) TASK2_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK2_PRIO,
(TaskHandle_t * ) &middle_task_handler );
xTaskCreate((TaskFunction_t ) high_task,
(char * ) "high_task",
(configSTACK_DEPTH_TYPE ) TASK3_STACK_SIZE,
(void * ) NULL,
(UBaseType_t ) TASK3_PRIO,
(TaskHandle_t * ) &high_task_handler );
vTaskDelete(NULL);
taskEXIT_CRITICAL(); /* 退出临界区 */
}
/* 任务一,低优先级任务*/
void low_task( void * pvParameters )
{
while(1)
{
printf("low_task获取信号量!!!\r\n");
xSemaphoreTake(semphore_handler,portMAX_DELAY);
printf("low_task正在运行!!!!!\r\n");
delay_ms(3000); //3ms
printf("low_task释放信号量!!!!!\r\n");
xSemaphoreGive(semphore_handler);
vTaskDelay(1000);
}
}
/* 任务二,中优先级任务*/
void middle_task( void * pvParameters )
{
while(1)
{
printf("middle_task正在运行。\r\n");
vTaskDelay(1000);
}
}
/* 任务三,高优先级任务*/
void high_task( void * pvParameters )
{
while(1)
{
printf("high_task获取信号量\r\n");
xSemaphoreTake(semphore_handler,portMAX_DELAY);
printf("high_task正在运行\r\n");
delay_ms(1000); //1ms
printf("high_task释放信号量\r\n");
xSemaphoreGive(semphore_handler);
vTaskDelay(1000);
}
}