传说互联网应用有两大利器,一个是缓存,另一个就是消息队列。 一直相对消息队列做一下梳理,希望早日另有成文。 一叶知秋,实际上消息队列在嵌入式系统中同样有着广泛的应用。 近来致力于IoT和智能硬件,现学习一下消息队列在RTOS中的应用场景。
RTOS是一个管理CPU的软件, 即微处理单元(MPU) , 还可能管理高效的DSP。 大多数 RTOS 内核是用 c 语言编写的, 同时需要用汇编语言编写一小部分代码来适应不同的 CPU 架构。一个 RTOS 内核为开发者提供了许多有用的服务, 如多任务处理、中断管理、通过消息队列、信号量、资源管理、时间管理、内存分区管理等等。
应用程序或者最终地产品基本上会划分为多个任务, 每个任务负责应用程序的一部分。 一个任务就是一个简单的程序, 它有自己的 CPU使用时间。 根据任务的重要性, 每个任务都被赋予了优先级。
RTOS中的消息队列
rtosmq-1.png
图1 消息队列是用于将内容传递给任务的内核对象
如图1所示, 消息队列是一个核心对象(即数据结构) , 通过这个对象, 消息从中断服务例程(ISR)或任务发送到另一个任务。 应用程序可以有任意数量的消息队列, 每一个都有自己的目的和用途。 例如, 消息队列可以用来将从通信接口 ISR 接收到的数据包传递给一个任务, 而 ISR 则负责处理数据包。 另一个队列可以用来将内容传递给显示任务, 该任务将负责正确地更新显示的内容。
消息通常是指向包含实际消息存储区域的void指针。 这些指针可以指向任何东西, 甚至可以指向接收任务执行的函数。 因此, 它的实际含义取决于应用程序。 每个消息队列都可以根据它将保存的存储量进行配置。 消息队列可以配置为保存单个消息或者 n 个消息。 队列的大小取决于应用程序以及消费者的处理速度。
如果一个任务等待一条消息, 并且队列中没有消息, 则该任务将被挂起, 直到有消息发送到队列中。 等待任务不消耗 CPU 时间, RTOS 可以运行其他任务。 如图1所示, 挂起的任务可以指定超时。 如果在指定时间内未收到消息, 则当该任务成为最高优先任务时, 将允许任务恢复执行(即 unblock)。 当任务执行时, 基本上会被告知它恢复的原因是因为超时。
消息队列通常作为先入先出(FIFO)实现, 这意味着收到的第一个消息将是从队列中提取的第一个消息。 然而, 有些内核允许发送被认为比其他内核更重要的消息, 从而排在队列的首位。 换句话说, 在"先入先出"顺序中, 使该消息成为任务提取的第一条信息。
消息队列的另一个重要方面是, 消息本身需要保持从发送到处理的时间范围。 这意味着不能将指针传递给栈变量等等。 为了将消息保持在作用域中, 通常会填充一个结构, 并从这些消息池中获取, 如图2所示。
rtosmq-1.png
图2 消息池的存储区域
发送消息的 ISR 或任务将从池中获取结构, 填充结构, 并将指针指向队列的结构。 接收任务将从队列中提取指针, 处理结构, 完成后将结构返回到池中。 当然, 发送方和接收方都需要使用相同的池, 除非数据结构中的字段指示使用了哪个池。
在 RTOS 中的许多消息队列实现中, 如队列已满, 则发送到队列的消息将被丢弃。 通常这不是一个问题, 应用程序的逻辑可以从这种情况中恢复。 然而, 如图3所示, 实现一个发送任务会发送消息的机制是相当容易的:
rtosmq-3 block.png
图3 如果队列已满, 则阻止发送
- 计数信号的初始化值与队列可接受的最大条目数相对应
- 发送任务在允许将消息发送到队列之前检查信号量。 如果信号量值为零, 则发送方等待。
- 如果值为非零, 则信号量减少, 发送方将消息发送到队列中
- 消息的接收方像往常一样将消息队列分隔开来
- 当收到消息时, 接收这将指针从队列中取出并向信号量发出信号, 表示队列中的条目已被释放
如图所示, 这个机制只适用于两个任务, 因为 ISR 不允许在信号量上分配信号。
消息队列的典型用法
图4显示了消息队列的不同用法:
rtosmq-4 usage.png
1-4. 消息队列通常用于从 ISR 发送消息或将任务发送到另一个任务, 如前面所讨论的那样
5.但是, 如果消息符合指针的大小, 则不必发送实际消息及分配存储区域。 例如, 如果一个32位指针, 那么可以将模拟转换器(ADC)从一个12位 ADC 读取到一个指针, 并通过消息队列发送,只要接收这知道将值返回整数即可, 这是完全合法的
6-7 一个任务如果知道这些消息将不会发送给它的,可以使用超时机制在一定的时间内延迟自己。 在这种情况下, 一个能够保存单个条目的队列就足够了。 事实上, 如果另一个任务或 ISR 发送消息, 那么延迟将被终止, 这可能也是想要实现的行为。
8 消息队列可以作为一个信号量来简单地向事件发生的任务发出信号。 在这种情况下, 信息可以是任何东西。 队列的大小取决于应用程序需要缓冲多少信号。
9-10 消息队列也可以用作二进制信号量或用于资源共享的计数信号量。 对于二进制信号量, 队列将包含单个消息, 并在队列中放置消息(任何值)。 为了访问资源, 可以在队列上分配一个任务。 如果队列中有消息, 则该任务将获得对资源的访问。 一旦使用了资源, 队列就会被发布, 从而根据需要放弃资源, 供其他任务使用。 同样的机制也适用于使用 n 个资源实现计数信号, 队列将预先填入n个虚拟消息。
11.消息实际上可以用来模拟事件标志, 其中32位指针大小的变量中,每一位可以代表一个事件
12.消息队列可以用来实现栈结构,这基本上是 LIFO 机制的另一个用途。
总而言之,消息队列有许多不同方式的使用场景。 事实上, 通过消息队列,可以编写相当复杂的应用程序。 使用消息队列可以减少代码的大小, 可以被模拟(信号、时间延迟和事件标志)许多其他的服务。