09-FreeRTOS通信机制(一)

消息队列

消息队列在RTOS中为任务和任务之间,或者任务和中断之间提供通信的一种机制。通过消息队列,任务A可以发送给任务B对应的信息,或者接收来自任务B的信息,是一种非常重要的通信机制。消息队列中如果发送的消息过大,可以采用指针的形式发送。任何任务都可以向同一队列写入和读出。

阻塞分析

消息队列提供了阻塞机制,所谓的阻塞有两方面,一方面是消息的发送,另外一方面是消息的接收。

  • 消息发送:任务A向某个消息队列发送消息

    • 消息队列未满:直接发送,一般采用的是将消息拷贝到队列的尾部;

    • 消息队列已满

      • 任务A等待某个时间,在这个时间内将任务A加入到队列的等待发送链表xTasksWaitingToSend中,同时依据等待的时间,将任务A添加到延时链表中,在该函数操作的过程中,要对队列进行上锁,同时挂起调度器,防止中断或者其他任务操作该队列。

        c 复制代码
        vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
        void vTaskPlaceOnEventList( List_t * const pxEventList, const TickType_t xTicksToWait )
        {
        	configASSERT( pxEventList );
        	vListInsert( pxEventList, &( pxCurrentTCB->xEventListItem ) );
        	prvAddCurrentTaskToDelayedList( xTicksToWait, pdTRUE );
        }
      • 任务A不等待某个时间,则之间返回errQUEUE_FULL

  • 消息接收:任务A接收来自消息队列的信息(出队)

    • 消息队列为空

      • 任务A等待时间为0,则之间返回errQUEUE_EMPTY

      • 任务A等待一会,此时任务进行阻塞状态,等待任务的到来,如果在规定的时间内收到了消息,就会从阻塞态改为就绪态;如果规定时间内仍没有消息,则从阻塞态唤醒,返回没有等待到消息的错误码,继续执行任务A的其他代码。

        c 复制代码
        vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
      • 任务A持续等待,此时任务进入阻塞态,直到读取到消息;

    • 消息队列不为空:直接收消息;

创建消息队列

如何创建消息队列?创建消息队列过程中,程序内部做了什么事情?

c 复制代码
#define	xQueueCreate(uxQueueLength,uxItemSize)    \
xQueueGenericCreate((uxQueueLength), (uxItemSize), (queueQUEUE_TYPE_BASE))

QueueHandle_t xQueueGenericCreate( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, const uint8_t ucQueueType )
{
	Queue_t *pxNewQueue;
	size_t xQueueSizeInBytes;
	uint8_t *pucQueueStorage;

	if( uxItemSize == ( UBaseType_t ) 0 )
	{
         /* 队列存储空间为0 */
		xQueueSizeInBytes = ( size_t ) 0;
	}else
	{
		/* 计算队列存储域的空闲大小*/
		xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize ); 
	}
    /* 向内存申请空闲,大小为队列结构体+队列存储域 */
	pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );
	if( pxNewQueue != NULL )
	{
		/* 更新队列存储域的地址 */
		pucQueueStorage = ( ( uint8_t * ) pxNewQueue ) + sizeof( Queue_t );
        /* 初始化新的队列 */
		prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );
	}
	return pxNewQueue;
}

在该函数内首先是向堆空间中申请一定的内存,大小为sizeof( Queue_t ) + xQueueSizeInBytes,代表实际的队列内容加上队列的结构体。内存空间申请完毕后,调用 prvInitialiseNewQueue 函数初始化该消息队列。

初始化消息队列的本质就是初始化 pxNewQueue 各个成员变量,其中变量 pcHead 指向消息队列内存本身,还有初始化 uxLength、uxItemSize。最后调用 xQueueGenericReset 函数,完成 uxMessagesWaiting、 pcWriteTo、cRxLock、xTasksWaitingToSend 和 xTasksWaitingToReceive 等初始化。

c 复制代码
static void prvInitialiseNewQueue( const UBaseType_t uxQueueLength, const UBaseType_t uxItemSize, uint8_t *pucQueueStorage, const uint8_t ucQueueType, Queue_t *pxNewQueue )
{
	if( uxItemSize == ( UBaseType_t ) 0 )
	{
		/* 队列存储域大小为0,将head指针指向队列本身 */
		pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
	}
	else
	{
		 /* head指针指向队列存储域的起始地址 */
		pxNewQueue->pcHead = ( int8_t * ) pucQueueStorage;
	}
	/* 初始化队列的成员变量 */
	pxNewQueue->uxLength = uxQueueLength;
	pxNewQueue->uxItemSize = uxItemSize;
	( void ) xQueueGenericReset( pxNewQueue, pdTRUE );
}

