FreeRTOS-队列源码分析

本文深入剖析FreeRTOS中的队列机制,详细介绍了队列的创建、数据的入队和出队过程,以及中断处理。队列作为一种任务间通信的数据结构,支持FIFO或LIFO模式,提供值传递和指针传递。文章通过代码分析展示了队列创建、入队、出队函数的工作原理,包括超时检查、任务阻塞和唤醒等关键操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

FreeRTOS的队列是用于任务与任务、任务与中断之间通信的一种数据结构。各个任务之间的数据通信通过一段共同的存储空间按需获取和发送数据,这段存储空间采用队列的形式进行访问,可以采用动态或静态的方式来创建这段内存。任务往队列中发送数据和接收数据通常采用先进先出(FIFO)的存储缓冲机制,当然也可以使用后进先出(LIFO)的形式。FreeRTOS往队列发送和接收数据采用的是直接拷贝的形式,将要发送和接收的数据使用memcpy这个函数拷贝到相应的内存空间,这样做的好处就是如果传递的是一个变量,那这个变量放入队列后,变量就可以作为它用重新赋值给其它代码使用,因为数据已经保存在了队列中,这种方式叫值传递。当然也可以传递一段固定存储空间的内存指针给队列做保存,但必须保证这段空间不能修改,否则在接收端根据收到的指针去访问那段内存的话就可能不是自己想要的数据了。

入队和出队:
入队就是往队列中发送,往队列中发送数据分三种情况。第一种如果在往队列发送数据的时候没有设置阻塞时间,则无论队列有没有满,入队成功与否,都毫不留情直接返回。第二种往队列发送数据的时候设置一段阻塞时间,如果队列没满则直接将数据复制到队列中,队列满了的话就将任务挂到等待列表进入阻塞状态,当阻塞时间到了就退出阻塞状态立即返回往下执行。第三种往队列发送数据时将阻塞时间设置为最大值,队列没满则直接将数据复制到队列中,队列满了的话就一直阻塞,直到入队成功为止。

队里的使用:
一般在使用队列前需要先创建队列,然后在某个线程中就可以往队列中发送信息,在另一个线程中从队列中获取信息,以一个简单的例子来带入:

/* 定义队列变量 */
QueueHandle_t Led_Queue;

/* 用于创建LED任务的开始任务 */
void start_task(void *pvParameters)
{
	/* 进入临界区 */
    taskENTER_CRITICAL();           

	/* 创建消息Led_Queue */
	Led_Queue = xQueueCreate(1, sizeof(unsigned char));
	
    /* 创建LED0任务 */
    xTaskCreate((TaskFunction_t )led0_task,     	
                (const char*    )"led0_task",   	
                (uint16_t       )LED0_STK_SIZE, 
                (void*          )NULL,				
                (UBaseType_t    )LED0_TASK_PRIO,	
                (TaskHandle_t*  )&LED0Task_Handler);   
    /* 创建LED1任务 */
    xTaskCreate((TaskFunction_t )led1_task,     
                (const char*    )"led1_task",   
                (uint16_t       )LED1_STK_SIZE, 
                (void*          )NULL,
                (UBaseType_t    )LED1_TASK_PRIO,
                (TaskHandle_t*  )&LED1Task_Handler);         
    /* 删除开始任务 */
	vTaskDelete(StartTask_Handler);
	/* 退出临界区 */
    taskEXIT_CRITICAL();            
}

/* LED0任务函数  */
void led0_task(void *pvParameters)
{
	unsigned char led_message = 0;
	
    while(1)
    {
        LED0=~LED0;
		led_message =! led_message;
		/* 发送消息到队列中 */
		xQueueSend(Led_Queue, &led_message, 10);
		vTaskDelay(500);
    }
}   

/* LED1任务函数 */
void led1_task(void *pvParameters)
{
	unsigned char led_message = 0;
	
    while(1)
    {
		/* 从队列中获取消息 */
		xQueueReceive(Led_Queue, &led_message, 10);
		if(led_message == 1){
			LED1 = 0;
		}
		else{
			LED1 = 1;
		}
		vTaskDelay(10);
    }
}

根据上面这段应用,可以了解到第一个使用到的函数就是队列创建,队列创建后会返回一个队列指针,这个队列指针是个结构体指针变量(QueueDefinition *):

