电电侠 坐公交车、办理业务等经常需要排队,计算机系统中也有类似的“队列“概念吗?
当然。队列被用作进程之间的通信管道,通常是一对一的,如下图所示。
科科君
任务 A 将信息存入队列,任务B以先进先出的方式提取信息。队列通常应足够大,可以承载许多数据,而不仅仅承载单个数据项。因此,它可以充当缓冲或暂存器,为管道提供灵活性。它的优点是插入和提取功能可以异步进行(只要管道没有填满)。它在 RAM 中实现。进程之间传递的信息可能是数据本身,也可能是指向数据的指针。指针通常用于在 RAM 存储受限时处理大量数据。实现队列的技术有两种:链表类型结构和循环缓冲区。
电电侠 想了解下链表的特性。 链表的一个非常有用的特性是它的大小不一定是固定的,而是可以根据需要扩大或缩小。 科科君
此外,可以构建非常大的队列,仅受可用内存空间的限制。但是对于嵌入式系统来说,这些并不是特别的优势。首先,如果 RAM 有限,则根本不可能构造很大的队列。其次,处理多个消息的大型FIFO队列从数据输入到数据输出可能会有较长的传输延迟,就性能而言,对于许多实时应用可能太慢。因此,用于嵌入式的首选队列(通道)结构是循环缓冲区,如下图所示。
电电侠 循环缓存区应该如何设计?
循环缓冲区通常设计为使用固定数量的内存空间,用来保存一定数量的数据,如上图(a) 所示。
科科君
缓冲区大小是在创建时定义的(例如这里是10个数据单位),但在之后是固定的。使其循环的原因是数据单元0是数据单元9的后继,寻址是使用模9计数器完成的(就像12小时时钟使用模12计算一样)。
电电侠 在读写操作期间,数据如果在通道中移动,时间开销增加怎么办?
但是,一般来说这会带来不可接受的时间开销。
科科君
这里使用另一种方法,上图(b)展示了如何使用指针来标识存储数据的起始和结束位置(“读取者”和“发送者”)。通过指针,不必在缓冲区中移动数据。插入的数据单元始终位于相同的内存位置,仅需改变指针的值,图(c)和图(d)所示。这些指针也可以用来定义队列满和队列空的条件(当它们相等时)。
电电侠 什么情况下任务会挂起?
在正常情况下,任务 A 和任务 B异步进行,根据需要从队列中插入和删除数据。任务挂起只在两种情况下发生:队列满和队列空。
科科君
小贴士:内存池和队列之间有一个重要的区别———内存池读取数据不会影响内容,但是从队列读取时会“消耗”数据,即破坏性操作(实际上这只是概念性看法,读指针只是移到了下一个位置)。
队列使用的概要
下方代码清单给出了队列使用的概要。 科科君 01
/* 基础API */
/* 1. 创建队列 */
FOS_CreateQueue(QLength, QItemSize);
/* 2. 从队列获取消息 */
FOS_GetFromQueue(QName, AddOfQData, QwaitingTime);
/* 3. 向队列发送消息 */
FOS_SendToQueue(QName, AddOfQData, QwaitingTime);
02
/* 创建一个全局的队列联结发送任务A和接收任务B */
/* 使用RTOS提供的数据类型 */
FOS_QName GlobalQA2B;
FOS_QLength QA2Blength = 1;
FOS_ItemSize QA2BItemSize = 4;
GlobalQA2B = FOS_CreateQueue (QA2Blength, QA2BItemSize);
03
/* 发送到队列-任务A */
/* 使用RTOS提供的数据类型 */
long DataForQueueA2B;
const FOS_QwaitTime NoWaiting = 0;
FOS_ QloadStatus QLoadState;
QLoadState = FOS_SendToQueue (GlobalQA2B, &DataForQueueA2B, 0);
04
/* 从队列获取-任务B */
/* 使用RTOS提供的数据类型 */
long DataFromQueueA2B;
const FOS_QwaitTime NoWaiting = 0;
FOS_QreadStatus QreadState;
审核编辑:黄飞
评论
查看更多