FreeRTOS:3.信号量

FreeRTOS信号量

参考链接:FreeRTOS-信号量详解_freertos信号量-CSDN博客

目录

  • FreeRTOS信号量
  • 一、信号量是什么
  • 二、 FreeRTOS信号量
    • 1、二值信号量
      • 1、获取信号量
      • 2、释放信号量
    • 2、计数信号量
    • 3、互斥信号量
      • 1、优先级反转
      • 2、优先级继承
      • 3、源码解析
        • 1、互斥量创建
        • 2、获取互斥量
        • 3、释放互斥量
    • 4、递归互斥信号量
        • 1、互斥信号量的缺陷
        • 2、递归互斥信号量解析
          • 1、创建递归互斥信号量
          • 2、释放递归互斥信号量
          • 3、获取递归互斥信号量

一、信号量是什么

信号量(Semaphore)是一种实现任务间通信的机制,可以实现任务之间同步或临界资源的互斥访问,实现的方式主要就是依靠队列(信号量是特殊的队列)的任务阻塞机制。

既然队列也可以实现同步与互斥那为什么还要信号量?
信号量相比队列更节省空间,因为实现同步与互斥不需要传递数据,所以信号量没有队列后面的环形存储区,信号量主要就是依靠计数值uxMessagesWaiting(在队列中表示队列现有消息个数,在信号量中表示有效信号量个数)。

uxMessagesWaiting作为复用在信号量中表示资源的数量,所有获取信号量的任务都会将该整数减一,当该整数值为零时,则此时想要获取的任务则会进入阻塞态,释放信号量的任务都会将该整数加一,不过当该整数值为最大值时(最大值要看你是什么信号量),则此时想要释信号量的任务则并不会进入阻塞态,直接返回释放信号量失败。

二、 FreeRTOS信号量

1、二值信号量

所谓二值信号量其实就是一个队列长度为1,没有数据存储区的队列,而二值则表示计数值uxMessagesWaiting只有0和1两种状态(就是队列空与队列满两种情况),uxMessagesWaiting在队列中表示队列中现有消息数量,而在信号量中则表示信号量的数量。

uxMessagesWaiting为0表示:信号量资源被获取了;uxMessagesWaiting为1表示:信号量资源被释放了。把这种只有 0 和 1 两种情况的信号量称之为二值信号量。
由于二值信号量就是特殊的队列,其实它的运转机制就是利用了队列的阻塞机制,从而达到实现任务之间的同步与互斥(有优先级反转的缺陷)。

二值信号量用于同步:

在串口接收中,我们并不知道什么时候有数据发送过来(等数据过来标记一次),还有一个处理串口接收到的数据,在任务系统中不可能时时刻刻去判断是否有串口有数据过来(判断标志位),所以在这种情况下使用二值信号量是很好的办法,当没有数据到来的时候,任务就进入阻塞态,不参与任务的调度,等到数据到来了,释放一个二值信号量,任务就立即从阻塞态中解除,进入就绪态,然后运行的时候处理数据,这样子系统的资源就会很好的被利用起来。
二值信号量一般不用于任务之间的互斥,因为它有优先级反转的缺点,解决方式就是使用互斥信号量(具有优先级继承的机制能减少优先级反转的影响)

在这里插入图片描述

在任务与在中断中使用的API函数的区别,其实函数的主体代码是一模一样的,就是在中断中:
1.不能允许阻塞
2.不能立马发起任务调度

二值信号量的创建也是调用xQueueGenericCreate函数进行队列的创建,创建的参数:队列长度为1,队列项(消息)为0,,也就是缓冲区大小为0,队列类型为二值信号量

#define xSemaphoreCreateBinary() xQueueGenericCreate( ( UBaseType_t ) 1, semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )

