FreeRTOS基础入门——FreeRTOS互斥信号量(十六)

news2024/11/15 12:32:28

 个人名片:

🎓作者简介:嵌入式领域优质创作者
🌐个人主页:妄北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):

  • 通过检查 pxQueue->uxQueueType == queueQUEUE_IS_MUTEX 来判断当前操作的队列是否是互斥信号量。
  • 当 pxQueue->uxItemSize 为 0 时,表明队列项大小为 0,意味着这是一个互斥信号量,而不是常规队列。对于互斥信号量,pcHead 和 pcTail 被重定义为 uxQueueType 和 pxMutexHolder,因此可以通过宏定义的 queueQUEUE_IS_MUTEX 来确认它是互斥信号量。

优先级继承处理 (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任务获得互斥信号量而运行。

从上面的分析可以看出互斥信号量有效的抑制了优先级翻转现象的发生。
 

📝大佬觉得本文有所裨益,不妨轻点一下👍给予鼓励吧!

❤️❤️❤️本人虽努力,但能力尚浅,若有不足之处,恳请各位大佬不吝赐教,您的批评指正将是我进步的动力!😊😊😊

💖💖💖若您认为此篇文章对您有所帮助,烦请点赞👍并收藏🌟,您的支持是我前行的最大动力!

🚀🚀🚀任务在默默中完成,价值在悄然间提升。让我们携手共进,一起加油,迎接更美好的未来!🌈🌈🌈

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2122330.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

本地部署Llama 3.1大模型

Meta推出的Llama 3.1系列包括80亿、700亿、4050亿参数版本&#xff0c;上下文长度扩展至12.8万tokens&#xff0c;并增加了对八种语言的支持。 部署模型需要用到Ollama的一个工具&#xff0c;访问官方网站https://ollama.com 点击下载&#xff0c;选择下载你对应的操作系统下…

opencv图像透视处理

引言 在图像处理与计算机视觉领域&#xff0c;透视变换&#xff08;Perspective Transformation&#xff09;是一种重要的图像校正技术&#xff0c;它允许我们根据图像中已知的四个点&#xff08;通常是矩形的四个角&#xff09;和目标位置的四个点&#xff0c;将图像从一个视…

2024.9.10 作业

代码&#xff1a; /*******************************************/ 文件名&#xff1a;widget.h /*******************************************/ #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QLabel> #include <QTimeEdit> #includ…

opencv学习:信用卡卡号识别

该代码用于从信用卡图像中自动识别和提取数字信息。该系统将识别信用卡类型&#xff0c;并输出信用卡上的数字序列。 1.创建命令行参数 数字模板 信用卡 # 创建命令行参数解析器 ap argparse.ArgumentParser() # 添加命令行参数 -i/--image&#xff0c;指定输入图像路径 ap.…

破局DRG/DIP亏损,医院应该怎么做

DRG/DIP付费实施后&#xff0c;医院各临床科室可结合前期数据积累&#xff0c;根据DRG/DIP专科病组/病种四级手术占比与医疗收入占比之间的变化关系、建立DRG/DIP战略分布象限图&#xff0c;将病组分为优势病组&#xff08;病种&#xff09;、潜力病组&#xff08;病种&#xf…

线程(Thread)

目录 线程&#xff08;Thread&#xff09; 线程的创建方式 实现方式 Runnable和Callable的区别 线程的命名和优先级 线程的六种状态 线程的插队 线程的中断 线程的让出 守护线程 设置线程为守护线程 sleep()和wait()的区别 线程的同步synchronized锁 语法格式 实现…

在线动漫信息平台

你好呀&#xff0c;我是计算机学姐码农小野&#xff01;如果有相关需求&#xff0c;可以私信联系我。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;Spring Boot框架 工具&#xff1a;IDEA/Eclipse、Navicat、Maven 系统展示 首页 会员后台 管理员…

day-52 下一个排列

思路 从后向前遍历数组&#xff0c;把遍历过的元素加入一个有序链表&#xff0c;没变里一个元素判断链表中是否有元素大于当前遍历元素&#xff0c;如果有&#xff0c;把链表中大于当前遍历元素的元素集合中最小的那一个元素赋给当前元素&#xff0c;然后将链表中剩余元素依次赋…

建造者模式builder

此篇为学习笔记&#xff0c;原文链接 https://refactoringguru.cn/design-patterns/builder 能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象

JavaWeb【day14】--(SpingBoot原理)

SpingBoot原理 在前面十多天的课程当中&#xff0c;我们学习的都是web开发的技术使用&#xff0c;都是面向应用层面的&#xff0c;我们学会了怎么样去用。而我们今天所要学习的是web后端开发的最后一个篇章springboot原理篇&#xff0c;主要偏向于底层原理。 我们今天的课程安…

2-2 opencv实战进阶系列 多边形识别

目录 一、不说废话&#xff0c;先上现象 二、前言 三、思路讲解 step1&#xff1a;用阈值编辑器对图像进行处理。 step2&#xff1a;应用阈值进行二值化 step3&#xff1a;轮廓查找 step4&#xff1a; 显示文字 四、完整代码贴出 五、现象展示 六、结语 一、不说废话&…

在单向链表中找环

在单向链表中找环也是有多种办法&#xff0c;不过快慢双指针方法是其中最为简洁的方法之一&#xff0c;接下来介绍这种方法。 首先两个指针都指向链表的头部&#xff0c;令一个指针一次走一步&#xff0c;另一个指针一次走两步&#xff0c;如果它们相遇了&#xff0c;证明有环…

数据结构(7.2_1)——顺序查找

顺序查找&#xff0c;又叫"线性查找"&#xff0c;通常用于线性表&#xff08;或者顺序表和链表&#xff09;。 算法思想&#xff1a;从头到尾全部查找出来&#xff08;或者反过来也OK&#xff09; 顺序查找的实现 typedef struct {//查找表的数据结构(顺序表)Elem…

再遇“类和对象”

一、类的默认成员函数 默认成员函数就是用户没有显式实现&#xff0c;编译器会自动生成的成员函数称为默认成员函数。一个类&#xff0c;我们不写的情况下编译器会默认生成以下6个默认成员函数&#xff0c;需要注意的是这6个中最重要的是前4个&#xff0c;最后两个取地址重载不…

visio修改默认字体、颜色、形状格式、连接线格式

设计中取消勾选“将主题应用于新建的形状” 在开发工具中打开绘图资源管理器&#xff0c;并分别修改纯文本、连接线、主题的样式

文本转化为声音

在许多场景下需要将文本转化为MP3格式&#xff0c;本文将实现文本转化为声音&#xff0c;并且将声音保存为MP3格式。本文一朱自清的《春》为例&#xff0c;要实现阅读《春》并且转化为mp3格式的音频文件。 1 导入包 import pyttsx3 from docx import Document def read_word_…

ubuntu内核升级后的问题修复

文章目录 需求当前环境禁止内核更新安装内核修复/usr/include/dlocate 测试 需求 升级后的常见问题 驱动程序不兼容: 新内核版本可能导致某些硬件驱动程序不再兼容&#xff0c;尤其是专有驱动程序或第三方驱动程序。启动问题:内核更新可能导致启动问题&#xff0c;例如无法启动…

《创新电力巡检,机器人铸就安全高效未来》

近年来&#xff0c;我国电力建设投资额持续波动增长&#xff0c;至2023年底&#xff0c;全国电力工程投资总额高达14950亿元&#xff0c;同比增长22%。其中&#xff0c;电源工程建设和电网工程建设投资均达到新的高度。在这一背景下&#xff0c;电力行业对巡检工作的要求也日益…

苹果iOS/ iPadOS18 RC 版、17.7 RC版更新发布

iPhone 16 / Pro 系列新机发布后&#xff0c;苹果一同推出了 iOS 18 和 iPadOS 18 的 RC 版本&#xff0c;iOS 18 RC 的内部版本号为22A3354&#xff0c;本次更新距离上次发布 Beta/RC 间隔 12 天。 在 iOS 18 中&#xff0c;苹果给我们带来了 Apple Intelligence&#xff0c;这…

springboot高校兼职平台-计算机毕业设计源码65602

摘要 基于SpringBoot框架的高校兼职平台专注于为普通用户提供便捷的兼职信息服务。该平台包括普通用户功能、系统内容浏览、通知公告查看与论坛交流互动、兼职信息搜索与申请、个人中心管理和管理员权限管理等模块。利用SpringBoot框架实现了模块化开发和依赖注入&#xff0c;结…