(四)FreeRTOS队列
在没有使用队列之前,任务之间的通信是通过共享全局变量或者传递指针参数来进行消息传递,但是全局变量一旦使用多了就会占用很大的资源,在操作系统中,这就会涉及到资源管理的问题。操作系统需要管理有限的资源,进而产生了队列,解决了任务与任务、中断与中断、任务与中断的通信问题,任务与任务、任务与中断之间要交流的数据保存在队列中,这就叫做队列项目。而队列中能存储的数据是有限的,每个数据项目大小是固定的。
操作系统使用队列有如下好处:
- 使用消息队列可以让RTOS内核有效地管理任务,而全局数组是无法做到的,任务的超时等机制需要用户自己去实现。
- 使用了全局数组就要防止多任务的访问冲突,而使用消息队列则处理好了这个问题,用户无需担心。
- 使用消息队列可以有效地解决中断服务程序与任务之间消息传递的问题。
- FIFO机制更有利于数据的处理。
数据存储
FreeRTOS有两种数据缓存机制,将数据发送到队列中,第一种是先进先出的的FIFO缓存,第二种为先进后出的类似栈的缓存机制。
数据缓存势必涉及到数据拷贝,将要发送的数据拷贝至队列中,FreeRTOS中采用的是值传递方式,在拷贝的是将整个数据拷贝到队列中,虽然这会导致时间上的浪费,但是数据一旦被拷贝至队列后,原始数据就可以重复读写。如果采用引用传递(即指针方式)将地址发送至队列,效率会比较高效,但是原始数据必须一致保持可见性,期间不能更改。很明显,在处理大量数据时,采用引用传递方式比较靠谱。
队列创建
队列创建API为xQueueCreate(x,y),x为创建队列长度,y为每个队列项的长度,通过宏定义可以知道,实际完成队列创建工作的xQueueGenericCreate()函数。
队列创建过程追踪:
队列创建完成
队列发送相关API
入队方式 |
API接口 |
实际执行函数 |
从队列尾部入队 |
xQueueSend() |
xQueueGenericSend() |
xQueueSendToBack() |
||
xQueueSendToFront() |
||
从队列首部入队 |
xQueueSendToFront() |
|
从队列尾部入队 (带中断保护) |
xQueueSendFromISR() |
xQueueGenericSendFromISR() |
xQueueSendToBackFromISR() |
||
xQueueOverWriteFromISR() |
||
从队列首部入队 (带中断保护) |
xQueueSendToFront() |
普通任务中发送数据到队列中最终是经过xQueueGenericSend()函数进行处理,带中断保护的发送函数最终是调用xQueueGenericSendFromISR()函数,不同的函数调用相同的函数进行处理,所以需要借用标识符在函数内部进行不同的处理。
在xQueueGenericSend()函数中主要完成的事情分两种情况:
第一种情况是队列未满的情况或者采用覆写的方式入队,这种情况处理比较简单,将数据拷贝至队列(FreeRTOS传递数据是通过传值方式进行),检查出对队列中是否有阻塞的任务,如果有则解除阻塞状态,如果该任务比当前运行的任务优先级高,则进行一次上下文切换,返回入队成功标志。
第二种情况是对列已满的情况,需要将调度器挂起,并且将队列上锁,因为希望任务在由于条件不满足的时候被阻塞挂在阻塞队列期间不希望被其他任务或者中断操作队列而导致原来阻塞的任务解除了,这是不符合的。接着再判断阻塞时间是否到达,如果已到达恢复调度器解锁队列,返回队列已满的信息,如果阻塞时间还未到而队列依旧是满,则只能将发送队列项挂起。
在入队函数中使用到了数据拷贝到队列的函数prvCopyDataToQueue(),在该函数中主要进行的工作是判断入队类型,如果是队列后面入队,就将数据拷贝至pcWriteTo指向的地方,如果是队列前入队,将数据拷贝至u.xQueue.pcReadFrom指向的地方,这里需注意如果传入的队列项长度为0,代表的是互斥列队类型,此时如果不使用互斥队列不能将pcWriteTo指针指向NULL,因为FreeRTOS中规定如果指向NULL代表互斥队列。
带中断保护的入队函数xQueueGenericSendFromISR()由于在中断中不能执行阻塞或者挂起操作,所以带中断保护的入队函数处理入队时比较简单,队列未满则数据拷贝进队列,队列已满则返回队列满的信息。
出队函数:
出队原理与入队差不多。