信号量的个数(资源的个数)用队列结构体中的uxMessagesWaiting表示(uxMessagesWaiting在队列中表示队列中现有消息数量,而在信号量中则表示信号量的数量

1、获取信号量

获取信号量的函数:

BaseType_t xQueueSemaphoreTake( QueueHandle_t xQueue,
                                TickType_t xTicksToWait )
{
    BaseType_t xEntryTimeSet = pdFALSE;
    TimeOut_t xTimeOut;
    Queue_t * const pxQueue = xQueue;

    #if ( configUSE_MUTEXES == 1 )
        BaseType_t xInheritanceOccurred = pdFALSE;
    #endif

    /* Check the queue pointer is not NULL. */
    configASSERT( ( pxQueue ) );

    /* 检查这是否真的是信号量,在这种情况下,项目大小将为0 */
    configASSERT( pxQueue->uxItemSize == 0 );

    /*如果调度程序已挂起,则无法阻塞。 */
    #if ( ( INCLUDE_xTaskGetSchedulerState == 1 ) || ( configUSE_TIMERS == 1 ) )
        {
            configASSERT( !( ( xTaskGetSchedulerState() == taskSCHEDULER_SUSPENDED ) && ( xTicksToWait != 0 ) ) );
        }
    #endif

    for( ; ; )
    {
        /* 关中断 */
        taskENTER_CRITICAL();
        {
            /* 信号量是消息大小为0的特殊队列
		     uxMessagesWaiting在信号量中表示资源数 */
            const UBaseType_t uxSemaphoreCount = pxQueue->uxMessagesWaiting;

			/* 判断信号量是否有资源 */
            if( uxSemaphoreCount > ( UBaseType_t ) 0 )
            {
                traceQUEUE_RECEIVE( pxQueue );

                /* 信号量资源数减一 */
                pxQueue->uxMessagesWaiting = uxSemaphoreCount - ( UBaseType_t ) 1;
            
                #if ( configUSE_MUTEXES == 1 )
                    { 
                        /* 判断队列的类型是否为互斥信号量 */
                        if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                        {
					     /* 设置互斥信号量的持有者,并更新该任务拥有互斥信号量的数量                 */
                            pxQueue->u.xSemaphore.xMutexHolder = pvTaskIncrementMutexHeldCount();
                        }
                    }
                #endif /* configUSE_MUTEXES */
				
				/* 判断信号量的释放阻塞任务列表中是否有任务 */
                if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
                {
                    /* 将阻塞任务从信号量释放阻塞任务列表中移除 */
                    if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
                    {
                        queueYIELD_IF_USING_PREEMPTION();
                    }
                }
                /* 开中断 */
                taskEXIT_CRITICAL();
                return pdPASS;
            }
            else
            {
                /* 若信号量无资源不能获取信号量,则需要判断任务是否需要阻塞 */
                if( xTicksToWait == ( TickType_t ) 0 )
                {
                    /* 要发生继承,必须存在初始超时,并且调整后的超时不能变为 0,因为如果为 0,函数就会退出。*/
                    #if ( configUSE_MUTEXES == 1 )
                        {
                            configASSERT( xInheritanceOccurred == pdFALSE );
                        }
                    #endif /* configUSE_MUTEXES */

                    /* 信号灯计数为 0,未指定阻止时间(或阻止时间已过期),因此立即退出。 */
                    taskEXIT_CRITICAL();
                    traceQUEUE_RECEIVE_FAILED( pxQueue );
                    return errQUEUE_EMPTY;
                }
                else if( xEntryTimeSet == pdFALSE )
                {
                    /* 信号量无资源,并指定了阻塞时间(则任务需要阻塞),
					所以需要记录下此时系统节拍计数器的值和溢出次数
					用于下面对阻塞时间进行补偿 */
                    vTaskInternalSetTimeOutState( &xTimeOut );
                    xEntryTimeSet = pdTRUE;
                }
            }
        }
		/* 开中断 */
        taskEXIT_CRITICAL();

        /* 开中断:此时中断和其他任务可以向信号量获取和获取信号量。 */

		/* 挂起任务调度器 */
        vTaskSuspendAll();
		/* 队列上锁 */
        prvLockQueue( pxQueue );

        /* 判断阻塞时间补偿后,是否还需要阻塞 */
        if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
        {
			/* 阻塞时间补偿后,还需要进行阻塞(未超时)
              再次确认信号量是否为空(无资源) */
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {
                traceBLOCKING_ON_QUEUE_RECEIVE( pxQueue );

                #if ( configUSE_MUTEXES == 1 )
                    {
                        /* 判断队列类型是否为互斥信号量 */
                        if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
                        {
                            taskENTER_CRITICAL();
                            {
                                /* 进行优先级继承,互斥信号量用于解决优先级翻转的问题*/
                                xInheritanceOccurred = xTaskPriorityInherit( pxQueue->u.xSemaphore.xMutexHolder );
                            }
                            taskEXIT_CRITICAL();
                        }
                    }
                #endif /* if ( configUSE_MUTEXES == 1 ) */
			    /* 将任务添加到队列读取阻塞任务列表中进行阻塞 */
                vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
			    /* 解锁队列 */
                prvUnlockQueue( pxQueue );
				/* 恢复任务调度器,判断是否需要的进行任务切换 */
                if( xTaskResumeAll() == pdFALSE )
                {
                    portYIELD_WITHIN_API();
                }
            }
            else
            {
				/* 解锁队列 	*/
                prvUnlockQueue( pxQueue );
				/* 恢复任务调度器 */
                ( void ) xTaskResumeAll();
            }
        }
        else
        {
            /* 已超时,解锁队列 */
            prvUnlockQueue( pxQueue );
			/* 恢复任务调度器 */
            ( void ) xTaskResumeAll();

			/* 判断信号量是否为空 */
            if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
            {
                #if ( configUSE_MUTEXES == 1 ) // 互斥量特有的判断
                    {
                        /* 判断任务是否发生优先级继承 */
                        if( xInheritanceOccurred != pdFALSE )
                        {
                            taskENTER_CRITICAL();
                            {
                                UBaseType_t uxHighestWaitingPriority;

                                /* 互斥锁上的此任务阻塞导致另一个任务继承此任务的优先级。 现在此任务已超时,
                                优先级应再次取消继承,但只能低至等待相同互斥锁的下一个最高优先级任务。 */
                                uxHighestWaitingPriority = prvGetDisinheritPriorityAfterTimeout( pxQueue );
                                vTaskPriorityDisinheritAfterTimeout( pxQueue->u.xSemaphore.xMutexHolder, uxHighestWaitingPriority );
                            }
                            taskEXIT_CRITICAL();
                        }
                    }
                #endif /* configUSE_MUTEXES */

                traceQUEUE_RECEIVE_FAILED( pxQueue );
                return errQUEUE_EMPTY;
            }
        }
    } /*lint -restore */
}
/*-----------------------------------------------------------*/			

总结:
获取信号量其实与队列的出队基本差不多的

  1. 获取信号量资源数uxMessagesWaiting(在二值信号量中只能是0/1,代表信号量为空或满)
  2. 判断信号量是否有资源uxSemaphoreCount >0 (有资源才能被获取)
  3. 如果信号量有资源,uxSemaphoreCount 计数值减一
  4. 如果信号量无资源

(1).如果设置的不等待,等待时间为0,则即可返回获取信号量失败的错误

(2).如果设置的等待时间为0-portMAX_DELAY,则判断任务是否超时,若未超时,即刻阻塞。

(3).如果设置的等待时间portMAX_DELAY,则任务一直阻塞,直到信号量有资源才会被唤醒。

2、释放信号量

xSemaphoreTake函数本质上调用的是xQueueGenericSend函数

#define xSemaphoreGive( xSemaphore )		xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )

相当于xSemaphoreGive()向xQueueGenericSend()函数中传入的参数为

  1. xSemaphore:要释放的信号量

  2. NULL:信号量不需要传递数据

  3. semGIVE_BLOCK_TIME:释放信号量不能阻塞,(信号量计数值达最大值,在释放则直接返回错误)

  4. queueSEND_TO_BACK:向队尾入队(这个不重要,因为信号量操作并不会拷贝数据)

xQueueGenericSend()函数在讲解队列的文章已经讲解过,可以看上一篇文章,不再赘述

总结:
1.判断信号量计数值是否达最大值1,计数值要等于0才能释放信号
2.若满足信号量释放条件,则计数值(uxMessagesWaiting )加1,并不会拷贝数据,此时计数值为1表示信号量有资源,如果有因为获取信号量而阻塞的任务,则需要将其唤醒(从xTasksWaitingToReceive列表中移除,将任务挂入就绪列表)。
3.若不满足信号量释放条件(计数值达最大值,二值信号量就是1),因为xSemaphoreGive()带入的参数为阻塞时间为0,则释放信号量不会阻塞,直接返回释放信号量失败。

2、计数信号量

计数值信号量也与二值信号量一样也是特殊的队列,二值信号量是长度为1的队列,而计数值信号量是长度大于0的队列,他们本质的区别就是应用场景不同二值信号量常用于同步,计数值信号量常用于事件计数、资源管理,其实如果限定计数值信号量计数值最大值只能为1则就等同于二值信号量。

在这里插入图片描述

由上图可知,计数值信号量与二值信号量相比,除了创建函数不同其他函数都相同,所以主要将计数值信号量的创建函数。

xSemaphoreCreateCounting本质上还是通过队列创建函数xQueueGenericCreate,创建一个长度为uxMaxCount,消息大小为0的特殊队列即计数值信号量,当然创建成功后,将计数值初值赋值成uxInitialCount,就是这么简单,然后其他获取、释放信号量等操作与二值信号量一模一样。

3、互斥信号量

前面的二值信号量/计数值信号量其实也可以实现互斥,但二值信号量存在优先级反转的缺点,而解决方法就是优先级继承,优先级继承是互斥信号量的特性,则互斥信号量的本质就是具有优先级继承的二值信号量。

1、优先级反转

什么叫做优先级反转,简单来说就是低优先级的任务霸占CPU资源,导致高优先级任务无法运行的情况:低优先级的任务持有一个被高优先级任务所需要的共享资源(低优先级给共享资源上锁,高优先级需要等待低优先级解锁共享资源,而在这期间正好一个中等优先级的任务打断低优先级任务去执行,则低优先级任务迟迟不能运行则不能给共享资源解锁,导致高优先级要一直等待(阻塞),而中等优先级的任务一直运行。
导致这种严重优先级反转的问题的根本原因在于持有锁的低优先级任务因为优先级低,而得不到执行,得不到执行的话,就无法解锁,无法解锁就导致高优先级的任务获取锁会失败,从而导致高优先级任务一直在阻塞状态。

优先级反转示意:

假设有三个任务A,B,C,优先级分别为低、中、高优先级,假设任务A先运行(中、高优先级的任务在阻塞)调用上锁函数(获取二值信号量,使用某个共享资源),然后任务B解除阻塞抢占任务A,任务B运行,最后任务C解除阻塞抢占任务B,任务C运行,任务C想要去获取锁(获取二值信号量,使用某个共享资源),问题是任务A还没有释放这个锁(释放二值信号量),则任务C想要获取这个锁就会失败进入阻塞状态,任务C进入阻塞态后,任务B中等优先级的任务一直执行,这样一来任务A就无法运行(无法去解锁),则任务C(优先级最高的任务会一直阻塞),则最终导致明明任务C的优先级最高反而得不到执行,而中等优先级的任务B一直执行,则就是所谓的优先级反转,这种情况在实时操作系统中是绝对不允许的。
在这里插入图片描述

所以解决方式就是优先级继承,不是说低优先级任务无法执行嘛,那我就在高优先级任务进入阻塞之前将低优先级任务的优先级提升至与高优先级一致,这样等高优先级任务进入阻塞之后,低优先级任务就能继承高优先级任务的优先级,这样低优先级任务就能尽快执行(从而解锁,让高优先级能够获取锁)。

2、优先级继承

优先级继承:暂时提高某个占有某种资源的低优先级任务的优先级,使之与在所有等待该资源的任务中优先级最高那个任务的优先级相等,而当这个低优先级任务执行完毕释放该资源时,优先级重新回到初始设定值。

在这里插入图片描述

当高优先级任务获取锁进入阻塞之前,会将低优先级任务的优先级提升至与高优先级任务一样,则等高优先级任务C进入阻塞之后,则低优先级任务A继承任务C的优先级,则任务A立马执行(任务B没机会执行),则任务A继续执行就能尽快开锁(释放信号量),这样就能极大的减少优先级反转带来的影响。

这里为什么说是减少优先级反转带来的影响,而不是消除优先级反转?

因为优先级反转概念是指一个低优先级的任务持有一个被高优先级任务所需要的共享资源,也就是说当因为任务A上锁之后,然后任务C来获取锁失败而进入阻塞态,在高优先级任务进入阻塞态的时候已经是优先级反转了,而优先级继承只是让任务A尽快执行(尽快解锁),让高优先级任务C尽快解除阻塞态,所以优先级继承只能是缩短优先级反转的时间。

3、源码解析

互斥信号量相当于是一个具有优先级继承机制的二值信号量,所以互斥信号量就是为了降低优先级反转所带来的影响。一般用于保护共享临界资源,从而实现独占式访问,而可以降低优先级反转带来的影响。

比如:独占使用串口,不可能两个任务同时向串口发送、接收数据,此时就可以使用互斥量来互斥的使用串口(另一个任务必须等待当前正在使用传递任务使用完串口才能使用)

  • 注意:
    优先级继承只是减轻优先级反转的影响
  • 互斥信号量并不能用于中断中,因为互斥信号量具有优先级继承的机制,而中断不是任务,没有任务优先级;另外,中断服务函数中不能因为等待互斥量而进入阻塞态。

从之前消息队列的解析中可知,以队列出队为例,如果队列为空,那么在中断中尝试出队会直接返回出队错误,而不会陷入阻塞。

在这里插入图片描述

获取、释放信号量的函数与二值信号量是一样的但是函数里面有关于互斥信号量的条件编译。

在任务结构体TCB中,如果配置了使用互斥量的宏,那么TCB会多两个成员,如下:

#if ( configUSE_MUTEXES == 1 )
		UBaseType_t		uxBasePriority;		// 任务的基础优先级,用于记录优先级继承前的任务优先级
		UBaseType_t		uxMutexesHeld;	// 持有互斥量数量
#endif
1、互斥量创建
QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
	{
	QueueHandle_t xNewQueue;
	const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;

		xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
		prvInitialiseMutex( ( Queue_t * ) xNewQueue );

		return xNewQueue;
	}

static void prvInitialiseMutex( Queue_t *pxNewQueue )
	{
		if( pxNewQueue != NULL )
		{	
			pxNewQueue->u.xSemaphore.xMutexHolder = NULL;//互斥量的持有者初始化为空
			pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;//初始化队列类型

			/* 如果是递归互斥量,互斥信号量的资源数初始化为0 */
			pxNewQueue->u.xSemaphore.uxRecursiveCallCount = 0;

			/* 释放一次信号量,新建的互斥信号量是有资源的 */
			( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
		}
	}

**其实xQueueCreateMutex()函数里面最终调用的还是队列创建函数xQueueGenericCreate(),且传入的参数与二值信号量一样,同样为队列长度为1的特殊队列,与二值信号量不同是里面还调用了prvInitialiseMutex函数。**在创建互斥信号量时候会释放一次信号量,表示一开始就有资源,而二值信号量则不会释放。

优先级继承发生在获取互斥量,解除优先级继承发生在释放互斥量。

2、获取互斥量

获取信号量的函数xSemaphoreTake实际是调用了xQueueSemaphoreTake,xQueueSemaphoreTake函数在二值信号量获取那里讲解过,不再赘述。只不过在函数内部根据判断当前是否使用互斥量去进行额外操作。

优先级继承发生在高优先级任务进入阻塞之前,调用了xTaskPriorityInherit()函数进行优先级继承。

xTaskPriorityInherit函数的主要内容:

#if ( configUSE_MUTEXES == 1 )

	BaseType_t xTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
	{
		TCB_t * const pxMutexHolderTCB = pxMutexHolder;
		BaseType_t xReturn = pdFALSE;

		if( pxMutexHolder != NULL ) // 判断互斥量是否被持有
		{	// 如果互斥锁持有者的当前优先级低于尝试获取互斥锁的优先级,那么发生优先级继承
			if( pxMutexHolderTCB->uxPriority < pxCurrentTCB->uxPriority )
			{
				if( ( listGET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
				{
					listSET_LIST_ITEM_VALUE( &( pxMutexHolderTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority );
				}
				// 如果正在修改的任务处于就绪态,则需要将其移动到新列表中
				if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxMutexHolderTCB->uxPriority ] ), &( pxMutexHolderTCB->xStateListItem ) ) != pdFALSE )
				{	// 从原来的就绪链表中移除
					if( uxListRemove( &( pxMutexHolderTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
					{	// 更新位图
						portRESET_READY_PRIORITY( pxMutexHolderTCB->uxPriority, uxTopReadyPriority );
					}
					// 优先级继承
					pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
					// 移动到新的就绪链表中
					prvAddTaskToReadyList( pxMutexHolderTCB );
				}
				else
				{	// 修改的任务不处于就绪态,那么只需要修改其优先级
					pxMutexHolderTCB->uxPriority = pxCurrentTCB->uxPriority;
				}

				/* Inheritance occurred. */
				xReturn = pdTRUE;
			}
			else
			{	// 如果持有互斥量的任务的当前优先级高于当前任务的优先级,但基础优先级低于当前任务的优先级,说明持有互斥量的任务已经发生了优先级继承。
				if( pxMutexHolderTCB->uxBasePriority < pxCurrentTCB->uxPriority )
				{
					xReturn = pdTRUE;
				}
			}
		}
		return xReturn;
	}
#endif /* configUSE_MUTEXES */

1. 判断互斥信号量是否被持有,然后就是判断如果互斥锁持有者的优先级低于尝试获取互斥锁的任务的优先级(高优先级任务),才要发送优先级继承,因为互斥锁持有者已经是高优先级任务了,就不需要发生优先级继承了

2. 所谓继承优先级,就是该任务的优先级修改成与高优先级任务一样,然后将任务添加到优先级对应的就绪列表中(这样任务就可以以新的优先级参与调度)

3、释放互斥量

解除优先级继承发生了释放互斥量的地方,也就是xSemaphoreGive函数,本质上属于队列的入队函数,在前面消息队列讲解中有源码分析。

解除优先级继承藏在prvCopyDataToQueue()函数中,调用xTaskPriorityDisinherit()函数解除优先级继承。

// prvCopyDataToQueue函数
#if ( configUSE_MUTEXES == 1 )
		{
			if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
			{
				/* The mutex is no longer being held. */
				xReturn = xTaskPriorityDisinherit( pxQueue->u.xSemaphore.xMutexHolder );
				pxQueue->u.xSemaphore.xMutexHolder = NULL;
			}
		}
#endif /* configUSE_MUTEXES */

BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
	{
		TCB_t * const pxTCB = pxMutexHolder;
		BaseType_t xReturn = pdFALSE;

		if( pxMutexHolder != NULL ) // 互斥量持有者不为空
		{
			configASSERT( pxTCB == pxCurrentTCB );
			configASSERT( pxTCB->uxMutexesHeld );
			// 该任务持有互斥量数量-1
			( pxTCB->uxMutexesHeld )--;
			// 互斥锁的持有者是否继承了另一个任务(高优先级任务)的优先级
			if( pxTCB->uxPriority != pxTCB->uxBasePriority )
			{
				/* 仅在没有其他互斥锁的情况下取消继承 */
				if( pxTCB->uxMutexesHeld == ( UBaseType_t ) 0 )
				{	// 将该任务从就绪链表中移除
					if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
					{	// 更新位图
						taskRESET_READY_PRIORITY( pxTCB->uxPriority );
					}
					
					traceTASK_PRIORITY_DISINHERIT( pxTCB, pxTCB->uxBasePriority );
					pxTCB->uxPriority = pxTCB->uxBasePriority; // 恢复成基础优先级

					listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority ); 
					// 将任务插入到新的就绪链表
					prvAddTaskToReadyList( pxTCB );

					xReturn = pdTRUE;
				}
			}
		}
		return xReturn;
	}

总结:

所谓优先级继承,其实就是低优先级任务继承高优先级任务的优先级
发生优先级继承:在高优先级任务获取信号量失败(低优先级任务已经持有信号量),进入阻塞态之前,将持有信号量的低优先级任务的优先级提升

解除优先级继承:在持有信号量的低优先级任务释放信号量的时候,将自己的优先级恢复到初始值(因为已经释放了信号量,完成了任务)

而改变任务优先级的本质就是任务改变任务所挂入的优先级对应的就绪链表。

所以互斥信号量与二值信号量的区别在于:

  1. 互斥信号量创建时就有资源(因为是专门针对互斥的),不需要手动释放一次信号量,而二值信号量则没有。
  2. 互斥信号量具有优先级继承的机制,能减少优先级反转带来的影响。

4、递归互斥信号量

1、互斥信号量的缺陷

1.并没有实现谁持有锁就由谁释放
也就是说,比如任务A想要去独占的使用串口资源(上锁),但是有一个任务不讲武德直接还没等任务A解锁(释放信号量),就偷偷去解锁,则串口资源就被失去保护,则任何任务都可以去使用串口(则就会打印的乱七八糟)。

2.不能递归上锁,会导致死锁

而递归互斥信号量则完美实现了这两点,而且递归互斥信号量也是基于互斥信号量的,所以一样具有优先级继承的机制。

2、递归互斥信号量解析

在这里插入图片描述

1、创建递归互斥信号量

创建递归互斥信号量与互斥信号量是一模一样的:xQueueCreateMutex函数

2、释放递归互斥信号量

xSemaphoreGiveRecursive函数,本质上是调用xQueueGiveMutexRecursive函数

#if ( configUSE_RECURSIVE_MUTEXES == 1 )

	BaseType_t xQueueGiveMutexRecursive( QueueHandle_t xMutex )
	{
		BaseType_t xReturn;
		Queue_t * const pxMutex = ( Queue_t * ) xMutex;

		configASSERT( pxMutex );
		//	判断当前递归互斥信号量的获取者是否为持有者
		if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() )
		{
			traceGIVE_MUTEX_RECURSIVE( pxMutex );
			// 更新递归互斥信号量的被递归获取计数器
			( pxMutex->u.xSemaphore.uxRecursiveCallCount )--;

			/* 判断递归互斥信号量的被获取次数是否为0,被获取次数为0,才真正释放 */
			if( pxMutex->u.xSemaphore.uxRecursiveCallCount == ( UBaseType_t ) 0 )
			{
				// 释放信号量
				( void ) xQueueGenericSend( pxMutex, NULL, queueMUTEX_GIVE_BLOCK_TIME, queueSEND_TO_BACK );
			}

			xReturn = pdPASS;
		}
		else
		{	// 无法提供互斥锁,因为调用任务不是持有者
			xReturn = pdFAIL;

			traceGIVE_MUTEX_RECURSIVE_FAILED( pxMutex );
		}

		return xReturn;
	}

#endif /* configUSE_RECURSIVE_MUTEXES */

1.想要释放递归互斥信号量必须是信号量的持有者(这样就实现了谁上锁就由谁来解锁)

2.让uxRecursiveCallCount减一,因为uxRecursiveCallCount表示该信号量上锁了多少次,当uxRecursiveCallCount等于0就说明递归互斥信号量释放了最后一次信号量,则就要真正去执行一次释放。

3、获取递归互斥信号量

xSemaphoreTakeRecursive本质上是调用xQueueTakeMutexRecursive函数

#if ( configUSE_RECURSIVE_MUTEXES == 1 )

	BaseType_t xQueueTakeMutexRecursive( QueueHandle_t xMutex, TickType_t xTicksToWait )
	{
		BaseType_t xReturn;
		Queue_t * const pxMutex = ( Queue_t * ) xMutex;

		configASSERT( pxMutex );

		traceTAKE_MUTEX_RECURSIVE( pxMutex );
		// 判断当前递归互斥量的获取者是否为持有者
		if( pxMutex->u.xSemaphore.xMutexHolder == xTaskGetCurrentTaskHandle() )
		{
			// 递归互斥量的被递归获取计数值加一,获取成功
			( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
			xReturn = pdPASS;
		}
		else // 当前递归互斥信号量的获取者不是持有者
		{
			// 尝试获取信号量,根据设定的超时时间发生不同的情况,可能直接返回,也可能发生阻塞
			xReturn = xQueueSemaphoreTake( pxMutex, xTicksToWait );
			// 判断是否成功
			if( xReturn != pdFAIL )
			{
				( pxMutex->u.xSemaphore.uxRecursiveCallCount )++;
			}
			else
			{
				traceTAKE_MUTEX_RECURSIVE_FAILED( pxMutex );
			}
		}

		return xReturn;
	}

#endif /* configUSE_RECURSIVE_MUTEXES */

由谁上锁就由谁解锁,递归上锁功能是依靠xMutexHolder(记录互斥量的持有者)、uxRecursiveCallCount(递归上锁深度)两个变量实现的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FFmpeg常用结构体、关键函数、ffplay.c分析

一、常用结构体&#xff1a; 1、AVFormatContext结构体&#xff1a; AVFormatContext是一个贯穿全局的数据结构&#xff0c;很多函数都要用它作为参数。FFmpeg代码中对这个数据结构的注释是format I/O context&#xff0c;此结构包含了一个视频流的格式内容。其中存有AVIputFor…

电脑使用笔记

1.电脑亮度调节 亮度&#xff1a;50 对比度&#xff1a;45 暗部平衡&#xff1a;40

毕业设计注意事项(嘉庚学院2024届更新中)

1.开题 根据学院发的开题报告模板完成&#xff0c;其中大纲部分可参考资料。 2.毕设以及实习 2.1 毕设 根据资料中的毕设评价标准&#xff0c;对照工作量 2.2 实习材料提交 2.2.1 校外实习 实习前&#xff1a;根据学院要求&#xff0c;填写好实习承诺书&#xff0c;实习单位…

【数据结构初阶】时间复杂度和空间复杂度详解

今天我们来详细讲讲时间复杂度和空间复杂度&#xff0c;途中如果有不懂的地方可翻阅我之前文章。 个人主页&#xff1a;小八哥向前冲~-CSDN博客 数据结构专栏&#xff1a;数据结构【c语言版】_小八哥向前冲~的博客-CSDN博客 c语言专栏&#xff1a;c语言_小八哥向前冲~的博客-CS…

网络服务SSH-远程访问及控制

一.SSH远程管理 1.SSH介绍 SSH&#xff08;Secure Shell&#xff09;是一种安全通道协议&#xff0c;最早是由芬兰的一家公司开发出来&#xff0c;并且在IETF &#xff08;Internet Engineering Task Force&#xff09;的网络草案基础上制定而成的标准协议。主要用来实现字符…

合规基线:让安全大检查更顺利

前言 说起安全检查&#xff0c;安全从业人员可能都非常熟悉“安全标准”概念。所有企事业单位网络安全建设都需要满足来自于国家或监管单位的安全标准&#xff0c;如等保2.0、CIS安全标准等。安全标准&#xff0c;还有一个叫法就是“安全基线”。字典上对“基线”的解释是&…

【PostgreSQL】pg触发器介绍

注: 本文为云贝教育 刘峰 原创&#xff0c;请尊重知识产权&#xff0c;转发请注明出处&#xff0c;不接受任何抄袭、演绎和未经注明出处的转载。 触发器是在对指定表执行指定更改操作&#xff08;SQL INSERT、UPDATE、DELETE 或 TRUNCATE 语句&#xff09;时自动运行的一组操作…

Java全栈开发前端+后端(全栈工程师进阶之路)-环境搭建

在课程开始前我们要配置好我们的开发环境&#xff0c;这里我的电脑太乱了&#xff0c;我使用vm虚拟机进行搭建开发环境&#xff0c;如果有需要环境的或者安装包&#xff0c;可以私信我。 那我们开始 首先我们安装数据库 这里我们使用小皮面板 小皮面板(phpstudy) - 让天下没…

面向表格数据的大模型推理

现实世界中存在大量未利用的先验知识和未标记数据。而在医疗等高价值领域&#xff0c;获取足够标记数据训练机器学习模型尤其困难&#xff0c;这限制了传统监督学习算法的应用。尽管深度学习方法在其他领域取得了显著进展&#xff0c;但在表格数据分类上仍未能超越传统的机器学…

WEB攻防-PHP特性-函数缺陷对比

目录 和 MD5函数 intval ​strpos in_array preg_match str_replace 和 使用 时&#xff0c;如果两个比较的操作数类型不同&#xff0c;PHP 会尝试将它们转换为相同的类型&#xff0c;然后再进行比较。 使用 进行比较时&#xff0c;不仅比较值&#xff0c;还比较变量…

在Linux操作系统中关于磁盘(硬盘)管理的操作

电脑中数据存储设备&#xff1a;硬盘&#xff08;实现数据的持久化存储&#xff09;&#xff0c;内存 在Linux操作系统中一切皆文件的思想&#xff0c;所有的设备在Linux操作系统中都是通过文件来标识的&#xff0c;所以每一个硬盘都对应一个块设备文件。 在Linux操作系统中所…

虚函数表与虚函数表指针

虚函数表与虚函数表是用来实现多态的&#xff0c;每一个类只有一个虚函数表 静态多态&#xff1a;函数重载&#xff08;编译期确定&#xff09; 动态多态&#xff1a;虚函数&#xff08;运行期确定&#xff09; 虚函数表的创建时机&#xff1a; 生成时间&#xff1a; 编译期…

C++——map和set的基础应用

目录 1.关联式容器 2.键值对 3.树型结构的关联式容器 4.set&#xff08;key模型&#xff09; 1.set性质 2.注意 3.set的使用 1.创建对象 2.插入 3.删除 4.查找 4.map&#xff08;key value模型&#xff09; 1.性质 2.map的使用 1.创建对象 2.插入 3.删除 4.[] 1.关联式容器 C中…

pytho爬取南京房源成交价信息并导入到excel

# encoding: utf-8 # File_name: import requests from bs4 import BeautifulSoup import xlrd #导入xlrd库 import pandas as pd import openpyxl# 定义函数来获取南京最新的二手房房子成交价 def get_nanjing_latest_second_hand_prices():cookies {select_city: 320100,li…

C语言 | Leetcode C语言题解之第50题Pow(x,n)

题目&#xff1a; 题解&#xff1a; double myPow(double x, int n){if(n 0 || x 1){return 1;}if(n < 0){return 1/(x*myPow(x,-(n1)));}if(n % 2 0){return myPow(x*x,n/2);}else{return x*myPow(x*x,(n - 1)/2);} }

selenium 4.x入门篇(环境搭建、八大元素定位)

背景 Web自动化测现状 1. 属于 E2E 测试 2. 过去通过点点点 3. 好的测试&#xff0c;还需要记录、调试网页的细节 一、selenium4.x环境搭建 一键搭建 pip3 install webdriver-helper 安装后自动的完成&#xff1a; 1. 查看浏览器的版本号 2. 查询操作系统的类型…

68.网络游戏逆向分析与漏洞攻防-利用数据包构建角色信息-自动生成CPP函数解决数据更新的问题

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

0426GoodsBiddingAJAX项目

0426GoodsBiddingAJAX项目包-CSDN博客 数据库字段 ​ 管理员的登录界面 ​ 登录成功跳转在线拍卖界面&#xff0c;使用监听器拦截请求&#xff0c;只能登录管理员后访问该界面 ​ 商品竞拍列表 ​ 商品竞拍列表的竞拍操作&#xff1a; ​ 1 用户未登录跳转用户登录界面&#x…

(三十一)第 5 章 数组和广义表(稀疏矩阵的三元组行逻辑链接的顺序存储表示实现)

1. 背景说明 2. 示例代码 1)errorRecord.h // 记录错误宏定义头文件#ifndef ERROR_RECORD_H #define ERROR_RECORD_H#include <stdio.h> #include <string.h> #include <stdint.h>// 从文件路径中提取文件名 #define FILE_NAME(X) strrchr(X, \\) ? strrch…

算法03贪心与动态规划

算法03贪心与动态规划 1. 贪心与动态规划概述1.贪心1.1贪心概述1.2贪心常用场景1.3贪心解题思路 2.动态规划2.1动态规划概述2.2动态规划常用场景2.3动态规划解题模板 3.浅谈贪心与动态规划的关系 2.贪心经典题目区间问题 3.动态规划经典题目3.1体会“从整体到局部”的思考过程3…
最新文章