1. 队列一般传递的不是单个整型数据或者字符型数据,而是传递结构体或者内存块,一块内存的指针
2. 定义结构体数据类型
/* 定义队列传递的结构类型。 */
typedef struct {
unsigned char ucValue;
unsigned char ucSource;
} xData;
/* 声明两个xData类型的变量,通过队列进行传递。 */
static const xData xStructsToSend[ 2 ] = {
{ 100, mainSENDER_1 }, // task1 传递的结构体参数
{ 200, mainSENDER_2 } // task2 传递的结构体参数
};
3. 往队列里面写入结构体参数
static void vSenderTask(void *pvParameters)
{
xData lValueToSend;
portBASE_TYPE xStatus;
lValueToSend = (xData)pvParameters; // 两个任务都使用这个函数vSenderTask,并在此接收各自的实参
for(;;)
{
/* 往队列发送数据
第一个参数是要写入的队列
第二个参数是被发送数据的地址
第三个参数是阻塞超时时间: 当队列满时,任务转入阻塞状态以等待队列空间有效。本例中没有设定超时时间,因为此队列决不会保持有超过一个数据单元的机会,所以也决不会满。
*/
xStatus = xQueueSendToBack(xQueue, &lValueToSend, 0);
if (xStatus != pdPASS)
{
/* 发送操作由于队列满而无法完成 – 这必然存在错误,因为本例中的队列不可能满。 */
printf("Could not send to the queue.\r\n");
}
/* 允许其它发送任务执行。 taskYIELD()通知调度器现在就切换到其它任务,而不必等到本任务的时间片耗尽 */
taskYIELD();
}
}
4. 从队列中读出结构体
static void vReceiverTask( void *pvParameters )
{
/* 声明结构体变量以保存从队列中读出的数据单元 */
xData xReceivedStructure;
portBASE_TYPE xStatus;
/* This task is also defined within an infinite loop. */
for( ;; )
{
/* 读队列任务的优先级最低,所以其只可能在写队列任务阻塞时得到执行。而写队列任务只会在队列写满时才会进入阻塞态,所以
读队列任务执行时队列肯定已满。所以队列中数据单元的个数应当等于队列的深度 – 本例中队列深度为3 */
if( uxQueueMessagesWaiting( xQueue ) != 3 )
{
printf( "Queue should have been full!\r\n" );
}
/* Receive from the queue.
第二个参数是存放接收数据的缓存空间。本例简单地采用一个具有足够空间大小的变量的地址。
第三个参数是阻塞超时时间 – 本例不需要指定超时时间,因为读队列任会只会在队列满时才会得到执行,故而不会因队列空而阻塞 */
xStatus = xQueueReceive( xQueue, &xReceivedStructure, 0 );
if( xStatus == pdPASS )
{
/* 数据成功读出,打印输出数值及数据来源。 */
if( xReceivedStructure.ucSource == mainSENDER_1 )
{
printf( "From Sender 1 = %d\r\n", xReceivedStructure.ucValue );
}
else
{
printf( "From Sender 2 = %d\r\n", xReceivedStructure.ucValue );
}
}
else
{
/* 没有读到任何数据。这一定是发生了错误,因为此任务只支在队列满时才会得到执行 */
printf( "Could not receive from the queue.\r\n" );
}
}
}
5. main 函数中创建队列 & 写任务1 & 写任务2 & 读任务
写任务优先级比读任务优先级高,所以会先一直写,知道队列里面写满了数据,写任务阻塞,然后才开始读,读走一个数据,读任务立马被写任务抢占,又写一个数据进去,循环写,读
int main( void )
{
/* 创建队列用于保存最多3个xData类型的数据单元。 */
xQueue = xQueueCreate( 3, sizeof( xData ) );
if( xQueue != NULL )
{
/* 为写队列任务创建2个实例。 任务入口参数用于传递发送到队列中的数据。因此其中一个任务往队列中一直写入xStructsToSend[0],而另一个则往队
列中一直写入xStructsToSend[1]。这两个任务的优先级都设为2,高于读队列任务的优先级 */
xTaskCreate( vSenderTask, "Sender1", 1000, &(xStructsToSend[0]), 2, NULL );
xTaskCreate( vSenderTask, "Sender2", 1000, &(xStructsToSend[1]), 2, NULL );
/* 创建读队列任务。读队列任务优先级设为1,低于写队列任务的优先级。 */
xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 1, NULL );
vTaskStartScheduler();
}
else
{
/* 创建队列失败。 */
}
/* 如果一切正常,main()函数不应该会执行到这里。但如果执行到这里,很可能是内存堆空间不足导致空闲任务无法创建。第五章将提供更多关于内存管理方面的信息 */
for( ;; )
{
}
}
6. 输出结果
7. 任务的执行流程,切换过程
优先级高的会抢占优先级低的,老二在睡觉,老大一脚踢走,老大要睡觉了
优先级一样的,等待时间最久的优先执行
8. 用队列来传递大型数据的时候,最好使用指针,指向一块内存的指针,把指针传来传去就可以了,然后再通过指针来操作里面的任一数据,常用的做法,速度,效率,节省内存,还方便
8.1 指针指向的内存空间的所有权必须明确
当任务间通过指针共享内存时,应该从根本上保证所不会有任意两个任务同时修改共享内存中的数据,或是以其它行为方式使得共享内存数据无效或产生一致性问题。原则上,共享内存在其指针发送到队列之前,其内容只允许被发送任务访问;共享内存指针从队列中被读出之后,其内容亦只允许被接收任务访问
8.2 指针指向的内存空间必须有效
如果指针指向的内存空间是动态分配的,只应该有一个任务负责对其进行内存释放。当这段内存空间被释放之后,就不应该有任何一个任务再访问这段空间。
切忌用指针访问任务栈上分配的空间。因为当栈帧发生改变后,栈上的数据将不再有效。