来看一下消息队列这个结构体中成员变量的含义:

  • int8_t * pcHead 指向队列数据存储区域开始的位置;
  • int8_t * pcWriteTo 指向队列存储区域中空闲的位置;
  • xTasksWaitingToSend/xTasksWaitingToReceive 代表阻塞到队列上的任务;
  • uxMessagesWaiting 代表消息队列中元素的目前个数;
  • uxLength 代表队列的长度;
  • uxItemSize代表每一个元素的大小;
  • cRxLock/cTxLock 接收和发送阻塞的任务个数; 联合体如果是队列,则有两个成员变量;
  • pcTail指向队列存储区域的末尾;
  • pcReadFrom指向上一次从那个位置读的元素。

消息队列的创建关键有两个步骤,第一步是向申请内存,用于存储消息和消息队列结构体,第二步是初始化消息队列结构体成员变量,包括等待发送链表xTasksWaitingToSendxTasksWaitingToSend 和等待接收链表xTasksWaitingToReceive

发送消息
1. 发送消息的主体

任务或中断服务程序;

2. 发送消息的函数
c 复制代码
//常用函数,不可在中断服务程序中调用;
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) 
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

//中断服务函数中调用的版本
#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) 
xQueueGenericSendFromISR(( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )
    
//向队列起始部位发送消息
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) 
xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )
    
//向队列首位置发送消息,中断版本
#define xQueueSendToFrontFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )
3. 任务中调用的xQueueGenericSend函数
c 复制代码
BaseType_t xQueueGenericSend( QueueHandle_t xQueue, const void * const pvItemToQueue, TickType_t xTicksToWait, const BaseType_t xCopyPosition )
{
	BaseType_t xEntryTimeSet = pdFALSE, xYieldRequired;
	TimeOut_t xTimeOut;
	Queue_t * const pxQueue = ( Queue_t * ) xQueue;
	for( ;; )
	{
		taskENTER_CRITICAL();
		{
		/* 队列未满或可覆盖入队*/
		if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
			{
            	/*将发送的消息拷贝到队列存储区域,prvCopyDataToQueue内会判断是拷贝到首还是尾 */
				xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
				{
					/* 判断是否有任务在等待获取消息 */
					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
					{
                        /* 从等待链表中移除结点 */
						if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ) )!=pdFALSE )
						{
                            	/* 如果等待消息的任务优先级大于当前任务的优先级,则进行调度 */
								queueYIELD_IF_USING_PREEMPTION();
						}

					}else if( xYieldRequired != pdFALSE )
					{
							queueYIELD_IF_USING_PREEMPTION();
					}
					else{}
				}
			}
			taskEXIT_CRITICAL();
			return pdPASS;
			}
			else  /* 队列已满 */
			{
                /* 等待时间为0,直接返回 */
				if( xTicksToWait == ( TickType_t ) 0 )
				{
					taskEXIT_CRITICAL();
					return errQUEUE_FULL;
				}
				else if( xEntryTimeSet == pdFALSE )
				{
、				   /* 初始化进入阻塞的时间xTimeOut  */
					vTaskSetTimeOutState( &xTimeOut );
					xEntryTimeSet = pdTRUE;
				}
				else
				{
					/* Entry time was already set. */
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		taskEXIT_CRITICAL();
		vTaskSuspendAll();
		prvLockQueue( pxQueue );
		/* 检查超时的时间是否过去,false代表未到达超时时间 */
		if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
		{
            /* 检查队列是否已满,返回true代表队列满 */
			if( prvIsQueueFull( pxQueue ) != pdFALSE )
			{
                /* 将当前任务添加到xTasksWaitingToSend事件链表,并将当前任务添加到延时链表 */
				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToSend ), xTicksToWait );
				prvUnlockQueue( pxQueue );
				if( xTaskResumeAll() == pdFALSE )
				{
					portYIELD_WITHIN_API();
				}
			}/* 队列未满,则恢复调度器,进行任务的再一次执行 */
			else
			{
				/* Try again. */
				prvUnlockQueue( pxQueue );
				( void ) xTaskResumeAll();
			}
		}
		else
		{
			/* 超时 */
			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();
			return errQUEUE_FULL;
		}
	}
}
  • 消息的发送过程如下:

    首先是检查队列是否满,如果没有满,则将要发动的内容拷贝至消息队列的存储区域; 如果队列满,并且不允许覆盖入队,则下面要检查阻塞的时间,如果阻塞时间为0,则直接返回,阻塞时间不为0的情况下,会检查阻塞的时间是否到达,第一次肯定是没有到达,在阻塞未到达的情况下,检查队列是否满,则如果队列满的情况下,则将该任务加入到延时链表,延时的时间为阻塞的时间,并且将任务的事件加入到等待发送事件链表中。如果队列未满,则解锁队列,进行一下次尝试(for循环执行)。如果超时事件到达,则直接返回。

  • 如何实现等待接收队列或发送队列中任务状态的变化?

