FreeRTOS-V10 源码分析——队列(queue)

上篇:《FreeRTOS-V10 源码分析——链表(list),任务(task)》

下篇:《FreeRTOS-V10 源码分析——内存管理 (heap 1~5)》

目录

前言

1. 源码分析

1)QUEUE

xQueueCreate

xQueueCreateStatic

xQueueSendToFront

xQueueSendToBack

xQueueSend

xQueueOverwrite

xQueueGenericSend

xQueuePeek

xQueuePeekFromISR

xQueueReceive

uxQueueMessagesWaiting

uxQueueSpacesAvailable

vQueueDelete

xQueueSendToFrontFromISR

xQueueSendToBackFromISR

xQueueOverwriteFromISR

xQueueSendFromISR

xQueueReceiveFromISR

xQueueIsQueueEmptyFromISR、xQueueIsQueueFullFromISR、uxQueueMessagesWaitingFromISR

xQueueCreateMutex

2) 信号量,互斥量


 

前言

        源码中queue也是FreeRTOS基本要包含的一个组件。请对照源码一起查看。

FreeRTOS 官网以及源码下载: https://www.freertos.org/

1. 源码分析

1)QUEUE

  • xQueueCreate

        函数原型

QueueHandle_t  xQueueCreate( uxQueueLength, uxItemSize )
#define xQueueCreate( uxQueueLength, uxItemSize ) xQueueGenericCreate( ( uxQueueLength ), ( uxItemSize ), ( queueQUEUE_TYPE_BASE ) )

        首先申请一块区域用于存放整个队列

pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes ); 

        然后将申请下来的内存初始化给一个队列的结构体,用于存储队列信息

pucQueueStorage = ( uint8_t * ) pxNewQueue;
pucQueueStorage += sizeof( Queue_t );
prvInitialiseNewQueue( uxQueueLength, uxItemSize, pucQueueStorage, ucQueueType, pxNewQueue );

        队列结构体的信息如下,信号量本质是调用队列这一点在结构体里也有所体现

typedef struct QueueDefinition 		/* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
	int8_t *pcHead;					/*< Points to the beginning of the queue storage area. */
	int8_t *pcWriteTo;				/*< Points to the free next place in the storage area. */

	union
	{
		QueuePointers_t xQueue;		/*< Data required exclusively when this structure is used as a queue. */
		SemaphoreData_t xSemaphore; /*< Data required exclusively when this structure is used as a semaphore. */
	} u;

	List_t xTasksWaitingToSend;		/*< List of tasks that are blocked waiting to post onto this queue.  Stored in priority order. */
	List_t xTasksWaitingToReceive;	/*< List of tasks that are blocked waiting to read from this queue.  Stored in priority order. */

	volatile UBaseType_t uxMessagesWaiting;/*< The number of items currently in the queue. */
	UBaseType_t uxLength;			/*< The length of the queue defined as the number of items it will hold, not the number of bytes. */
	UBaseType_t uxItemSize;			/*< The size of each items that the queue will hold. */

	volatile int8_t cRxLock;		/*< Stores the number of items received from the queue (removed from the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */
	volatile int8_t cTxLock;		/*< Stores the number of items transmitted to the queue (added to the queue) while the queue was locked.  Set to queueUNLOCKED when the queue is not locked. */

	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t ucStaticallyAllocated;	/*< Set to pdTRUE if the memory used by the queue was statically allocated to ensure no attempt is made to free the memory. */
	#endif

	#if ( configUSE_QUEUE_SETS == 1 )
		struct QueueDefinition *pxQueueSetContainer;
	#endif

	#if ( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t uxQueueNumber;
		uint8_t ucQueueType;
	#endif

} xQUEUE;
typedef xQUEUE Queue_t;

typedef struct QueuePointers
{
	int8_t *pcTail;					/*< Points to the byte at the end of the queue storage area.  Once more byte is allocated than necessary to store the queue items, this is used as a marker. */
	int8_t *pcReadFrom;				/*< Points to the last place that a queued item was read from when the structure is used as a queue. */
} QueuePointers_t;

