在实际的应用中,常常会遇到一个任务或者中断服务需要和另外一个任务进行“沟通交流”,这个“沟通交流”的过程其实就是消息传递的过程。在没有操作系统的时候两个应用程序进行消息传递一般使用全局变量的方式,但是如果在使用操作系统的应用中用全局变量来传递消息就会涉及到“资源管理”的问题。FreeRTOS 对此提供了一个叫做“队列”的机制来完成任务与任务、任务与中断之间的消息传递。
任务与任务之间消息传递的常见场景有业务数据的缓存和使用、标志位的设置和使用等,裸机中都是使用全局变量,到了操作系统环境中,就不必使用全局变量了,而是使用系统提供的一种机制——队列——来实现,内部原理肯定也是使用的全局变量,只不过这些全局变量由操作系统进行合理地管理了。可以说,操作系统本身就是一种程序架构。
注意区分列表和队列。列表一般被叫做任务列表,队列一般被叫做消息队列。
全局变量有什么问题呢?
因为全局变量底层实际是要进行读改写的操作的,在多任务的场景下,全局变量可能会在一个任务中还没完成操作,就被下一个任务打断,这样,就会导致某些程序获取到的数据是不对的。
而队列在实现时,加入了临界区保护
因此,全局变量只适合在裸机环境下使用,因为裸机环境下不存在任务之间的中途切换问题。在系统环境下,就需要使用队列来进行消息传递。
队列简介
队列是为了任务与任务、任务与中断之间的通信而准备的,可以在任务与任务、任务与中断之间传递消息,队列中可以存储有限的、大小固定的数据项目。任务与任务、任务与中断之间要交流的数据保存在队列中,叫做队列项目。队列所能保存的最大数据项目数量叫做队列的长度,创建队列的时候会指定数据项目的大小和队列的长度。由于队列用来传递消息的,所以也称为消息队列。
FreeRTOS中的队列的队列与平时数据结构中所说的队列大同小异,只不过在FreeRTOS中将队列做了一个扩展,就是队列既可以先进先出(FIFO)也可以先进后出(FILO),即它兼具了数据结构中的队列和栈。
FreeRTOS的队列是一个循环队列,也就是一个循环缓冲区。
我们一般使用的就是常规的队列模式,即队尾入队,队头出队,FIFO先进先出,这里的队头队尾并不是指的哪个具体方向,而是队头指针和队尾指针,也就是读指针和写指针。
队列的特点
接下来详细说明。
值传递
通常队列采用先进先出(FIFO)的存储缓冲机制,也就是往队列发送数据的时候(也叫入队)永远都是发送到队列的尾部,而从队列提取数据的时候(也叫出队)是从队列的头部提取的。但是也可以使用 LIFO 的存储缓冲,也就是后进先出,FreeRTOS 中的队列也提供了 LIFO 的存储缓冲机制。
数据发送到队列中会导致数据拷贝,也就是将要发送的数据拷贝到队列中,这就意味着在队列中存储的是数据的原始值,而不是原数据的引用(即只传递数据的指针),这个也叫做值传递。学过UCOS 的同学应该知道,UCOS 的消息队列采用的是引用传递,传递的是消息指针。采用引用传递的话消息内容就必须一直保持可见性,也就是消息内容必须有效,那么局部变量这种可能会随时被删掉的东西就不能用来传递消息,但是采用引用传递会节省时间啊!因为不用进行数据拷贝。
采用值传递的话虽然会导致数据拷贝,会浪费一点时间,但是一旦将消息发送到队列中原始的数据缓冲区就可以删除掉或者覆写,这样的话这些缓冲区就可以被重复的使用。FreeRTOS中使用队列传递消息的话虽然使用的是数据拷贝,但是也可以使用引用来传递消息啊,我直接往队列中发送指向这个消息的地址指针不就可以了!这样当我要发送的消息数据太大的时候就可以直接发送消息缓冲区的地址指针,比如在网络应用环境中,网络的数据量往往都很大的,采用数据拷贝的话就不现实。
多任务访问
队列不是属于某个特别指定的任务的,任何任务都可以向队列中发送消息,或者从队列中提取消息。就好像全局变量,哪个函数都能访问。只不过,不同的函数访问的是不同的全局变量,同理,我们可以为不同的任务指定不同的队列。
出队阻塞
当任务尝试从一个队列中读取消息的时候可以指定一个阻塞时间,这个阻塞时间就是当任务从队列中读取消息无效的时候任务阻塞的时间。出队就是就从队列中读取消息,出队阻塞是针对从队列中读取消息的任务而言的。比如任务 A 用于处理串口接收到的数据,串口接收到数据以后就会放到队列 Q 中,任务 A 从队列 Q 中读取数据。但是如果此时队列 Q 是空的,说明还没有数据,任务 A 这时候来读取的话肯定是获取不到任何东西,那该怎么办呢?任务 A 现在有三种选择,一:二话不说扭头就走,二:要不我在等等吧,等一会看看,说不定一会就有数据了,三:死等,死也要等到你有数据!选哪一个就是由这个阻塞时间决定的,这个阻塞时间单位是时钟节拍数。阻塞时间为 0 的话就是不阻塞,没有数据的话就马上返回任务继续执行接下来的代码,对应第一种选择。如果阻塞时间为 0~portMAX_DELAY,当任务没有从队列中获取到消息的话就进入阻塞态,阻塞时间指定了任务进入阻塞态的时间,当阻塞时间到了以后还没有接收到数据的话就退出阻塞态,返回任务接着运行下面的代码,如果在阻塞时间内接收到了数据就立即返回,执行任务中下面的代码,这种情况对应第二种选择。当阻塞时间设置为portMAX_DELAY 的话,任务就会一直进入阻塞态等待,直到接收到数据为止!这个就是第三种选择。
入队阻塞
入队说的是向队列中发送消息,将消息加入到队列中。和出队阻塞一样,当一个任务向队列发送消息的话也可以设置阻塞时间。比如任务 B 向消息队列 Q 发送消息,但是此时队列 Q 是满的,那肯定是发送失败的。此时任务 B 就会遇到和上面任务