​ 通过该xTaskRemoveFromEventList函数实现,在该函数内会将xTasksWaitingToSend/xTasksWaitingToReceive 链表中头部的节 点删除,然后判断当前调度器是否挂起,如果挂起状态,则直接将该节点加入到就绪链表中,等待调度。如果是未挂起状态,则将该链 表的xEventListItem加入到xPendingReadyList 链表。最后判断下该节点对应任务的优先级,如果高于当前任务,返回真,并且将 xYieldPending写为真,否则返回FALSE。

  • 初始化xTimeOut 的作用是什么?

    xTimeOut 是xQueueGenericSend函数内的局部变量,在初始化时会在xOverflowCount和xTimeOnEntering写入当前系统中的对应数值;

  • 如何检查超时时间是否过去?

    c 复制代码
    //1. 等待时间为portMAX_DELAY,结果一直返回false,代表还没有到达指定的时间
    if( *pxTicksToWait == portMAX_DELAY ) xReturn = pdFALSE;
    //2. 达到了超时时间,返回true;
    if( ( xNumOfOverflows != pxTimeOut->xOverflowCount ) && ( xConstTickCount >= pxTimeOut->xTimeOnEntering ) ) xReturn = pdTRUE;
    //3. 未到达指定的时间 ,返回false,并更新pxTicksToWait
    if( ( ( TickType_t ) ( xConstTickCount - pxTimeOut->xTimeOnEntering ) ) < *pxTicksToWait )
  • 在将任务设置为阻塞的过程中,系统不希望有其它任务和中断操作这个队列的xTasksWaitingToReceive 列表xTasksWaitingToSend 列表,因为可能引起其它任务解除阻塞,这可能会发生优先级翻转,因此这个过程中不仅将调度器进行了挂起,而且还对队列进行了上锁。

4. 中断调用的xQueueGenericSendFromISR函数

该函数与任务中调用的函数非常类似,下面主要罗列以下差异点:

  • 当队列未满的情况下,在数据完成拷贝后,会检查队列是否上锁,如果已经上锁,则pxQueue->cTxLock加1;如果没有上锁,处理逻辑与任务中的基本一致,先是看看等待接收的事件队列中有没有任务,如果由任务,则将该任务加入到就绪链表,判断对应的优先级,如果优先级高于当前任务,则pxHigherPriorityTaskWoken写为true。
  • 队列已满的状态下,中断不能阻塞,因此直接返回。
任务接收消息
1. 接收消息的函数
c 复制代码
//任务中使用
#define xQueueReceive( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdFALSE )

//接收到消息后,消息不会从队列删除
#define xQueuePeek( xQueue, pvBuffer, xTicksToWait ) xQueueGenericReceive( ( xQueue ), ( pvBuffer ), ( xTicksToWait ), pdTRUE )

//中断中使用
BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void * const pvBuffer, BaseType_t * const pxHigherPriorityTaskWoken )
    
