资料来源于硬件家园:资料汇总 - FreeRTOS实时操作系统课程(多任务管理)
目录
一、互斥信号量的定义与应用
1、互斥信号量的定义
2、互斥信号量的应用
3、简要了解递归互斥信号量
二、优先级翻转问题
1、运行条件
2、优先级翻转编程测试
三、互斥信号量的运作机制
四、互斥信号量常用的API函数
1、互斥信号量的典型流程与API
2、互斥信号量创建与删除
3、互斥信号量释放
4、互斥信号量获取
五、互斥信号量的应用编程
1、使能互斥信号量
2、创建互斥信号量
3、创建三个优先级不同任务验证
一、互斥信号量的定义与应用
1、互斥信号量的定义
前面学过,取值只有0与1两种状态的信号量称之为二值信号量。 而互斥信号量是一种特殊的二值信号量,具有防止优先级翻转的特性。
创建互斥信号量时,系统会为创建的互斥信号量分配内存,互斥信号量创建完成后的示意图如下:
2、互斥信号量的应用
在嵌入式操作系统中,互斥信号量用于临界资源的独占式访问,只能用于任务与任务间,因为其特有的优先级继承机制只能在任务中起作用,在中断的上下文环境毫无意义。
应用场景:
比如有两个任务需要通过同一串口发送数据,其硬件资源只有一个,那么两个任务不能同时发送,否则会导致数据错误。此时就可以用互斥信号量对串口资源进行保护,当任务1正在使用串口发送数据时,互斥信号量变为无效,任务2无法使用串口,任务2必须等待互斥信号量有效(任务1释放信号量),才能获得串口使用权,进而发送数据。
3、简要了解递归互斥信号量
递归互斥信号量是一种特殊的互斥信号量,支持拥有该信号量使用权的任务重复多次获取,而不会死锁。 任务成功获取几次递归互斥信号量,就要返还几次,在此之前,递归互斥信号量都处于无效状态。
二、优先级翻转问题
使用互斥信号量,可以有效的防止优先级翻转问题。
1、运行条件
创建 3 个任务 Task1,Task2 和 Task3,优先级分别为 3,2,1。也就是 Task1 的优先级最高。
任务 Task1 和 Task3 互斥访问串口打印 printf,采用二值信号实现互斥访问。
起初 Task3 通过二值信号量正在调用 printf,被任务 Task1 抢占,开始执行任务 Task1,也就是上图的起始位置。
任务 Task1 运行的过程需要调用函数 printf,发现任务 Task3 正在调用,任务 Task1 会被挂起,等待 Task3 释放函数 printf。
在调度器的作用下,任务 Task3 得到运行,Task3 运行的过程中,由于任务 Task2 就绪,抢占了 Task3的运行。优先级翻转问题就出在这里了,从任务执行的现象上看,任务 Task1 需要等待 Task2 执行完毕才有机会得到执行,这个与抢占式调度正好反了,正常情况下应该是高优先级任务抢占低优先级任务的执行,这里成了高优先级任务 Task1 等待低优先级任务 Task2 完成。这种情况被称之为优先级翻转问题。
任务 Task2 执行完毕后,任务 Task3 恢复执行,Task3 释放互斥资源后,任务 Task1 得到互斥资源,从而可以继续执行。
2、优先级翻转编程测试
三、互斥信号量的运作机制
互斥量处理不同任务对临界资源的访问时,任务要获得互斥量才能进行资源访问。一旦有任务成功获得了互斥量,则互斥量立即变为闭锁状态,此时其他任务会因为获取不到互斥量而不能访问这个资源。任务会根据用户自定义的等待时间进行等待,直到互斥量被持有的任务释放,其他任务才能获取互斥量从而得以访问该临界资源。此时互斥量再次上锁,如此一来就可以确保每个时刻只有一个任务正在访问这个临界资源,保证了临界资源操作的安全性,具体如下图所示。
①:因为互斥量具有优先级继承机制,一般选择使用互斥量对资源进行保护。当采用互斥量保护的资源被占用时,无论是什么优先级的任务,想要使用该资源都会被阻塞
②:假如正在使用该资源的任务1比阻塞中的任务2的优先级低,那么任务1的优先级将被系统临时提升到与高优先级任务2相等(任务1的优先级从L变成H),这个就是所谓的优先级继承,这样就有效地防止了优先级翻转问题,因为此时优先级介于任务1与任务2之间的任务,抢占不了CPU。
③:当任务1使用完资源之后,释放互斥量,此时任务1的优先级会从H变回原来的L
④~⑤:任务2此时可以获得互斥量,然后进行资源的访问,当任务2访问了资源时,该互斥量的状态又变为闭锁状态,其他任务无法获取互斥量。
四、互斥信号量常用的API函数
1、互斥信号量的典型流程与API
> 创建互斥信号量 xSemaphoreCreateMutex()
> 释放互斥信号量 xSemaphoreGive()
> 获取互斥信号量 xSemaphoreTake()
> 删除互斥信号量 vSemaphoreDelete()
2、互斥信号量创建与删除
互斥信号量控制块(句柄)
如下图:二值信号量的句柄为消息队列的句柄,因为二值信号量是一种长度为1,消息大小为0的特殊消息队列
互斥信号量创建
函数原型:SemaphoreHandle_t xSemaphoreCreateMutex( void )
函数描述:函数 xSemaphoreCreateMutex 用于创建互斥信号量。
返回值,如果创建成功会返回互斥信号量的句柄,如果由于 FreeRTOSConfig.h 文件中 heap 大小不足,无法为此互斥信号量提供所需的空间会返回 NULL。
使用这个函数要注意以下问题
1. 使用此函数要在 FreeRTOSConfig.h 文件中使能宏定义:#define configUSE_MUTEXES 1
说明:此函数基于消息队列函数实现:
应用举例:
互斥信号量删除
函数原型:void vSemaphoreDelete( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */
函数描述:函数 vSemaphoreDelete可用于删除互斥信号量。
3、互斥信号量释放
函数原型:xSemaphoreGive( SemaphoreHandle_t xSemaphore ); /* 信号量句柄 */
函数描述:函数 xSemaphoreGive 用于在任务代码中释放信号量。
第 1 个参数是信号量句柄。
返回值,如果信号量释放成功返回 pdTRUE,否则返回 pdFALSE,因为信号量的实现是基于消息队列,返回失败的主要原因是消息队列已经满了。
使用这个函数要注意以下问题:
1. 此函数是用于任务代码中调用的,不可以在中断服务程序中调用此函数。
2. 使用此函数前,一定要保证用函数 xSemaphoreCreateBinary(), xSemaphoreCreateMutex() 或者xSemaphoreCreateCounting()创建了信号量。
3. 此函数不支持使用 xSemaphoreCreateRecursiveMutex()创建的信号量。
应用举例:
4、互斥信号量获取
函数原型:xSemaphoreTake( SemaphoreHandle_t xSemaphore, TickType_t xTicksToWait ); 函数描述:函数 xSemaphoreTake 用于在任务代码中获取信号量。
第 1 个参数是信号量句柄。
第 2 个参数是没有信号量可用时,等待信号量可用的最大等待时间,单位系统时钟节拍。 返回值,如果创建成功会获取信号量返回 pdTRUE,否则返回 pdFALSE。
使用这个函数要注意以下问题:
1. 此函数是用于任务代码中调用的,不可以在中断服务程序中调用此函数。
2. 如果消息队列为空且第 2 个参数为 0,那么此函数会立即返回。
3. 如果用户将 FreeRTOSConfig.h 文件中的宏定义 INCLUDE_vTaskSuspend 配置为 1 且第 2 个参数配置为 portMAX_DELAY,那么此函数会永久等待直到信号量可用。
应用举例:
五、互斥信号量的应用编程
1、使能互斥信号量
2、创建互斥信号量
3、创建三个优先级不同任务验证
if(myMutex01Handle==NULL)
HAL_UART_Transmit(&huart2, (uint8_t*)"创建互斥信号量成功 \r\n",16, HAL_MAX_DELAY);
else
HAL_UART_Transmit(&huart2, (uint8_t*)"创建互斥信号量失败 \r\n",16, HAL_MAX_DELAY);
void StartTask03(void const * argument)
{
/* USER CODE BEGIN StartTask03 */
BaseType_t xResult;
/* Infinite loop */
for(;;)
{
HAL_UART_Transmit(&huart2, (uint8_t*)"H_StartTask03获取互斥信号量...\r\n",32, HAL_MAX_DELAY);
xResult=xSemaphoreTake(myMutex01Handle,portMAX_DELAY);
if(xResult==pdTRUE)
{
HAL_UART_Transmit(&huart2, (uint8_t*)"H_StartTask03 Running...\r\n",28, HAL_MAX_DELAY);
}
HAL_UART_Transmit(&huart2, (uint8_t*)"H_StartTask03 释放互斥信号量...\r\n",35, HAL_MAX_DELAY);
xResult=xSemaphoreGive(myMutex01Handle);
osDelay(500);
}
/* USER CODE END StartTask03 */
}
void StartTask04(void const * argument)
{
/* USER CODE BEGIN StartTask04 */
/* Infinite loop */
for(;;)
{
HAL_UART_Transmit(&huart2, (uint8_t*)"M_StartTask04 runing...\r\n",27, HAL_MAX_DELAY);
osDelay(500);
}
/* USER CODE END StartTask04 */
}
void StartTask05(void const * argument)
{
/* USER CODE BEGIN StartTask05 */
BaseType_t xResult;
/* Infinite loop */
for(;;)
{
HAL_UART_Transmit(&huart2, (uint8_t*)"L_StartTask03获取互斥信号量...\r\n",27, HAL_MAX_DELAY);
xResult=xSemaphoreTake(myMutex01Handle,portMAX_DELAY);
if(xResult==pdTRUE)
{
HAL_UART_Transmit(&huart2, (uint8_t*)"L_StartTask03 Running...\r\n",28, HAL_MAX_DELAY);
}
HAL_Delay(3000);
HAL_UART_Transmit(&huart2, (uint8_t*)"L_StartTask03 释放互斥信号量...\r\n",28, HAL_MAX_DELAY);
xResult=xSemaphoreGive(myMutex01Handle);
osDelay(500);
}
/* USER CODE END StartTask05 */
}