typedef struct SemaphoreData
{
	TaskHandle_t xMutexHolder;		 /*< The handle of the task that holds the mutex. */
	UBaseType_t uxRecursiveCallCount;/*< Maintains a count of the number of times a recursive mutex has been recursively 'taken' when the structure is used as a mutex. */
} SemaphoreData_t;

        在 prvInitialiseNewQueue 初始化指向该内存区域的队列信息,xQueueGenericReset初始化联合中的队列信息,队列接收和发送两个链表

pxNewQueue->pcHead = ( int8_t * ) pxNewQueue;
pxNewQueue->uxLength = uxQueueLength;
pxNewQueue->uxItemSize = uxItemSize;
( void ) xQueueGenericReset( pxNewQueue, pdTRUE );  
  • xQueueCreateStatic

        函数原型

QueueHandle_t  xQueueCreateStatic( uxQueueLength, uxItemSize, pucQueueStorage, pxQueueBuffer )
#define xQueueCreateStatic( uxQueueLength, uxItemSize, pucQueueStorage, pxQueueBuffer ) xQueueGenericCreateStatic( ( uxQueueLength ), ( uxItemSize ), ( pucQueueStorage ), ( pxQueueBuffer ), ( queueQUEUE_TYPE_BASE ) )

        相比xQueueCreate多了传入参数 队列的静态存储区域。

  • xQueueSendToFront

        函数原型

BaseType_t xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait )
#define xQueueSendToFront( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_FRONT )

        检查传入参数后,taskENTER_CRITICAL停止任务切换。如果队列有空间或者是覆盖写入,那么就将内容复制到队列

if( ( pxQueue->uxMessagesWaiting < pxQueue->uxLength ) || ( xCopyPosition == queueOVERWRITE ) )
{
    xYieldRequired = prvCopyDataToQueue( pxQueue, pvItemToQueue, xCopyPosition );
... ...

/* prvCopyDataToQueue -----------------------------------------------------------*/

( void ) memcpy( ( void * ) pxQueue->u.xQueue.pcReadFrom, pvItemToQueue, ( size_t ) pxQueue->uxItemSize ); /*lint !e961 !e9087 !e418 MISRA exception as the casts are only redundant for some ports.  Cast to void required by function signature and safe as no alignment requirement and copy length specified in bytes.  Assert checks null pointer only used when length is 0. */
pxQueue->u.xQueue.pcReadFrom -= pxQueue->uxItemSize;
if( pxQueue->u.xQueue.pcReadFrom < pxQueue->pcHead ) /*lint !e946 MISRA exception justified as comparison of pointers is the cleanest solution. */
{
    pxQueue->u.xQueue.pcReadFrom = ( pxQueue->u.xQueue.pcTail - pxQueue->uxItemSize );
}

        pxQueue->u.xQueue.pcReadFrom是一个指向队列第一个元素的前一个(在队列内存区域内)待写入位置,每次写入就前移pxQueue->uxItemSize

  • xQueueSendToBack

        函数原型

BaseType_t xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait )
#define xQueueSendToBack( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

        同xQueueSendToFront也是调用xQueueGenericSend,只是从队列的后部待写入指针pxQueue->pcWriteTo开始写入,每次写入向后偏移pxQueue->uxItemSize

  • xQueueSend

        函数原型

BaseType_t xQueueSend( xQueue, pvItemToQueue, xTicksToWait )
#define xQueueSend( xQueue, pvItemToQueue, xTicksToWait ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), ( xTicksToWait ), queueSEND_TO_BACK )

        等同于xQueueSendToBack()

  • xQueueOverwrite

        函数原型

BaseType_t xQueueOverwrite( xQueue, pvItemToQueue )
#define xQueueOverwrite( xQueue, pvItemToQueue ) xQueueGenericSend( ( xQueue ), ( pvItemToQueue ), 0, queueOVERWRITE )

        只能在长度为1的队列中使用,相比xQueueSendToFront操作队列的数量不会增加