//中断中使用,只接收不删除
BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue,  void * const pvBuffer )
2. 任务中调用xQueueGenericReceive
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;
	for( ;; )
	{
		taskENTER_CRITICAL();
		{
            //获取当前队列中消息的个数
			const UBaseType_t uxMessagesWaiting = pxQueue->uxMessagesWaiting;
			//当前队列有数据
			if( uxMessagesWaiting > ( UBaseType_t ) 0 )
			{
				/* 记录上次读取元素的位置 */
				pcOriginalReadPosition = pxQueue->u.pcReadFrom;
				prvCopyDataFromQueue( pxQueue, pvBuffer );  /* 数据的拷贝,内部会改变u.pcReadFrom */
				if( xJustPeeking == pdFALSE )  /* 进行出队操作 */
				{
					pxQueue->uxMessagesWaiting = uxMessagesWaiting - 1;
					#if ( configUSE_MUTEXES == 1 )
					{
						if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
						{
							pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount();
						}
					}
					#endif /* configUSE_MUTEXES */
					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToSend ) ) == pdFALSE )
					{
						if( xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToSend ) ) != pdFALSE )
						{
							queueYIELD_IF_USING_PREEMPTION();
						}
					}
				} //仅仅获取队列的元素,不删除
				else
				{
					pxQueue->u.pcReadFrom = pcOriginalReadPosition;  //改变后恢复之前的位置
					if( listLIST_IS_EMPTY( &( pxQueue->xTasksWaitingToReceive ) ) == pdFALSE )
					{
						if(xTaskRemoveFromEventList( &( pxQueue->xTasksWaitingToReceive ))!= pdFALSE )
						{
							queueYIELD_IF_USING_PREEMPTION();
						}
					}

				}
				taskEXIT_CRITICAL();
				return pdPASS;
			}  /* 队列中没有数据 */
			else
			{
				if( xTicksToWait == ( TickType_t ) 0 ) /* 不等待,直接退出 */
				{
					taskEXIT_CRITICAL();
					return errQUEUE_EMPTY;
				}
				else if( xEntryTimeSet == pdFALSE )
				{	/* 初始化xTimeOut */
					vTaskSetTimeOutState( &xTimeOut );
					xEntryTimeSet = pdTRUE;
				}
				else{}
			}
		}
		taskEXIT_CRITICAL();
		vTaskSuspendAll();
		prvLockQueue( pxQueue );
		/* 阻塞时间是否已到? */
		if( xTaskCheckForTimeOut( &xTimeOut, &xTicksToWait ) == pdFALSE )
		{    /* 队列是否有数据 */
			if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
			{
				#if ( configUSE_MUTEXES == 1 )
				{
					if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
					{
						taskENTER_CRITICAL();
						{
							vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
						}
						taskEXIT_CRITICAL();
					}
				}
				#endif /* 任务放入等待接收队列中,并进行延时阻塞*/
				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
				prvUnlockQueue( pxQueue );
				if( xTaskResumeAll() == pdFALSE )
				{
					portYIELD_WITHIN_API();
				}
			}
			else  /* 队列中有数据,则再次进行接收,for循环 */
			{
				prvUnlockQueue( pxQueue );
				( void ) xTaskResumeAll();
			}
		}
		else  /* 阻塞时间已到*/
		{
			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();
			if( prvIsQueueEmpty( pxQueue ) != pdFALSE )  /* 检查队列中是否有数据 */
			{
				return errQUEUE_EMPTY;  /* 队列中没有数据,返回空 */
			}  /* 如果有数据,则进行下一次的接收 for循环 */
		}
	}
}
  • 如何实现仅仅是获取队列中的消息,而对消息不出队?

    正常情况下,在获取消息后,会改变pxQueue->u.pcReadFrom 的数值,该成员变量记录着上一次读取消息的位置。如果要实现不出队,就需要恢复之前的位置,关键的操作代码如下。同时不修改pxQueue->uxMessagesWaiting的数值。

    c 复制代码
    pxQueue->u.pcReadFrom += pxQueue->uxItemSize;  //prvCopyDataFromQueue
    pcOriginalReadPosition = pxQueue->u.pcReadFrom; //先保存
    pxQueue->u.pcReadFrom = pcOriginalReadPosition;  //再更新
  • 函数流程图

    简单画了下函数的执行过程,首先判断队列中是否有数据,有数据的话再判断是否要出队。然后如果队列中没有数据,则判断等待的时间,如果不等待,直接返回; 等待时间的大于0,则判断等待的过程中是否超时,如果没有超时,则判断队列中是否有数据,没有数据进行阻塞,有数据则进行下一次for循环。阻塞超时后,如果队列没有数据,则直接返回。

3. 中断中调用xQueueReceiveFromISR

与任务中的主要差异点:

  • 没有出不出队这一说,中断获取后都要出队;
  • 出队后如果队列处于解锁状态下,才会进行等待发送链表的判断,如果等待发送链表中的任务比当前优先级高,不会立刻进行调度,会写入pxHigherPriorityTaskWoken 变量,中断返回后进行调度;
  • 如果队列为空,则直接返回,不会进行阻塞;

