系列文章目录
文章目录
- 系列文章目录
- 信号量
- 二值信号量
- 二值信号量API函数
- 创建二值信号量函数
- 释放二值信号量函数
- 获取二值信号量函数
- 实验测试
- 计数型信号量
- 计数型信号量API函数
- 动态创建函数
- 信号量计数值获取函数
- 实验测试
- 优先级反翻转
- 实验测试
- 互斥信号量
- API函数
- 实验测试
信号量
信号量解决同步问题的机制,可以对共享资源的有序访问。
当计数值大于0,代表有信号量资源
当释放信号量,计数值即资源数加1
当获取信号量,信号量计数值减1
一般计数值的最大值有限制,最大值为1:二值信号量;最大值不为1:计数型信号量。
二值信号量
二值信号量的本质是一个队列长度为 1 的队列 ,该队列就只有空和满两种情况。
二值信号量通常用于互斥访问或任务同步, 与互斥信号量比较类似,但是二值信号量有可能会导致优先级翻转的问题 ,二值信号量更适合用于同步!
二值信号量API函数
创建二值信号量—释放二值信号量—获取二值信号量
函数 | 描述 |
---|---|
xSemaphoreCreateBinary() | 使用动态方式创建二值信号量 |
xSemaphoreCreateBinaryStatic() | 使用静态方式创建二值信号量 |
xSemaphoreGive() | 释放信号量 |
xSemaphoreGiveFromISR() | 在中断中释放信号量 |
xSemaphoreTake() | 获取信号量 |
xSemaphoreTakeFromISR() | 在中断中获取信号量 |
创建二值信号量函数
#define xSemaphoreCreateBinary() \
xQueueGenericCreate( 1,semSEMAPHORE_QUEUE_ITEM_LENGTH,queueQUEUE_TYPE_BINARY_SEMAPHORE)
#define semSEMAPHORE_QUEUE_ITEM_LENGTH ((uint8_t)0U)
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) /* 队列 */
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U ) /* 队列集 */
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U ) /* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U ) /* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) /* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U ) /* 递归互斥信号量
和队列通用的创建函数,参数:队列的长度(1),队列项的大小(0),类型
函数的返回值:NULL:创建失败;其他值:创建成功,返回二值信号量的句柄。
释放二值信号量函数
#define xSemaphoreGive ( xSemaphore ) \
xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ) , NULL , semGIVE_BLOCK_TIME , queueSEND_TO_BACK )
#define semGIVE_BLOCK_TIME ( ( TickType_t ) 0U
形参:
xSemaphore:要释放的信号量句柄(释放是没有阻塞时间的,只有立刻释放或释放失败)
返回值:
pdPASS:释放信号量成功;errQUEUE_FULL:释放信号量失败;
获取二值信号量函数
BaseType_t xSemaphoreTake( xSemaphore, xBlockTime )
形参:
xSemaphore:要获取的任务量句柄
xBlockTime:阻塞时间(获取有阻塞时间,可以永远阻塞)
返回值:
pdTRUE:互殴去信号量成功;
pdFALSE:超时,获取信号量失败。
实验测试
#include "semphr.h"
int fputc(int ch,FILE *f)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,0xffff);
return ch;
}
TaskHandle_t task1_handler;
#define TASK1_PRIO 2
#define TASK1_STACK_SIZE 128
TaskHandle_t task1_handler;
void task1( void * pvParameters );
/* TASK2 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK2_PRIO 2
#define TASK2_STACK_SIZE 128
TaskHandle_t task2_handler;
void task2( void * pvParameters );
QueueHandle_t semphore_handle;
void vTaskCode( 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(); /* 退出临界区 */
}
// Function that creates a task.
void vOtherFunction( void )
{
semphore_handle=xSemaphoreCreateBinary();
if(semphore_handle != NULL)
{
printf("二值信号量创建成功!!!\r\n");
}
xTaskCreate( vTaskCode, "tak1", 128, NULL, 1, &task1_handler );
vTaskStartScheduler();
}
void task1( void * pvParameters )
{
BaseType_t err;
if(semphore_handle != NULL)
{
err = xSemaphoreGive(semphore_handle);
if(err == pdPASS)
{
printf("信号量释放成功!!\r\n");
}else printf("信号量释放失败!!\r\n");
}
while(1)
{
vTaskDelay(100);
}
}
void task2( void * pvParameters )
{
BaseType_t err;
while(1)
{
err = xSemaphoreTake(semphore_handle,portMAX_DELAY); /* 获取信号量并死等 portMAX_DELAY可以变换 #define portMAX_DELAY ( TickType_t ) 0xffffffffUL*/
if(err == pdTRUE)
{
printf("获取信号量成功\r\n");
}
vTaskDelay(100);
}
}
计数型信号量
计数型信号量相当于队列长度大于1 的队列,因此计数型信号量能够容纳多个资源,这在计数型信号量被创建的时候确定的
事件计数:当每次事件发生后,在事件处理函数中释放计数型信号量(计数值+1),其他任务会获取计数型信号量(计数值-1) ,这种场合一般在创建时将初始计数值设置为 0 。
资源管理:信号量表示有效的资源数目。任务必须先获取信号量(信号量计数值-1 )才能获取资源控制权。当计数值减为零时表示没有的资源。当任务使用完资源后,必须释放信号量(信号量计数值+1)。信号量创建时计数值应等于最大资源数目
计数型信号量API函数
函数 | 描述 |
---|---|
xSemaphoreCreateCounting() | 使用动态方法创建计数型信号量。 |
xSemaphoreCreateCountingStatic() | 使用静态方法创建计数型信号量 |
uxSemaphoreGetCount() | 获取信号量的计数值 |
动态创建函数
#define xSemaphoreCreateCounting( uxMaxCount , uxInitialCount ) xQueueCreateCountingSemaphore( ( uxMaxCount ) , ( uxInitialCount ) )
形参:
uxMaxCount :计数值的最大值限定;
uxInitialCount :计数值的初始值;
返回值:
NULL:创建失败;
其他值:创建成功返回计数型信号的句柄;
信号量计数值获取函数
#define uxSemaphoreGetCount( xSemaphore ) uxQueueMessagesWaiting( ( QueueHandle_t ) ( xSemaphore ) )
形参:
xSemaphore:信号量句柄
返回值:
整数:当前信号量的计数值大小
实验测试
QueueHandle_t count_semphore_handle;
// Function that creates a task.
void vOtherFunction( void )
{
count_semphore_handle = xSemaphoreCreateCounting(100 , 100); /* 创建计数型信号量,且是满的 */
if(count_semphore_handle != NULL)
{
printf("计数型信号量创建成功!!!\r\n");
}
xTaskCreate( vTaskCode, "tak1", 128, NULL, 1, &task1_handler );
vTaskStartScheduler();
}
//释放计数型信号量
void task1( void * pvParameters ) //优先级是2
{
while(1)
{
xSemaphoreGive(count_semphore_handle); /* 释放信号量 */
vTaskDelay(1000);
printf("信号量释放成功!!\r\n");
}
}
//获取计数型信号量
void task2( void * pvParameters ) //优先级是3
{
BaseType_t err = 0;
while(1)
{
err = xSemaphoreTake(count_semphore_handle,portMAX_DELAY); /* 获取信号量并死等 */
if(err == pdTRUE)
{
printf("信号量的计数值为:%d\r\n",(int)uxSemaphoreGetCount(count_semphore_handle));
}
vTaskDelay(500);
}
}
优先级反翻转
优先级翻转:高优先级的任务反而慢执行,低优先级的任务反而优先执行。
优先级翻转在抢占式内核中是非常常见的,但是在实时操作系统中是不允许出现优先级翻转的,因为优先级翻转会破坏任务的预期顺序,可能会导致未知的严重后果。
在使用二值信号量的时候,经常会遇到优先级翻转的问题。
高优先级任务被低优先级任务阻塞,导致高优先级任务迟迟得不到调度。但其他中等优先级的任务却能抢到CPU资源。从现象上看,就像是中优先级的任务比高优先级任务具有更高的优先权(即优先级翻转)
实验测试
TaskHandle_t task1_handler;
#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 );
/* TASK3 任务 配置
* 包括: 任务句柄 任务优先级 堆栈大小 创建任务
*/
#define TASK3_PRIO 4
#define TASK3_STACK_SIZE 128
TaskHandle_t high_task_handler;
void high_task( void * pvParameters );
void vTaskCode( 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 );
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(); /* 退出临界区 */
}
QueueHandle_t semphore_handle;
// Function that creates a task.
void vOtherFunction( void )
{
semphore_handle = xSemaphoreCreateBinary();
if(semphore_handle != NULL)
{
printf("二值信号量创建成功!!!\r\n");
}
xSemaphoreGive(semphore_handle); /* 释放一次信号量 */
xTaskCreate( vTaskCode, "tak1", 128, NULL, 1, &task1_handler );
vTaskStartScheduler();
}
//低优先级任务
void task1( void * pvParameters )
{
while(1)
{
printf("低优先级任务获取信号量\r\n");
xSemaphoreTake(semphore_handle,portMAX_DELAY);
printf("低优先级任务正在运行!!!\r\n");
HAL_Delay(3000);
printf("低优先级任务释放信号量\r\n");
xSemaphoreGive(semphore_handle);
vTaskDelay(1000);
}
}
//中等优先级任务
void task2( void * pvParameters )
{
while(1)
{
printf("中优先级任务正在运行!!!\r\n");
vTaskDelay(1000);
}
}
/* 任务三,高优先级任务 */
void high_task( void * pvParameters )
{
while(1)
{
printf("高优先级任务获取信号量\r\n");
xSemaphoreTake(semphore_handle,portMAX_DELAY);
printf("高优先级任务正在运行!!!\r\n");
HAL_Delay(1000);
printf("高优先级任务释放信号量\r\n");
xSemaphoreGive(semphore_handle);
vTaskDelay(1000);
}
}
在这一段,低优先级占用二值信号量导致高优先级任务进入阻塞,一直是其他优先级任务执行。
互斥信号量
互斥信号量是一个拥有优先级继承的二值信号量,在同步的应用中二值信号量最适合。互斥信号量适合用于那些需要互斥访问的应用中!
优先级继承:当一个互斥信号量正在被一个低优先级的任务持有时, 如果此时有个高优先级的任务也尝试获取这个互斥信号量,那么这个高优先级的任务就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级。
任务H的阻塞时间仅仅是任务L 的执行时间,将优先级翻转的危害降到了最低
优先级继承并不能完全的消除优先级翻转的问题,它只是尽可能的降低优先级翻转带来的影响
注意:互斥信号量不能用于中断服务函数中,原因如下:
(1) 互斥信号量有任务优先级继承的机制, 但是中断不是任务,没有任务优先级, 所以互斥信号量只能用与任务中,不能用于中断服务函数。
(2) 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
API函数
宏configUSE_MUTEXES置1
使用流程:创建互斥信号量,获取信号量 ,释放信号量
创建函数:
函数 | 描述 |
---|---|
xSemaphoreCreateMutex() | 使用动态方法创建互斥信号量。 |
xSemaphoreCreateMutexStatic() | 使用静态方法创建互斥信号量。 |
创建互斥信号量时,会主动释放一次信号量。
#define xSemaphoreCreateMutex() xQueueCreateMutex( queueQUEUE_TYPE_MUTEX )
返回值:
NULL:创建失败
其他值:创建成功返回互斥信号量的句柄
实验测试
QueueHandle_t mutex_semphore_handle;
// Function that creates a task.
void vOtherFunction( void )
{
mutex_semphore_handle = xSemaphoreCreateMutex();
if(mutex_semphore_handle != NULL)
{
printf("二值信号量创建成功!!!\r\n");
}
xTaskCreate( vTaskCode, "tak1", 128, NULL, 1, &task1_handler );
vTaskStartScheduler();
}
//低优先级任务
void task1( void * pvParameters )
{
while(1)
{
printf("低优先级任务获取信号量\r\n");
xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY);
printf("低优先级任务正在运行!!!\r\n");
HAL_Delay(3000);
printf("低优先级任务释放信号量\r\n");
xSemaphoreGive(mutex_semphore_handle);
vTaskDelay(1000);
}
}
//中等优先级任务
void task2( void * pvParameters )
{
while(1)
{
printf("中优先级任务正在运行!!!\r\n");
vTaskDelay(1000);
}
}
/* 任务三,高优先级任务 */
void high_task( void * pvParameters )
{
while(1)
{
printf("高优先级任务获取信号量\r\n");
xSemaphoreTake(mutex_semphore_handle,portMAX_DELAY);
printf("高优先级任务正在运行!!!\r\n");
HAL_Delay(1000);
printf("高优先级任务释放信号量\r\n");
xSemaphoreGive(mutex_semphore_handle);
vTaskDelay(1000);
}
}