个人名片:
🎓作者简介:嵌入式领域优质创作者
🌐个人主页:妄北y📞个人QQ:2061314755
💌个人邮箱:[mailto:2061314755@qq.com]
📱个人微信:Vir2025WBY🖥️个人公众号:科技妄北
🖋️本文为妄北y原创佳作,独家首发于CSDN🎊🎊🎊
💡座右铭:改造世界固然伟大,但改造自我更为可贵。
专栏导航:
妄北y系列专栏导航:
物联网嵌入式开发项目:大学期间的毕业设计,课程设计,大创项目,各种竞赛项目,全面覆盖了需求分析、方案设计、实施与调试、成果展示以及总结反思等关键环节。📚💼💡
QT基础入门学习:对QT的基础图形化页面设计进行了一个简单的学习与认识,利用QT的基础知识进行了翻金币小游戏的制作。🛠️🔧💭
Linux基础编程:初步认识什么是Linux,为什么学Linux,安装环境,进行基础命令的学习,入门级的shell编程。🍻🎉🖥️
深耕Linux应用开发:分享Linux的基本概念、命令行操作、文件系统、用户和权限管理等,网络编程相关知识,TCP/IP 协议、套接字(Socket)编程等,可以实现网络通信功能。常见开源库的二次开发,如libcurl、OpenSSL、json-c、freetype等💐📝💡
Linux驱动开发:Linux驱动开发是Linux系统不可或缺的组成部分,它专注于编写特殊的程序——驱动程序。这些程序承载着硬件设备的详细信息,并扮演着操作系统与硬件间沟通的桥梁角色。驱动开发的核心使命在于确保硬件设备在Linux系统上顺畅运作,同时实现与操作系统的无缝集成,为用户带来流畅稳定的体验。🚀🔧💻
Linux项目开发:Linux基础知识的实践,做项目是最锻炼能力的一个学习方法,这里我们会学习到一些简单基础的项目开发与应用,而且都是毕业设计级别的哦。🤸🌱🚀
非常期待与您一同在这个广阔的互联网天地里,携手探索知识的海洋,互相学习,共同进步。🌐💫🌱 熠熠星光,照亮我们的成长之路
✨✨ 欢迎订阅本专栏,对专栏内容任何问题都可以随时联系博主,共同书写属于我们的精彩篇章!✨✨
文章介绍:
📚本篇文章将深入剖析RTOS学习的精髓与奥秘,与您一同分享相关知识!🎉🎉🎉
若您觉得文章尚可入目,期待您能慷慨地送上点赞、收藏与分享的三连支持!您的每一份鼓励,都是我创作路上源源不断的动力。让我们携手并进,共同奔跑,期待在顶峰相见的那一天,共庆辉煌!🚀🚀🚀
🙏衷心感谢大家的点赞👍、收藏⭐和评论✍️,您的支持是我前进的动力!
目录:
目录:
一、什么是互斥信号量
二、创建互斥信号量:
2.1 函数xSemaphoreCreateMutex()
2.2 函数:xSemaphoreCreateMutexStatic()
三、互斥信号量创建过程分析
四、释放互斥信号量:
4.1 函数 prvCopyDataToQueue()
4.4 更新信号量的等待消息计数:
五、获取互斥信号量:
六、互斥信号量操作设计
6.1 程序设计
6.2 任务函数:
六、程序运行结果分析:
一、什么是互斥信号量
互斥信号量本质上是带有优先级继承机制的二值信号量,通常用于需要互斥访问共享资源的场景。与普通的二值信号量一样,它在任务之间或者中断与任务之间的同步操作中广泛使用,特别是用来确保只有一个任务可以同时访问某个资源。
互斥信号量的运行方式可以类比为一把钥匙:当一个任务想要访问某个共享资源时,它需要先获得这把钥匙(即获取信号量)。当任务完成对资源的使用后,它必须归还钥匙(释放信号量),以便其他任务可以继续使用该资源。
与二值信号量的主要区别在于,互斥信号量具备优先级继承特性。假设当前有一个低优先级的任务持有互斥信号量(即在使用共享资源),而此时一个高优先级的任务也试图获取该信号量。由于信号量已被低优先级任务持有,高优先级任务将被阻塞。为了减少高优先级任务的等待时间,系统会临时提升低优先级任务的优先级,使其与高优先级任务相同,以便尽快释放信号量。这种机制称为优先级继承。
优先级继承机制能够有效减轻“优先级翻转”的影响,避免高优先级任务长期等待,从而提高系统的实时性能。
优先级继承机制无法完全消除优先级翻转,它只是尽可能减少因优先级翻转带来的负面影响。在硬实时系统的设计阶段,就应尽量避免优先级翻转的发生,以确保系统的实时性和可靠性。互斥信号量并不适用于中断服务例程,原因如下:
1. 互斥信号量具备优先级继承机制,而这一机制只适用于任务调度,因此只能用于任务之间的同步,无法在中断服务例程中使用。
2. 中断服务例程不能因等待互斥信号量而进入阻塞状态,因为中断是系统中具有较高优先级的事件,必须及时处理,不能等待信号量导致延迟。
因此,互斥信号量应仅在任务间使用,而不应在中断服务例程中使用,以避免引发实时系统中的阻塞和延迟问题。
二、创建互斥信号量:
2.1 函数xSemaphoreCreateMutex()
该函数用于创建一个互斥信号量,内存通过动态内存管理方法进行分配。此函数本质上是一个宏,实际执行信号量创建的底层函数为 xQueueCreateMutex()
。
SemaphoreHandle_t xSemaphoreCreateMutex(void);
参数:
- 无
返回值:
- NULL:表示互斥信号量创建失败。
- 非NULL值:表示创建成功,返回互斥信号量的句柄。
说明:
该函数用于任务间同步,在创建成功后,可以通过返回的句柄操作互斥信号量。
2.2 函数:xSemaphoreCreateMutexStatic()
该函数用于创建一个互斥信号量,但与 xSemaphoreCreateMutex()
不同,使用此函数创建的互斥信号量所需的内存必须由用户自行分配。此函数本质上是一个宏,实际的创建过程由 xQueueCreateMutexStatic()
函数完成。
SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *pxMutexBuffer);
参数:
- pxMutexBuffer:指向一个
StaticSemaphore_t
类型的变量,用于保存信号量的结构体。
返回值:
- NULL:表示互斥信号量创建失败。
- 非NULL值:表示创建成功,返回互斥信号量的句柄。
说明:
使用 xSemaphoreCreateMutexStatic()
函数,用户需要提供一个缓冲区来存储互斥信号量的状态信息。这种方式适用于需要在静态内存中分配信号量的场景,适合于内存受限或需要避免动态内存管理的应用。
三、互斥信号量创建过程分析
xSemaphoreCreateMutex
是一个用于动态创建互斥信号量的宏,其定义如下:
#define xSemaphoreCreateMutex() xQueueCreateMutex(queueQUEUE_TYPE_MUTEX)
从宏定义中可以看出,实际创建互斥信号量的操作是由 xQueueCreateMutex()
函数完成的。
此函数在文件 queue.c
中定义,用于实现互斥信号量的创建。
QueueHandle_t xQueueCreateMutex(const uint8_t ucQueueType)
{
Queue_t *pxNewQueue;
const UBaseType_t uxMutexLength = (UBaseType_t)1, uxMutexSize = (UBaseType_t)0;
// 创建队列
pxNewQueue = (Queue_t *)xQueueGenericCreate(uxMutexLength, uxMutexSize, ucQueueType);
// 初始化互斥信号量
prvInitialiseMutex(pxNewQueue);
// 返回新创建的队列句柄(互斥信号量)
return pxNewQueue;
}
1. 调用 xQueueGenericCreate()
函数:
- 创建一个队列,该队列的长度为1,队列项长度为0,队列类型由参数
ucQueueType
指定。 - 因为此函数用于创建互斥信号量,所以
ucQueueType
参数为queueQUEUE_TYPE_MUTEX
。
2. 调用 prvInitialiseMutex()
函数:
- 对新创建的互斥信号量进行初始化操作。
3. 返回新创建的队列句柄:
- 返回表示互斥信号量的
QueueHandle_t
类型的句柄。
prvInitialiseMutex()
该函数如下:
static void prvInitialiseMutex(Queue_t *pxNewQueue)
{
if(pxNewQueue != NULL)
{
// 1. 将互斥信号量的拥有者置为空,表示当前没有任务拥有互斥信号量
pxNewQueue->pxMutexHolder = NULL; // (1)
// 2. 设置队列类型为互斥信号量
pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX; // (2)
// 3. 如果是递归互斥信号量,则初始化递归调用计数器
pxNewQueue->u.uxRecursiveCallCount = 0; // (3)
// 跟踪互斥信号量的创建
traceCREATE_MUTEX(pxNewQueue);
// 释放互斥信号量,使其默认处于可用状态
(void)xQueueGenericSend(pxNewQueue, NULL, (TickType_t)0, queueSEND_TO_BACK);
}
else
{
// 跟踪互斥信号量创建失败的情况
traceCREATE_MUTEX_FAILED();
}
}
1. pxMutexHolder (宏定义):
pxMutexHolder
通过宏定义,将pcTail
重命名为pxMutexHolder
,用于指向当前持有互斥信号量的任务控制块(TCB)。- 当
Queue_t
结构体被用作队列时,pcTail
指向队列的尾部;但当其被用作互斥信号量时,pcTail
则用来保存持有该互斥信号量的任务。 - 在互斥信号量创建时,将
pxMutexHolder
设置为NULL
,表示当前没有任务拥有该互斥信号量。
2. uxQueueType (宏定义):
uxQueueType
通过宏定义,将pcHead
重命名为uxQueueType
,用于标识队列的类型。- 在队列用作互斥信号量时,不需要
pcHead
来指向存储区域,因此用uxQueueType
来区分它是普通队列还是互斥信号量。 - 在此处,设置
uxQueueType
为queueQUEUE_IS_MUTEX
,标识它是互斥信号量。
在 queue.c
中,相关宏定义如下:
#define pxMutexHolder pcTail
#define uxQueueType pcHead
#define queueQUEUE_IS_MUTEX NULL
pxMutexHolder
:将pcTail
重新定义为pxMutexHolder
,用于指向当前持有互斥信号量的任务。uxQueueType
:将pcHead
重新定义为uxQueueType
,用于表示队列的类型(在这里表示它是一个互斥信号量)。queueQUEUE_IS_MUTEX
:定义为NULL
,用来标识互斥信号量的类型。
整体过程:
在创建互斥信号量时,首先调用 xQueueGenericCreate()
创建一个长度为1、队列项长度为0的队列,类型为 queueQUEUE_TYPE_MUTEX
。
接着,prvInitialiseMutex()
对该队列进行特定的初始化,包括:
- 设置
pxMutexHolder
为NULL
,表示没有任务持有该互斥信号量。 - 设置
uxQueueType
为queueQUEUE_IS_MUTEX
,标识它是互斥信号量。 - 如果是递归互斥信号量,初始化递归计数器。
最后,通过 xQueueGenericSend()
释放互斥信号量,使其默认处于可用状态。
四、释放互斥信号量:
在 FreeRTOS 中,释放互斥信号量的过程与二值信号量和计数型信号量的过程类似,都是通过 xSemaphoreGive()`函数进行的。实际上,xSemaphoreGive()的底层调用了 xQueueGenericSend() 函数,完成信号量的释放操作。不过,由于互斥信号量涉及到优先级继承机制,处理过程有所不同。
释放互斥信号量时,最重要的一步是将互斥信号量的 uxMessagesWaiting值加一,表示当前信号量已经被释放,这一步是在prvCopyDataToQueue()函数中完成的。同时,互斥信号量的优先级继承机制也在 prvCopyDataToQueue()中处理。该函数的核心目的是将数据复制到队列中,或者在互斥信号量的情况下,处理优先级继承。
4.1 函数 prvCopyDataToQueue()
以下是 prvCopyDataToQueue()
函数的相关片段代码,专门处理互斥信号量的优先级继承逻辑:
static BaseType_t prvCopyDataToQueue(Queue_t const * pxQueue,
const void * pvItemToQueue,
const BaseType_t xPosition)
{
BaseType_t xReturn = pdFALSE;
UBaseType_t uxMessagesWaiting;
// 获取当前等待信号量的任务数量
uxMessagesWaiting = pxQueue->uxMessagesWaiting;
// 检查队列项大小为0(意味着这是互斥信号量,而不是普通队列)
if (pxQueue->uxItemSize == (UBaseType_t) 0)
{
#if (configUSE_MUTEXES == 1)
// 处理互斥信号量
if (pxQueue->uxQueueType == queueQUEUE_IS_MUTEX) // (1)
{
// 处理优先级继承,解除互斥信号量的优先级继承
xReturn = xTaskPriorityDisinherit((void *) pxQueue->pxMutexHolder); // (2)
// 将互斥信号量持有者设为 NULL,表示释放
pxQueue->pxMutexHolder = NULL; // (3)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
#endif /* configUSE_MUTEXES */
}
// 处理其他队列类型的逻辑省略
pxQueue->uxMessagesWaiting = uxMessagesWaiting + 1; // 增加信号量的可用计数
return xReturn;
}
1 判断当前操作的是互斥信号量 (1):
- 通过检查
pxQueue->uxQueueType == queueQUEUE_IS_MUTEX
来判断当前操作的队列是否是互斥信号量。 - 当
pxQueue->uxItemSize
为0
时,表明队列项大小为 0,意味着这是一个互斥信号量,而不是常规队列。对于互斥信号量,pcHead
和pcTail
被重定义为uxQueueType
和pxMutexHolder
,因此可以通过宏定义的queueQUEUE_IS_MUTEX
来确认它是互斥信号量。
2 优先级继承处理 (2):
- 调用
xTaskPriorityDisinherit()
函数处理优先级继承问题。优先级继承机制的目的是为了防止 优先级反转,即低优先级任务持有互斥信号量时,有更高优先级的任务需要该信号量。 - 当任务释放互斥信号量时,任务可能需要放弃它继承的高优先级并恢复其原本的优先级,这就是
xTaskPriorityDisinherit()
的作用。 - 该函数会通过比较持有该互斥信号量的任务的优先级和等待信号量的任务的优先级,来决定是否恢复原有的优先级。
3 .清除互斥信号量持有者 (3):
- 当任务成功释放互斥信号量后,信号量就不再属于任何任务了。因此,需要将
pxQueue->pxMutexHolder
设为NULL
,以清除该信号量当前的持有者。 - 这一步非常关键,因为一旦信号量被释放,它就可以被其他任务获取,
pxMutexHolder
必须被重置。
4.4 更新信号量的等待消息计数:
- 函数最后将
uxMessagesWaiting
值加 1,这相当于增加信号量的可用计数,表示信号量已经释放,其他等待任务可以尝试获取它。
函数xTaskPriorityDisinherit()是怎么具体的处理优先级继承的?函数xTaskPriorityDisinherit()代码如下:
BaseType_t xTaskPriorityDisinherit(TaskHandle_t const pxMutexHolder)
{
// 获取持有互斥信号量的任务控制块(TCB)
TCB_t const *pxTCB = (TCB_t *) pxMutexHolder;
BaseType_t xReturn = pdFALSE;
if (pxMutexHolder != NULL) // (1)
{
// 当前释放互斥信号量的任务应该是当前正在运行的任务
configASSERT(pxTCB == pxCurrentTCB);
configASSERT(pxTCB->uxMutexesHeld > 0);
// 释放互斥信号量后,减少任务持有的互斥信号量计数
(pxTCB->uxMutexesHeld)--; // (2)
// 检查任务是否存在优先级继承,如果当前优先级不等于基优先级则存在继承
if (pxTCB->uxPriority != pxTCB->uxBasePriority) // (3)
{
// 如果任务只持有一个互斥信号量
if (pxTCB->uxMutexesHeld == (UBaseType_t) 0) // (4)
{
// 如果任务从其状态列表中被移除,则重置其优先级
if (uxListRemove(&(pxTCB->xStateListItem)) == (UBaseType_t) 0) // (5)
{
// 重置任务的就绪优先级
taskRESET_READY_PRIORITY(pxTCB->uxPriority); // (6)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
// 恢复任务的基优先级
traceTASK_PRIORITY_DISINHERIT(pxTCB, pxTCB->uxBasePriority);
pxTCB->uxPriority = pxTCB->uxBasePriority; // (7)
// 更新事件列表项的值,确保使用基优先级
listSET_LIST_ITEM_VALUE(&(pxTCB->xEventListItem),
(TickType_t)configMAX_PRIORITIES - (TickType_t)pxTCB->uxPriority); // (8)
// 将任务重新添加到就绪列表
prvAddTaskToReadyList(pxTCB); // (9)
xReturn = pdTRUE; // (10)
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
return xReturn;
}
1. 检查互斥信号量持有者是否有效 (1):
- 首先,函数会检查传入的
pxMutexHolder
是否为NULL
,以确保互斥信号量确实被某个任务持有。 - 持有互斥信号量的任务控制块(TCB)通过
pxMutexHolder
来获取。
2. 任务正在释放的互斥信号量 (2):
- 由于此函数处理的是优先级继承问题,因此当前释放互斥信号量的任务应该是当前正在运行的任务,即
pxCurrentTCB
。 - 使用断言
configASSERT
确认这一点,并且任务必须持有至少一个互斥信号量。 - 随后减少任务持有的互斥信号量计数。
3. 检查是否存在优先级继承 (3):
- 如果任务的当前优先级 (
uxPriority
) 不等于其基优先级 (uxBasePriority
),这意味着任务处于优先级继承状态。 - 也就是说,任务因为持有互斥信号量并且有更高优先级的任务在等待该信号量,所以继承了更高的优先级。
4. 任务只持有一个互斥信号量 (4):
- 如果任务在此时只持有一个互斥信号量,即
uxMutexesHeld == 0
,则可以开始恢复其原始优先级。
5. 移除任务状态列表项 (5):
- 如果任务成功从状态列表中移除,即
uxListRemove()
返回0
,则可以进行优先级重置。 - 这一步确认任务的状态列表项已经处理完毕。
6. 重置任务的就绪优先级 (6):
taskRESET_READY_PRIORITY(pxTCB->uxPriority)
用于重置任务的就绪优先级,确保它按照新的优先级进行调度。
7. 恢复基优先级 (7):
- 在优先级继承解除后,任务的优先级恢复到它的基优先级
uxBasePriority
。 - 使用
traceTASK_PRIORITY_DISINHERIT
跟踪优先级恢复的过程。
8. 重置事件列表项的优先级值 (8):
- 更新事件列表项的值,以确保任务的事件列表项与其恢复后的基优先级保持一致。
9. 将任务重新添加到就绪列表 (9):
- 使用
prvAddTaskToReadyList(pxTCB)
函数将任务重新添加到系统的就绪列表中,以便调度器能够正确调度它。
10. 返回优先级继承解除的结果 (10):
- 如果任务成功恢复了基优先级,返回
pdTRUE
,表示优先级继承已被成功解除。
五、获取互斥信号量:
获取互斥信号量的函数同获取二值信号量和计数型信号量的函数相同,都是 xSemaphore Take()实际执行信号量获取的函数是xQueueGenericReceive(),获取互斥信号量的 过程也需要处理优先级继承的问题,函数xQueueGenericReceive()在文件queue..c中有定义,缩减后 的函数代码如下:
BaseType_t xQueueGenericReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait, const BaseType_t xJustPeeking )
{
BaseType_t xEntryTimeSet = pdFALSE;
TimeOut_t xTimeOut;
int8_t *pcOriginalReadPosition;
Queue_t * const pxQueue = ( Queue_t * ) xQueue;
configASSERT( pxQueue );
configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
#if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
{
configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
}
#endif
/* This function relaxes the coding standard somewhat to allow return
statements within the function itself. This is done in the interest
of execution time efficiency. */
for( ;; )
{
taskENTER_CRITICAL();
{
const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
/* Is there data in the queue now? To be running the calling task
must be the highest priority task wanting to access the queue. */
if( uxMessagesWaiting > ( UBaseType_t ) 0 ) (1)
{
/* Remember the read position in case the queue is only being
peeked. */
pcOriginalReadPosition = pxQueue->u.pcReadFrom;
prvCopyDataFromQueue( pxQueue, pvBuffer ); (2)
if( xJustPeeking == pdFALSE ) (3)
{
traceQUEUE_RECEIVE( pxQueue );
/* Actually removing data, not just peeking. */
pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;(4)
#if ( configUSE_MUTEXES == 1 ) (5)
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
{
/* Record the information required to implement
priority inheritance should it become necessary. */
pxQueue->pxMutexHolder = ( int8_t * ) (6) pvTaskIncrementMutexHeldCount(); /*lint !e961 Cast is not redundant as TaskHandle_t is a typedef. */
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif /* configUSE_MUTEXES */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )(7)
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
{
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else (8)
{
traceQUEUE_PEEK( pxQueue );
/* The data is not being removed, so reset the read
pointer. */
pxQueue->u.pcReadFrom = pcOriginalReadPosition;
/* The data is being left in the queue, so see if there are
any other tasks waiting for the data. */
if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE ) (9)
{
if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) ) != pdFALSE )
{
/* The task waiting has a higher priority than this task. */
queueYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL();
return pdPASS;
}
else (10)
{
if( xTicksToWait == ( TickType_t ) 0 )
{
/* The queue was empty and no block time is specified (or
the block time has expired) so leave now. */
taskEXIT_CRITICAL();
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else if( xEntryTimeSet == pdFALSE )
{
/* The queue was empty and a block time was specified so
configure the timeout structure. */
vTaskSetTimeOutState( &xTimeOut );
xEntryTimeSet = pdTRUE;
}
else
{
/* Entry time was already set. */
mtCOVERAGE_TEST_MARKER();
}
}
}
taskEXIT_CRITICAL();
/* Interrupts and other tasks can send to and receive from the queue
now the critical section has been exited. */
vTaskSuspendAll();
prvLockQueue( pxQueue );
/* Update the timeout state to see if it has expired yet. */
if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE ) (11)
{
if( prvIsQueueEmpty( pxQueue ) != pdFALSE ) (12)
{
traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );
#if ( configUSE_MUTEXES == 1 )
{
if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX ) (13)
{
taskENTER_CRITICAL();
{
vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );(14)
}
taskEXIT_CRITICAL();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
#endif
vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait ); (15)
prvUnlockQueue( pxQueue );
if( xTaskResumeAll() == pdFALSE )
{
portYIELD_WITHIN_API();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* Try again. */
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
}
}
else
{
prvUnlockQueue( pxQueue );
( void ) xTaskResumeAll();
if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
{
traceQUEUE_RECEIVE_FAILED( pxQueue );
return errQUEUE_EMPTY;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
}
}
1. 检查队列是否为空 (1):
- 在尝试从队列中获取数据之前,首先判断队列
uxMessagesWaiting
是否大于 0。如果队列不为空,则可以进行数据提取。
2. 提取数据 (2):
- 使用
prvCopyDataFromQueue()
函数从队列中拷贝数据到提供的缓冲区pvBuffer
。
3. 数据删除 (3):
- 一旦数据读取成功,需要将这个数据从队列中删除。
4. 更新消息计数 (4):
- 通过减少队列的消息计数器
uxMessagesWaiting
来完成数据的删除。
5. 互斥信号量特定处理 (5):
- 在互斥信号量的上下文中,这个函数会处理额外的逻辑。
6. 标记互斥信号量拥有者 (6):
- 如果获取互斥信号量成功,需要更新
pxMutexHolder
,将其指向当前任务的任务控制块。 - 通过调用
pvTaskIncrementMutexHeldCount()
实现此功能,该函数会将当前任务控制块中的uxMutexesHeld
加一,标识任务获取到一个互斥信号量。
7. 解除阻塞 (7):
- 提取数据后,检查是否有任务因等待此信号量而被阻塞。如果有阻塞任务,解除其阻塞状态。如果解除的任务优先级高于当前任务,还需要进行任务切换。
8. 出队操作 (8):
- 当从队列中出队时,通常不需要删除消息;消息仍然有效。
9. 处理有效消息 (9):
- 如果出队时没有删除消息,意味着该消息仍然有效。如果有任务因出队而被阻塞,解除其阻塞状态,优先考虑任务的优先级。
10. 处理队列为空的情况 (10):
- 如果队列为空,处理方式类似于任务级通用的入队函数
xQueueGenericSend()
。 - 如果阻塞时间为 0,直接返回
errQUEUE_EMPTY
表示队列为空。 - 如果设置了阻塞时间,则进行相应的处理。
11. 超时检查 (11):
- 检查超时状态。如果未超时,任务将添加到
xTasksWaitingToReceive
列表中以等待信号量。
12. 重试出队 (12):
- 再次检查队列是否为空,如果不为空,则尝试再次出队。
13.互斥信号量的特性 (13):
- 此函数用于获取互斥信号量,并处理相关的优先级继承问题。
14. 优先级继承处理 (14):
- 调用
vTaskPriorityInherit()
来处理优先级继承问题。如果当前任务无法获取互斥信号量(即该信号量已被其他任务占用),则会将持有信号量的任务的优先级提升到当前任务的优先级。 - 这意味着,如果
LowTask
的优先级低于HighTask
,则在HighTask
请求信号量时,LowTask
的优先级将被提升到 4(即与HighTask
相同)。
15. 任务状态调整 (15):
- 如果队列依然为空,将当前任务添加到
xTasksWaitingToReceive
列表中,等待信号量的释放。
六、互斥信号量操作设计
6.1 程序设计
学习使用互斥信号量,并且观察互斥信号量是否可以解决或者缓解优先级翻转。
本设计在FreeRTOS基础入门——FreeRTOS优先级翻转(十五)基础上修改的,除了任务函数以外其他的部分都相同。
6.2 任务函数:
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建互斥信号量
MutexSemaphore = xSemaphoreCreateMutex(); (1)
if(MutexSemaphore == NULL) //互斥信号量创建成功
printf("Sema Create Failed!\r\n");
//创建LOW任务
xTaskCreate((TaskFunction_t )low_task,
(const char* )"low_task",
(uint16_t )LOW_STK_SIZE,
(void* )NULL,
(UBaseType_t )LOW_TASK_PRIO,
(TaskHandle_t* )&LowTask_Handler);
//创建MIDDLE任务
xTaskCreate((TaskFunction_t )middle_task,
(const char* )"middle_task",
(uint16_t )MIDDLE_STK_SIZE,
(void* )NULL,
(UBaseType_t )MIDDLE_TASK_PRIO,
(TaskHandle_t* )&MiddleTask_Handler);
//创建HIGH任务
xTaskCreate((TaskFunction_t )high_task,
(const char* )"high_task",
(uint16_t )HIGH_STK_SIZE,
(void* )NULL,
(UBaseType_t )HIGH_TASK_PRIO,
(TaskHandle_t* )&HighTask_Handler);
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//low任务函数
void low_task(void *pvParameters)
{
static u32 times;
while(1)
{
xSemaphoreTake(MutexSemaphore, portMAX_DELAY); //死等
printf("lwo task running!\r\n");
//模拟低优先级任务占用信号量
for(times=0;times<2000000;times++)
{
delay_us(2);
//taskYIELD(); //任务切换
}
xSemaphoreGive(MutexSemaphore); //释放信号量
vTaskDelay(1000); //延时1s
}
}
//middle_task函数
void middle_task(void *pvParameters)
{
while(1)
{
printf("middle task running!\r\n");
vTaskDelay(1000); //延时10ms,也就是10个时钟节拍
}
}
//high_task函数
void high_task(void *pvParameters)
{
while(1)
{
printf("high task Pend Sem!\r\n");
xSemaphoreTake(MutexSemaphore, portMAX_DELAY); //死等
printf("high task running!\r\n");
xSemaphoreGive(MutexSemaphore); //释放信号量
vTaskDelay(1000); //延时10ms,也就是10个时钟节拍
}
}
1. 创建互斥信号量 (1):
- 调 用函数
xSemaphoreCreateMutex()
创建一个互斥信号量MutexSemaphore
。该信号量将用于保护共享资源,确保同一时间只能有一个任务访问该资源。
2. 任务 HighTask
获取互斥信号量 (2):
- 任务
HighTask
尝试获取互斥信号量MutexSemaphore
,以确保对共享资源的独占访问。
3. 释放互斥信号量 (3):
- 在完成对共享资源的访问后,
HighTask
必须释放互斥信号量,以便其他任务可以获取。
4. 任务 LowTask
获取互斥信号量 (4):
- 任务
LowTask
尝试获取互斥信号量MutexSemaphore
,并设置阻塞时间为portMAX_DELAY
,表示它会一直等待,直到信号量可用为止。
5. 长时间占用互斥信号量 (5):
- 在获取信号量后,任务
LowTask
模拟长时间占用互斥信号量的场景。可以在此处执行一些耗时操作。
6. 释放互斥信号量 (6):
LowTask
完成对共享资源的访问后,释放互斥信号量,以允许其他任务(如HighTask
)获得访问权限。
六、程序运行结果分析:
LCD ID:5510
middle task Running! (1)
low task Running! (2)
high task Pend Sem (3)
high task Running! (4)
middle task Running!
high task Pend Sem
high task Running!
middle task Running!
low task Running!
(1)、middle task任务运行
(2)、low task获得互斥信号量运行。
(3)、high task请求信号量,在这里会等待一段时间,等待low task任务释放互斥信号量。但是middle task不会运行。因为由于low task正在使用互斥信号量,所以low task任务优先级暂时提升到了与high task相同的优先级,这个优先级比任务middle task高,所以middle task任务不能再打断low task任务的运行了!
(4)、high task任务获得互斥信号量而运行。
从上面的分析可以看出互斥信号量有效的抑制了优先级翻转现象的发生。
📝大佬觉得本文有所裨益,不妨轻点一下👍给予鼓励吧!
❤️❤️❤️本人虽努力,但能力尚浅,若有不足之处,恳请各位大佬不吝赐教,您的批评指正将是我进步的动力!😊😊😊
💖💖💖若您认为此篇文章对您有所帮助,烦请点赞👍并收藏🌟,您的支持是我前行的最大动力!
🚀🚀🚀任务在默默中完成,价值在悄然间提升。让我们携手共进,一起加油,迎接更美好的未来!🌈🌈🌈