信号量

什么是信号量,信号量是一种通信机制,可以假设为一个全局变量,主要有两方面的用途,一个是任务之间的同步,另外一个是临界资源的访问。在分类上可以有二值信号量、计数信号量、互斥信号量和递归信号量四种,二值信号量顾名思义,信号量的数值只有两种状态,通常是0和1,主要用于任务之间的同步,没有优先级继承机制,互斥信号量是一种特殊的二值信号量,而计数信号量数值大于2,主要用于临界资源的访问。

信号量定义

信号量和互斥量用的是消息队列那一套机制,在消息队列中进行了封装。因此信号量的定义与消息队列的定义一致,只不过在某些成员变量中含义不同,涉及的有:

c 复制代码
typedef struct QueueDefinition
{
	/* 省略部分代码 */
	volatile UBaseType_t uxMessagesWaiting;
	UBaseType_t uxLength;			
	UBaseType_t uxItemSize;			
	/* 省略部分代码 */
} xQUEUE;
成员变量 消息队列 二值信号量 计数信号量
uxMessagesWaiting 消息个数 1 计数的个数
uxLength 队列长度 1 计数的个数
uxItemSize 单个消息大小 0 0
接口函数
1. 创建信号量
  • 创建计数信号量

    c 复制代码
    QueueHandle_t xQueueCreateCountingSemaphore( const UBaseType_t uxMaxCount, const UBaseType_t uxInitialCount )
    • uxMaxCount 计数信号量的最大值;
    • uxInitialCount 计数信号量的初始值;
    • 该函数最终调用xQueueGenericCreate,uxMaxCount变量传入uxLength数值,uxInitialCount 写入uxMessagesWaiting;
  • 创建二值信号量

    c 复制代码
    #define xSemaphoreCreateBinary()    xQueueGenericCreate( \
    	( UBaseType_t ) 1,	semSEMAPHORE_QUEUE_ITEM_LENGTH, queueQUEUE_TYPE_BINARY_SEMAPHORE )
    • 二值信号量同样调用xQueueGenericCreate,差异点在于队列长度为1,消息空间队列项为0;
2. 删除信号量
c 复制代码
#define vSemaphoreDelete( xSemaphore ) vQueueDelete( ( QueueHandle_t ) ( xSemaphore ) )

调用队列的删除函数;

3. 释放和获取信号量
  • 释放信号量函数

    c 复制代码
    #define xSemaphoreGive( xSemaphore )		xQueueGenericSend( ( QueueHandle_t ) ( xSemaphore ), NULL, semGIVE_BLOCK_TIME, queueSEND_TO_BACK )
    • 实际为向队列发送一个空的消息;
    • semGIVE_BLOCK_TIME数值为0,如果队列满,则不会等待;
    • 整个过程为如果队列未满,则uxMessageWaiting加1,判断等待队列中是否有任务,如果有任务,恢复此任务;
    • 对应的中断函数为xSemaphoreGiveFromISR;
  • 获取信号量函数

    c 复制代码
    #define xSemaphoreTake( xSemaphore, xBlockTime )		xQueueGenericReceive( ( QueueHandle_t ) ( xSemaphore ), NULL, ( xBlockTime ), pdFALSE )
    • pdFALSE代表进行出队操作;
    • 获取信号量的本质是接收队列的数据,如果队列为空,则根据xBlockTime时间进行阻塞。
    • 中断版本的函数xSemaphoreTakeFromISR;

互斥量

互斥量是信号量的一种,与信号量的差异在于支持互斥量所有权、递归访问和防止优先级翻转的特性,常常用于临界资源的保护。互斥量不能在中断服务函数中使用。

1. 优先级继承机制

互斥量的一个特性是优先级继承机制,所谓的优先级继承就是当一个低优先级的任务通过互斥量占有着某一个临界资源时,有一个高优先级的任务也要访问该临界资源,由于该临界资源被低优先级任务占用,所以高优先级的任务会在此阻塞。假设此时任务中还有中优先级的任务在执行,此时低优先级占用临界资源,但是它就会被中优先级的任务抢占,或者两个任务交替执行。这样高优先级的任务将得不到执行,从而整个系统的实时性就会降低。优先级继承的方法是改变低优先级任务的优先级,与高优先级一致,这样原本的低优先级任务就不会被中优先级抢占,任务快速执行完毕后,释放临界资源,高优先级得以执行,此时再将低任务的优先级复位。