#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 )
{...}
typedef struct QueueDefinition * QueueHandle_t;

结构体变量(QueueDefinition )保存着队列的描述信息,每个队列都对应一个结构体变量,可以通过这个结构体获取到队列当前的状态、信息及空间等。结构体的定义及含义如下:

typedef struct QueueDefinition /* The old naming convention is used to prevent breaking kernel aware debuggers. */
{
	int8_t *pcHead;		/* 指向队列存储区开始地址 */			
	int8_t *pcWriteTo;	/* 指向存储区中下一个空闲区域 */		

	union
	{
		QueuePointers_t xQueue;		/* 使用队列时用于保存队列尾部指针和最后一个出队的队列项首地址 */	
		SemaphoreData_t xSemaphore; /* 使用信号量时用于保存信号量相关变量 */
	} u;

	List_t xTasksWaitingToSend;		/* 等待发送任务列表,队列满导致入队阻塞的话会将任务暂存到此列表 */	
	List_t xTasksWaitingToReceive;	/* 等待接收任务列表,队列空导致出队阻塞的话会将任务暂存到此列表 */ 

	volatile UBaseType_t uxMessagesWaiting;	 /* 队列当前的队列项数量 */
	UBaseType_t uxLength;		/* 队列的总队列项数量,即队列长度 */		
	UBaseType_t uxItemSize;		/* 每个队列项所占的字节数 */	
	volatile int8_t cRxLock;	/* 当队列上锁后用于统计队列出队的列表项数量 */		
	volatile int8_t cTxLock;	/* 当队列上锁后用于统计队列入队的列表项数量 */	

	/* 如果使用了静态存储的话把这个变量置为pdTURE,确保删除时使用静态方式 */
	#if( ( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 ) )
		uint8_t ucStaticallyAllocated;
	#endif

	/* 队列集相关宏 */
	#if ( configUSE_QUEUE_SETS == 1 )
		struct QueueDefinition *pxQueueSetContainer;
	#endif

	/* 跟踪调试相关宏 */
	#if ( configUSE_TRACE_FACILITY == 1 )
		UBaseType_t uxQueueNumber;
		uint8_t ucQueueType;
	#endif

} xQUEUE;

队列的创建(xQueueCreate)实际上是一个宏,最终调用的是 xQueueGenericCreate:

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

其中有个参数用于指定创建的队列类型“ queueQUEUE_TYPE_BASE ” ,实际上队列的创建还支持多种类型:

#define queueQUEUE_TYPE_BASE				( ( uint8_t ) 0U )	/* 普通消息队列 */
#define queueQUEUE_TYPE_SET					( ( uint8_t ) 0U )	/* 队列集 */
#define queueQUEUE_TYPE_MUTEX 				( ( uint8_t ) 1U )	/* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE	( ( uint8_t ) 2U )	/* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE	( ( uint8_t ) 3U )	/* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX		( ( uint8_t ) 4U )	/* 递归互斥信号量 */

任务创建函数的源码分析如下:

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

		/* 队列的长度必须大于0(即队列项大于0) */
		configASSERT( uxQueueLength > ( UBaseType_t ) 0 );

		if( uxItemSize == ( UBaseType_t ) 0 )
		{
			/* 队列项的字节数为0,即不需要存储区 */
			xQueueSizeInBytes = ( size_t ) 0;
		}
		else
		{
			/* 队列项总大小 = 队列项数量 * 队列项字节数 */
			xQueueSizeInBytes = ( size_t ) ( uxQueueLength * uxItemSize );
		}


		/* 动态分配队列所需的总内存(包含消息存储区),Queue_t为队列结构体信息的内存空间 */
		pxNewQueue = ( Queue_t * ) pvPortMalloc( sizeof( Queue_t ) + xQueueSizeInBytes );

		if( pxNewQueue != NULL )
		{
			/* 内存分配成功,保存队列内存块的指针 */
			pucQueueStorage = ( uint8_t * ) pxNewQueue;
			/* 队列指针指向存储区(不包含消息存储区) */
			pucQueueStorage += sizeof( Queue_t ); 

			#if( configSUPPORT_STATIC_ALLOCATION == 1 )
			{
				/* 队列可以静态和动态创建的情况下默认为动态创建,这里标记不使用静态分配,后面删除时会采用动态形式 */
				pxNewQueue->ucStaticallyAl
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值