我之前写过一篇关于队列的blog,需要的戳静态队列。前一段时间分析了FreeRTOS的队列源码。FreeRTOS队列的核心思想和我前边写的队列的核心思想是一致的(我这里说的核心思想是都把队列缓存区看做一个环形缓存),只是在具体实现方式和功能上小有差异,FreeRTOS的队列支持从前向后入队和从后向前入队,这就相当于把栈的功能也实现了。
我把FreeRTOS队列的代码在Visual studio 环境中调试了一下,做了一些改造,方便以后在项目中使用(数据结构的知识很重要,项目中经常用到。而且面试官还总问,因为既能考察你的基础知识,又能考察你的逻辑思维能力,其实我曾经有一次面试就在队列上翻车了,血淋淋的教训)。现在先写到博客中,方便后续查阅。
队列结构体
typedef struct
{
uint8_t *phead; //指向第一个队列项的地址
uint8_t *ptail;//指向最后一个队列项的下一个地址
uint8_t *pwriteto;//指向队列中第一个空闲的列表项
uint8_t *preadfrom; //最后一个出队的队列项的地址
volatile ulong_t uxmessageswaiting;//队列中有效的队列项数
ulong_t uxlength;//队列的长度
ulong_t uxitemsize;//队列项的大小
}queue;
队列创建函数
/****
功能:队列创建
参数:队列长度
参数:队列项大小
返回值:队列句柄
****/
queue * queue_create(const ulong_t queue_length, const ulong_t queue_itemsize)
{
queue * handle = NULL;
ulong_t buffer_size = 0;
uint8_t * queuestorage = NULL;
buffer_size = queue_length * queue_itemsize;//计算出队列缓存区的大小
handle = (queue *)malloc(sizeof(queue)+buffer_size);//申请队列所需要的内存
if (handle != NULL)
{
queuestorage = (uint8_t *)handle + sizeof(queue);//计算出队列缓存区的起始地址
handle->phead = queuestorage;
handle->uxitemsize = queue_itemsize;
handle->uxlength = queue_length;
queue_rest(handle);
}
return handle;
}
队列创建函数完成了两个主要任务。
第一内存申请,申请队列结构体内存和队列缓存区内存。申请完内存如下图。
第二初始化队列结构体成员。在队列创建函数中初始化了一部分成员变量,在复位函数中初始化了一部分成员。
初始化以后队列结构体中成员变量的指向关系如下图。
成员uxmessageswaiting
表示的是队列中有效的队列项数,初始化以后是0。
队列复位函数
/****
功能:队列复位函数
参数:队列句柄
返回值:无
****/
void queue_rest(queue * const handle)
{
handle->ptail = handle->phead + handle->uxlength * handle->uxitemsize;
handle->uxmessageswaiting = 0;
handle->pwriteto = handle->phead;
handle->preadfrom = handle->phead + (((handle->uxlength) - 1) * handle->uxitemsize);
}
判断队列满函数
/****
功能:判断队列满函数
参数:队列句柄
返回值: 0队列满 1队列不满
****/
uint8_t queue_is_full(const queue * const handle)
{
if (handle->uxmessageswaiting == handle->uxlength)
{
return 0;
}
else
{
return 1;
}
}
判断队列空函数
/****
功能:判断队列空函数
参数:队列句柄
返回值: 0队列空 1队列不空
****/
uint8_t queue_is_empty(const queue * const handle)
{
if (handle->uxmessageswaiting == 0)
{
return 0;
}
else
{
return 1;
}
}
成员uxmessageswaiting
表示的是队列中有效的队列项数。当uxmessageswaiting
等于成员uxlength
时那么队列缓存中就没有多余的空间了,此时队列是满的。当uxmessageswaiting
等于0
的时候那么队列缓存中没有有效数据,表示队列是空的。
接下来我们看重点入队函数和出队函数:
入队函数:
/****
功能:入队函数
参数:队列句柄
参数:待入队数据的地址
返回值:入队结果 0成功 1失败
****/
uint8_t queue_send(queue * const handle, const void * const ptemp, const uint8_t xposition)
{
if (handle->uxmessageswaiting < handle->uxlength)
{
if (xposition == QUEUE_SEND_TO_BACK)//从前向后入队
{
memcpy(handle->pwriteto,ptemp,handle->uxitemsize);//复制数据到队列
handle->pwriteto += handle->uxitemsize;//偏移到下一个队列项的地址,
if (handle->pwriteto >= handle->ptail)//存放到末尾再从头开始存,在逻辑上是环形的
{
handle->pwriteto = handle->phead;
}
}
else if (xposition == QUEUE_SEND_TO_FRONT)//从后向前入队
{
memcpy(handle->preadfrom,ptemp,handle->uxitemsize);
handle->preadfrom -= handle->uxitemsize;
if (handle->preadfrom < handle->phead)
{
handle->preadfrom = handle->ptail - handle->uxitemsize;
}
}
handle->uxmessageswaiting += 1;
return 0;
}
return 1;
}
1、先判断队列是否有缓存空间存放数据,如果没有空间存放直接返回1,入队失败。如果有空间则做入队操作。
2、这个入队函数写的就比较强大了。我们知道入队就是将数据存入队列缓存。FreeRTOS入队有两种方向:从前向后入队(从缓存区的低地址向缓存区的高地址方向存放)和从后向前入队(从缓存区的高地址向缓存区的低地址方向存放)。
从前向后入队:
先将数据拷贝到队列缓存中。然后pwriteto
指针变量向后偏移一个队列项。偏移到末尾的时候回过头来再从第一个开始存。物理上是线性的,逻辑上是环形的。
从后向前入队是同样的道理。
3、如果入队成功队列缓存中有效个数要加1。
出队函数
/****
功能:出队函数
参数:队列句柄
参数:保存出队数据的地址
返回值:出队结果 0成功 1失败
****/
uint8_t queue_receive(queue * const handle, const void * const ptemp)
{
if (handle->uxmessageswaiting > 0)
{
handle->preadfrom += handle->uxitemsize;
if (handle->preadfrom >= handle->ptail)
{
handle->preadfrom = handle->phead;
}
memcpy((void *)ptemp,(void *)handle->preadfrom,handle->uxitemsize);
handle->uxmessageswaiting -= 1;
return 0;
}
return 1;
}
1、判断队列是否为空,如果为空直接返回1,不为空将读取数据的指针向后偏移一个队列项,如果超过队列的缓存边界,就回到缓存的起始地址处。
2、拷贝出数据,然后队列项数减1。
最后我们看下运行结果。
int main()
{
uart_receive_handle = queue_create(5,1);
test_data = 50;
if (queue_send(uart_receive_handle, &test_data, QUEUE_SEND_TO_FRONT) == 0)
{
printf("入队成功\n");
}
else
{
printf("入队失败\n");
}
test_data++;
if (queue_send(uart_receive_handle, &test_data, QUEUE_SEND_TO_FRONT) == 0)
{
printf("入队成功\n");
}
else
{
printf("入队失败\n");
}
test_data++;
if (queue_send(uart_receive_handle, &test_data, QUEUE_SEND_TO_FRONT) == 0)
{
printf("入队成功\n");
}
else
{
printf("入队失败\n");
}
test_data++;
if (queue_send(uart_receive_handle, &test_data, QUEUE_SEND_TO_FRONT) == 0)
{
printf("入队成功\n");
}
else
{
printf("入队失败\n");
}
test_data++;
if (queue_send(uart_receive_handle, &test_data, QUEUE_SEND_TO_FRONT) == 0)
{
printf("入队成功\n");
}
else
{
printf("入队失败\n");
}
test_data++;
if (queue_send(uart_receive_handle, &test_data, QUEUE_SEND_TO_FRONT) == 0)
{
printf("入队成功\n");
}
else
{
printf("入队失败\n");
}
if (queue_receive(uart_receive_handle, &receive_data) == 0)
{
printf("出队成功\t%d\n",receive_data);
}
else
{
printf("出队失败\n");
}
if (queue_receive(uart_receive_handle, &receive_data) == 0)
{
printf("出队成功\t%d\n", receive_data);
}
else
{
printf("出队失败\n");
}
if (queue_receive(uart_receive_handle, &receive_data) == 0)
{
printf("出队成功\t%d\n", receive_data);
}
else
{
printf("出队失败\n");
}
if (queue_receive(uart_receive_handle, &receive_data) == 0)
{
printf("出队成功\t%d\n", receive_data);
}
else
{
printf("出队失败\n");
}
if (queue_receive(uart_receive_handle, &receive_data) == 0)
{
printf("出队成功\t%d\n", receive_data);
}
else
{
printf("出队失败\n");
}
getchar();
return 0;
}
从前向后入队的效果。
从后向前入队的效果。