相关的代码:

c 复制代码
void vTaskPriorityInherit( TaskHandle_t const pxMutexHolder )
{
	TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
    //pxMutexHolder为互斥量的持有者
	if( pxMutexHolder != NULL )
	{    /* 小于当前任务(要访问互斥量的高优先级任务) */
		if( pxTCB->uxPriority < pxCurrentTCB->uxPriority )
		{
            /* 如果互斥量持有者的任务在等待事件列表中 */
			if( ( listGET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ) ) & taskEVENT_LIST_ITEM_VALUE_IN_USE ) == 0UL )
			{
              /*调整互斥锁持有者等待的事件列表项的优先级 */
				listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxCurrentTCB->uxPriority ); 
			}
			/* 如果提升优先级的任务处于就绪链表中 */
			if( listIS_CONTAINED_WITHIN( &( pxReadyTasksLists[ pxTCB->uxPriority ] ), &( pxTCB->xStateListItem ) ) != pdFALSE )
			{
                //从就绪链表中删除
				if( uxListRemove( &( pxTCB->xStateListItem ) ) == ( UBaseType_t ) 0 )
				{
					taskRESET_READY_PRIORITY( pxTCB->uxPriority );
				}
					/* 改变优先级,再次加入到就绪链表 */
				pxTCB->uxPriority = pxCurrentTCB->uxPriority;
				prvAddTaskToReadyList( pxTCB );
			}
			else
			{
					/* 提高优先级 */
					pxTCB->uxPriority = pxCurrentTCB->uxPriority;
			}
		}
	}
}
  • 如果互斥量持有者任务在事件链表中,则提高在事件链表中的排序,使其靠前,xItemValue数值越小,越靠前;
  • 如果持有者在就绪链表中,则删除,改变优先级后再次加入就绪链表,等待调度;
  • 其他情况,直接更改持有者的优先级;
c 复制代码
BaseType_t xTaskPriorityDisinherit( TaskHandle_t const pxMutexHolder )
{
	TCB_t * const pxTCB = ( TCB_t * ) pxMutexHolder;
	BaseType_t xReturn = pdFALSE;

		if( pxMutexHolder != NULL )
		{
			( 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 );
					}
					pxTCB->uxPriority = pxTCB->uxBasePriority;
                    //重置等待事件链表的优先级
					listSET_LIST_ITEM_VALUE( &( pxTCB->xEventListItem ), ( TickType_t ) configMAX_PRIORITIES - ( TickType_t ) pxTCB->uxPriority ); 
					prvAddTaskToReadyList( pxTCB );
					xReturn = pdTRUE;
				}
			}
		}
		return xReturn;
}
2. 互斥量控制块

使用的是队列那一套,但是也有部分的差异。

c 复制代码
typedef struct QueueDefinition
{
    /* 省略部分代码 */
	union
	{
		int8_t *pcReadFrom;			
		UBaseType_t uxRecursiveCallCount;
	} u;

	List_t xTasksWaitingToSend;		
	List_t xTasksWaitingToReceive;	

	volatile UBaseType_t uxMessagesWaiting;
	UBaseType_t uxLength;			
	UBaseType_t uxItemSize;			
	/* 省略部分代码 */
} xQUEUE;
  • 联合体u作为互斥量时,使用uxRecursiveCallCount记录递归互斥量调用的次数;
  • uxMessagesWaiting用于表示互斥量是否有效,1表示有效,0为无效;
  • uxLength数值为1,uxItemSize为0;