if( xPosition == queueOVERWRITE )
{
	if( uxMessagesWaiting > ( UBaseType_t ) 0 )
	{
		/* An item is not being added but overwritten, so subtract
		one from the recorded number of items in the queue so when
		one is added again below the number of recorded items remains
		correct. */
		--uxMessagesWaiting;
	}
... ...
  • xQueueGenericSend

        xQueueSend()xQueueSendToFront()xQueueSendToBack()调用的本体,查看以上

  • xQueuePeek

        函数原型

BaseType_t xQueuePeek( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )

        检查传入参数后,进入一个for(;;)循环,直至return 退出,等待时间xTicksToWait未到或者没有接收到数据,就调度任务,实现等待时间的效果。以下是for循环内容:

        taskENTER_CRITICAL停止任务切换。如果队列非空,从队列的前部复制出,但不改变尾部指针位置,结束临界段。然后分三种情况处理:

        第一种情况:挂起所有任务,锁定队列数量,检查等待时间是否到达,等待时间未到达且队列为空,那么将接收队列加入到pxCurrentTCB->xEventListItem事件触发状态,解锁队列数量,恢复所有任务,进行任务调度;

        第二种情况:挂起所有任务,锁定队列数量,检查等待时间是否到达,等待时间已经到达且队列为空,那么解锁队列数量,恢复所有任务,返回队列为errQUEUE_EMPTY

        第三种情况:挂起所有任务,锁定队列数量,检查等待时间是否到达,不管时间是否到达队列为非空,那么解锁队列数量,恢复所有任务,从for循环开头再开始;

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 )
			{
				/* Remember the read position so it can be reset after the data
				is read from the queue as this function is only peeking the
				data, not removing it. */
				pcOriginalReadPosition = pxQueue->u.xQueue.pcReadFrom;

				prvCopyDataFromQueue( pxQueue, pvBuffer );
				traceQUEUE_PEEK( pxQueue );

				/* The data is not being removed, so reset the read pointer. */
				pxQueue->u.xQueue.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 )
				{
					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
			{
				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_PEEK_FAILED( pxQueue );
					return errQUEUE_EMPTY;
				}
				else if( xEntryTimeSet == pdFALSE )
				{
					/* The queue was empty and a block time was specified so
					configure the timeout structure ready to enter the blocked
					state. */
					vTaskInternalSetTimeOutState( &xTimeOut );
					xEntryTimeSet = pdTRUE;
				}
				else
				{
					/* Entry time was already set. */
					mtCOVERAGE_TEST_MARKER();
				}
			}
		}
		taskEXIT_CRITICAL();
... ...

        从上面这段可以看出要是中断优先级高于 taskENTER_CRITICAL() 可屏蔽优先级,那么send和sendISR冲突的时候会导致数据丢失问题,所以应证了只能在优先级低于configMAX_SYSCALL_INTERRUPT_PRIORITY 的中断使用FreeRTOS的中断安全API。接下来是一个队列锁定和解锁,将由中断引起的入队操作导致的任务唤醒,在解锁的时候进行处理;

... ...

		/* 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 )
		{
			/* Timeout has not expired yet, check to see if there is data in the
			queue now, and if not enter the Blocked state to wait for data. */
			if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
			{
				traceBLOCKING_ON_QUEUE_PEEK( pxQueue );
				vTaskPlaceOnEventList( &( pxQueue->xTasksWaitingToReceive ), xTicksToWait );
				prvUnlockQueue( pxQueue );
				if( xTaskResumeAll() == pdFALSE )
				{
					portYIELD_WITHIN_API();
				}
				else
				{
					mtCOVERAGE_TEST_MARKER();
				}
			}
			else
			{
				/* There is data in the queue now, so don't enter the blocked
				state, instead return to try and obtain the data. */
				prvUnlockQueue( pxQueue );
				( void ) xTaskResumeAll();
			}
		}
		else
		{
			/* The timeout has expired.  If there is still no data in the queue
			exit, otherwise go back and try to read the data again. */
			prvUnlockQueue( pxQueue );
			( void ) xTaskResumeAll();

			if( prvIsQueueEmpty( pxQueue ) != pdFALSE )
			{
				traceQUEUE_PEEK_FAILED( pxQueue );
				return errQUEUE_EMPTY;
			}
			else
			{
				mtCOVERAGE_TEST_MARKER();
			}
		}
	} /*lint -restore */
  • xQueuePeekFromISR

        函数原型

BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue,  void * const pvBuffer )
/*-----------------------------------------------------------*/
BaseType_t xQueuePeekFromISR( QueueHandle_t xQueue,  void * const pvBuffer )
{
BaseType_t xReturn;
UBaseType_t uxSavedInterruptStatus;
int8_t *pcOriginalReadPosition;
Queue_t * const pxQueue = xQueue;

	configASSERT( pxQueue );
	configASSERT( !( ( pvBuffer == NULL ) && ( pxQueue->uxItemSize != ( UBaseType_t ) 0U ) ) );
	configASSERT( pxQueue->uxItemSize != 0 ); /* Can't peek a semaphore. */

	portASSERT_IF_INTERRUPT_PRIORITY_INVALID();

	uxSavedInterruptStatus = portSET_INTERRUPT_MASK_FROM_ISR();
	{
		/* Cannot block in an ISR, so check there is data available. */
		if( pxQueue->uxMessagesWaiting > ( UBaseType_t ) 0 )
		{
			traceQUEUE_PEEK_FROM_ISR( pxQueue );

			/* Remember the read position so it can be reset as nothing is
			actually being removed from the queue. */
			pcOriginalReadPosition = pxQueue->u.xQueue.pcReadFrom;
			prvCopyDataFromQueue( pxQueue, pvBuffer );
			pxQueue->u.xQueue.pcReadFrom = pcOriginalReadPosition;

			xReturn = pdPASS;
		}
		else
		{
			xReturn = pdFAIL;
			traceQUEUE_PEEK_FROM_ISR_FAILED( pxQueue );
		}
	}
	portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus );

	return xReturn;
}
void vPortValidateInterruptPriority( void )
{
uint32_t ulCurrentInterrupt;
uint8_t ucCurrentPriority;

	/* Obtain the number of the currently executing interrupt. */
	__asm volatile( "mrs %0, ipsr" : "=r"( ulCurrentInterrupt ) :: "memory" );

	/* Is the interrupt number a user defined interrupt? */
	if( ulCurrentInterrupt >= portFIRST_USER_INTERRUPT_NUMBER )
	{
		/* Look up the interrupt's priority. */
		ucCurrentPriority = pcInterruptPriorityRegisters[ ulCurrentInterrupt ];
		
		configASSERT( ucCurrentPriority >= ucMaxSysCallPriority );
	}
	
	configASSERT( ( portAIRCR_REG & portPRIORITY_GROUP_MASK ) <= ulMaxPRIGROUPValue );
}

/* Constants required to check the validity of an interrupt priority. */
#define portFIRST_USER_INTERRUPT_NUMBER        ( 16 )
#define portNVIC_IP_REGISTERS_OFFSET_16        ( 0xE000E3F0 )
#define portAIRCR_REG                          ( * ( ( volatile uint32_t * ) 0xE000ED0C ) )
#define portMAX_8_BIT_VALUE                    ( ( uint8_t ) 0xff )
#define portTOP_BIT_OF_BYTE                    ( ( uint8_t ) 0x80 )
#define portMAX_PRIGROUP_BITS                  ( ( uint8_t ) 7 )
#define portPRIORITY_GROUP_MASK                ( 0x07UL << 8UL )
#define portPRIGROUP_SHIFT                     ( 8UL )

/*-----------------------------------------------------------*/

/* Calculate the maximum acceptable priority group value for the number
of bits read back. */
ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
	ulMaxPRIGROUPValue--;
	ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}

#ifdef __NVIC_PRIO_BITS
{
	/* Check the CMSIS configuration that defines the number of
	priority bits matches the number of priority bits actually queried
	from the hardware. */
	configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );
}
#endif

#ifdef configPRIO_BITS
{
	/* Check the FreeRTOS configuration that defines the number of
	priority bits matches the number of priority bits actually queried
	from the hardware. */
	configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );
}
#endif

/* Shift the priority group value back to its position within the AIRCR
register. */
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;

        首先portASSERT_IF_INTERRUPT_PRIORITY_INVALID();确认中断优先级是用户优先级,且优先级高于临界段屏蔽优先级(数字上小于),确认是合法调用。

