队列的进阶
一、队列、消息队列
队列是什么,对于程序员都不会陌生,在单片机开发的时候,也会有接触。比如串口,有些MCU的串口为了高效自带FIFO功能。队列简单易用,在物联网下基于RTOS的开发中更是显神通,称为消息队列。
消息队列在FreeRTOS下所有的通信与同步机制都是基于队列实现的。用法和创建任务类似,独立于任务,也是RTOS下核心的一个组件。主要有几个特性:
1.缓冲数据:和普通队列一样
关注溢出,RTOS下数据的大小和队列的深度(个数)应该是已知的。不允许装入超出创建大小范围的数据。比如创建的时候队列大小为128个字节,个数为3。而实际装入的数据超过128,那么就给引入hardfault或者堆栈异常等问题。
2.多任务安全调用
关注安全,系统允许用户多任务对一个队列写入内容,至于队列本身的安全由系统保证。
3.阻塞
读空或写入满的时候会阻塞,可以用来把任务挂起,这个功能和消息队列的用法是一样的。
4.地址传输
如果需要传输的数据比较大。比如超过512/1024个字节,且经常要传输的数据。可以考虑传输地址的操作。FreeRTOS下的头文件中已经给出了示例,如下:
struct AMessage
{
char ucMessageID;
char ucData[ 20 ];
} xMessage;
QueueHandle_t xQueue;
// Task to create a queue and post a value.
void vATask( void *pvParameters )
{
struct AMessage *pxMessage;
// Create a queue capable of containing 10 pointers to AMessage structures.
// These should be passed by pointer as they contain a lot of data.
xQueue = xQueueCreate( 10, sizeof( struct AMessage * ) );
if( xQueue == 0 )
{
// Failed to create the queue.
}
// ...
// Send a pointer to a struct AMessage object. Don't block if the
// queue is already full.
pxMessage = & xMessage;
xQueueSend( xQueue, ( void * ) &pxMessage, ( TickType_t ) 0 );
// ... Rest of task code.
}
// Task to receive from the queue.
void vADifferentTask( void *pvParameters )
{
struct AMessage *pxRxedMessage;
if( xQueue != 0 )
{
// Receive a message on the created queue. Block for 10 ticks if a
// message is not immediately available.
if( xQueueReceive( xQueue, &( pxRxedMessage ), ( TickType_t ) 10 ) )
{
// pcRxedMessage now points to the struct AMessage variable posted
// by vATask.
}
}
// ... Rest of task code.
}
二、物联网场景
为了方便区分以下列出一些使用场景:
1.上行入队,统一处理
IOT场景下往往和服务器保持长连接。长连接除了要负责心跳外,还有来自不同传感器的数据,串口的数据上报等。心跳和传感器,串口数据可能来不同的任务,中断等。那么这些数据都通过在不同的任务下发给一个队列。统一由一个任务取出队列数据,进行上行。
这样做有几个好处:不会存在入队的临界问题。统一上行系统稳定可靠,可重用。多任务调用网络接口不同的传输形式往往要求不同。数据是否上行可以标记。坏处就是效率相对低。
2.普通队列的进阶
普通队列的最大的问题是不可以被多个任务写,当然读也是不行的(应该没人这么干吧)。参考单字节入队的形式改为有限个动态内存分配的入队模式,出队的时候释放内存。如果能够保证业务允许在消费者模型的种场景中实现,这种模型可以大大提高任务调度的效率。
三、模型设计
1. 根据以上设计模型给出如下
//创建一个处理消息队列任务
void Task_mian_upload()
{
while(1)
{
// 等待消息队列
if( xQueueReceive( xQueue, &( pxRxedMessage ), ( TickType_t ) 10 ) )
{
// do something.
send(…); //发送来自pxRxedMessage的数据到服务器
}
…
}
}
void Task_mian_upload()
{
while(1)
{
// 等待消息队列
if( xQueueReceive( xQueue, &( pxRxedMessage ), ( TickType_t ) 10 ) )
{
// do something.
send(…); //发送来自pxRxedMessage的数据到服务器
}
…
}
}
void Take_1_send_Q()
{
while(1)
{
…
if(true == check_temp()) // 定时检查温度
{
// 发送温度值到网络
xQueueSend (xQueue ,temp); // 发送消息队列
}
…
}
}
// 中断中触发事件
void _ISR1()
{
//发送事件类型消息队列
xQueueSendFromISR(xQueue,…);
}
以上第一个任务集中处理来自其他任务或者中断的消息队列,其他任务会根据任务或者事件的触发情况发送数据到消息队列中。第一个任务将接收到的消息队列内处理后,发送到网络中。