3. 常用接口函数
  • 创建互斥量

    c 复制代码
    QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )
    {
    	Queue_t *pxNewQueue;
    	const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
        //调用队列的创建函数xQueueGenericCreate
    	pxNewQueue = ( Queue_t * ) xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
        //初始化互斥量
    	prvInitialiseMutex( pxNewQueue );
    
    	return pxNewQueue;
    }
    • 入口参数ucQueueType为queueQUEUE_TYPE_MUTEX;

    • prvInitialiseMutex用于初始化创建后的互斥量,关键的几句函数如下,其中pxMutexHolder为pcTail成员变量,uxQueueType为pcHead成员变量,queueQUEUE_IS_MUTEX等于NULL。xQueueGenericSend函数向队列中发送一个消息,使其uxMessagesWaiting数值为1,互斥量默认有效。

      c 复制代码
      pxNewQueue->pxMutexHolder = NULL;   /* 用于优先级继承机制,指向持有互斥量的任务控制块 */
      pxNewQueue->uxQueueType = queueQUEUE_IS_MUTEX;
      pxNewQueue->u.uxRecursiveCallCount = 0;
      ( void ) xQueueGenericSend( pxNewQueue, NULL, ( TickType_t ) 0U, queueSEND_TO_BACK );
  • 删除互斥量

    由vSemaphoreDelete()完成。

  • 获取互斥量

    与信号量函数一致xSemaphoreTake,最终也是调用xQueueGenericReceive函数,该函数是接收队列的消息。对于互斥量有部分的差异,如下所示。

    c 复制代码
    //uxMessagesWaiting大于0,也就是互斥量有效时
    #if ( configUSE_MUTEXES == 1 )
    	if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
    	{
    		pxQueue->pxMutexHolder = ( int8_t * ) pvTaskIncrementMutexHeldCount();
    	}
    endif /* configUSE_MUTEXES */
    • pvTaskIncrementMutexHeldCount函数会将当前任务的TCB的uxMutexesHeld进行加加,然后返回当前任务的TCB;
    c 复制代码
    //uxMessagesWaiting=0,互斥量无效,且在阻塞时间内时
    #if ( configUSE_MUTEXES == 1 )
    	if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
    	{
    		taskENTER_CRITICAL();
    		{
    			vTaskPriorityInherit( ( void * ) pxQueue->pxMutexHolder );
    		}
    		taskEXIT_CRITICAL();
    	}
    #endif
    • vTaskPriorityInherit函数为优先级继承的实现函数。
    • 如果当前互相量无效,就改变持有者任务的优先级,让其加速执行;
  • 释放互斥量

    与信号量一致xSemaphoreGive,最终调用xQueueGenericSend函数。与互斥量相关的部分如下

    c 复制代码
    //在prvCopyDataToQueue函数中调用
    #if ( configUSE_MUTEXES == 1 )
    {
    	if( pxQueue->uxQueueType == queueQUEUE_IS_MUTEX )
    	{
    		xReturn = xTaskPriorityDisinherit( ( void * ) pxQueue->pxMutexHolder );
    		pxQueue->pxMutexHolder = NULL;
    	}
    }
    #endif /* configUSE_MUTEXES */
    • xTaskPriorityDisinherit函数用于修改优先级继承后的函数。
  • 递归互斥量创建/获取/释放

    递归的实现通过uxRecursiveCallCount实现。

总结

综上所述,在RTOS中消息队列一般用于任务和任务之间,任何和中断之间传递信息,在使用之前需要依据消息的大小申请一定的消息空间,如果消息队列已满或者为空,会对使用消息队列的任务进行阻塞;信号量建立在消息队列之上,底层使用消息队列的那一套函数,对应的结构体也是与消息队列一致;互斥量是信号量的一种,差异点在于互斥量具有优先级继承机制,支持递归访问,一般用于资源访问中,而信号量一般用于任务之间的同步。

相关推荐
智者知已应修善业2 小时前
【51单片机独立按键控制往复流水灯启停】2023-6-13
c++·经验分享·笔记·算法·51单片机
民乐团扒谱机2 小时前
【读论文】基于非线性光学的全光子人工神经网络处理器
论文阅读·笔记·论文
xian_wwq3 小时前
【学习笔记】3 种零防御 UAC 绕过技术
笔记·学习
zjeweler3 小时前
“网安+护网”终极300多问题面试笔记-1共3-内网&域相关
笔记·web安全·网络安全·面试·职场和发展·护网面试
夜瞬3 小时前
NLP学习笔记04:情感分析——从词典方法到 BERT
笔记·学习·自然语言处理
夜瞬3 小时前
NLP学习笔记04:情感分析实践练习实现说明
笔记·学习·自然语言处理
lhb07093 小时前
openssl预编译动态库dll下载(OpenSSL 1.1.1w最新版 OpenSSL 3.5.6 LTS)
笔记
風清掦3 小时前
【江科大STM32学习笔记-10】I2C通信协议 - 10.2 硬件 I2C 读写MPU6050
笔记·stm32·单片机·嵌入式硬件·学习
Engineer邓祥浩4 小时前
JVM学习笔记(10) 第三部分 虚拟机执行子系统 第9章 类加载及执行子系统的案例与实战
jvm·笔记·学习