这里指的注意的的是,《cortex-M3与M4权威指南》相关章节可以找到Cortex-M系列里面中断编号里1~15都是系统异常编号;16~255为可编程片上外设或外设中断源,所以将当前中断优先级与portFIRST_USER_INTERRUPT_NUMBER (16)比较;ucMaxSysCallPriority在任务调度器启动时初始化为临界段屏蔽优先级configMAX_SYSCALL_INTERRUPT_PRIORITY;因此第一个assert就是这个意思。中断与复位控制寄存器(AIRCR)的地址为0xE000ED0C,第8~10位为优先级分组配置寄存器(即& portPRIORITY_GROUP_MASK取出的几位);启动任务调度器时,ulMaxPRIGROUPValue就已经配置为最大值(7);

portSET_INTERRUPT_MASK_FROM_ISR()
#define portSET_INTERRUPT_MASK_FROM_ISR()		__get_BASEPRI(); portDISABLE_INTERRUPTS()
#define portDISABLE_INTERRUPTS()\
{\
	__set_BASEPRI( configMAX_SYSCALL_INTERRUPT_PRIORITY );\
	__DSB();\
	__ISB();\
}

        然后保存当前_BASEPRI,开启中断屏蔽;复制队列的数据,不改变队列读取指针位置,恢复_BASEPRI。

  • xQueueReceive

        函数原型

BaseType_t xQueueReceive( QueueHandle_t xQueue, void * const pvBuffer, TickType_t xTicksToWait )

        基本同xQueuePeek,只是复制后偏移读取位置指针;遵循FIFO原则;

  • uxQueueMessagesWaiting

        函数原型

UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue )

        临界段,直接返回( ( Queue_t * ) xQueue )->uxMessagesWaiting

  • uxQueueSpacesAvailable

        函数原型

UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue )

        临界段,直接返回(pxQueue->uxLength - pxQueue->uxMessagesWaiting)

  • vQueueDelete

        函数原型

void vQueueDelete( QueueHandle_t xQueue )

        释放队列申请的内存

  • xQueueSendToFrontFromISR

        函数原型

BaseType_t xQueueSendToFrontFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )
#define xQueueSendToFrontFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_FRONT )

        检查传入参数,中断屏蔽,参见xQueuePeekFromISR ,然后和xQueueSendToFront一样的流程,不过由于不能任务调度和等待取消掉for循环,只进行一次操作,成功返回pdPASS,失败返回errQUEUE_FULL,并确认是否需要调度。

  • xQueueSendToBackFromISR

        函数原型

BaseType_t xQueueSendToBackFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )
#define xQueueSendToBackFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )

        参见xQueueSendToFrontFromISR,调用同一函数,仅队列写入方式不同;

  • xQueueOverwriteFromISR

        函数原型

BaseType_t xQueueOverwriteFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )
#define xQueueOverwriteFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueOVERWRITE )

        参见xQueueSendToFrontFromISR,调用同一函数,仅队列写入方式不同;

  • xQueueSendFromISR

        函数原型

BaseType_t xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken )
#define xQueueSendFromISR( xQueue, pvItemToQueue, pxHigherPriorityTaskWoken ) xQueueGenericSendFromISR( ( xQueue ), ( pvItemToQueue ), ( pxHigherPriorityTaskWoken ), queueSEND_TO_BACK )

        同xQueueSendToFrontFromISR

  • xQueueReceiveFromISR

        函数原型

BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue, void * const pvBuffer, BaseType_t * const pxHigherPriorityTaskWoken )

        同xQueuePeekFromISR,只是复制数据后,减少队列数量。

  • xQueueIsQueueEmptyFromISR、xQueueIsQueueFullFromISR、uxQueueMessagesWaitingFromISR

        中断版本,获取队列信息;

  • xQueueCreateMutex

        函数原型

QueueHandle_t xQueueCreateMutex( const uint8_t ucQueueType )

        互斥量就是队列长度为1,容量为0的队列

const UBaseType_t uxMutexLength = ( UBaseType_t ) 1, uxMutexSize = ( UBaseType_t ) 0;
xNewQueue = xQueueGenericCreate( uxMutexLength, uxMutexSize, ucQueueType );
prvInitialiseMutex( ( Queue_t * ) xNewQueue );

2) 信号量,互斥量

        基本的实现和运用通过队列来实现,参见xQueueCreateMutex

 

 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值