堆是向上生长,栈是向下生长。
队列简介
队列介绍
队列是一种任务到任务、任务到中断、中断到任务数据交流的一种机制,简单来说就是用来传递消息的。
在裸机中虽然我们用全局变量也可以进行传递消息,但是由于FreeRTOS我们创建多个任务时,全局变量会出现在其他任务中修改的问题,也就是数据是无保护,易受损的。
通过读写队列进行消息传递的话,FreeRTOS对读写队列已经做好了保护,可以防止多任务同时访问的冲突(其实就是加入了临界区)。
基于队列, FreeRTOS 实现了多种功能,其中包括队列集、互斥信号量、计数型信号量、二值信号量、 递归互斥信号量。
队列示意图如下,
创建队列时,需要规定队列的长度和每个队列项目的大小。
队列特点
- 数据入队出队:队列通常采用 FIFO(先进先出)的存储缓冲机制,,先入队的先读出(First Input First Output),当然也可以配置成LIFO方式。
- 数据传递:FreeRTOS采用实际值传递,也就是拷贝数据传递,当然,也可以传递指针(传递大数据)。
- 多任务访问:队列不属于某个特定的任务,可以在任何的任务或中断中往队列中写入消息,或者从队列中读取消息。
- 出入队阻塞:在任务从队列读取消息时,可以指定一个阻塞超时时间,出入队阻塞相似
- 阻塞时间为0:直接返回不等待
- 阻塞时间0-port_MAX_DELAY:等待设定的阻塞时间,超时直接返回
- 阻塞时间port_MAX_DELAY:死等,一直等到入队为止
备注:当多个任务给一个满队列写数据,优先级高的先进入就绪态,优先级相同的话,等待时间最久的任务进入就绪态。(出队时也一样)
队列操作过程
队列操作过程分为四步,如下,
队列结构体
队列结构体的结构如下所示,
typedef struct QueueDefinition
{
int8_t * pcHead; /* 存储区域的起始地址 */
int8_t * pcWriteTo; /* 下一个写入的位置 */
/* 信号量是由队列实现的,
* 此结构体能用于队列和信号量,
* 当用于队列时,使用联合体中的 xQueue,
* 当用于信号量时,使用联合体中的 xSemaphore
*/
union
{
QueuePointers_t xQueue;
SemaphoreData_t xSemaphore;
} u;
List_t xTasksWaitingToSend; /* 写入阻塞任务列表,也叫等待发送列表 */
List_t xTasksWaitingToReceive; /* 读取阻塞任务列表,等待接收列表 */
volatile UBaseType_t uxMessagesWaiting; /* 非空闲队列项目的数量 */
UBaseType_t uxLength; /* 队列的长度 */
UBaseType_t uxItemSize; /* 队列项目的大小 */
/* 锁用于在任务因队列操作被阻塞前,防止中断或其他任务操作队列。
* 上锁期间,队列可以写入和读取消息,但不会操作队列阻塞任务列表,
* 当有消息写入时, cTxLock 加 1,当有消息被读取时, cRxLock 加 1,
* 在解锁时,会统一处理队列的阻塞任务列表
*/
volatile int8_t cRxLock; /* 读取上锁计数器 */
volatile int8_t cTxLock; /* 写入上锁计数器 */
/*其他一些条件编译*/
} xQUEUE;
在队列的结构中,有两个联合体 QueuePointers_t和 SemaphoreData_t分别表示用于队列还是信号量,他们的结构分别如下,
typedef struct QueuePointers
{
int8_t * pcTail; /* 存储区域的结束地址 */
int8_t * pcReadFrom; /* 最后一次读取队列的位置 */
} QueuePointers_t;
typedef struct SemaphoreData
{
TaskHandle_t xMutexHolder; /* 互斥信号量的持有者 */
UBaseType_t uxRecursiveCallCount; /* 递归互斥信号量被递归获取计数器 */
} SemaphoreData_t;
总体的队列示意图如下,
队列相关函数
创建队列
xQueueCreate():
#define xQueueCreate( uxQueueLength,
uxItemSize )
xQueueGenericCreate( ( uxQueueLength ),
( uxItemSize ),
( queueQUEUE_TYPE_BASE ))
参数:
- uxQueueLength:队列长度
- uxItemSize:队列项目大小
返回值:如果队列创建成功,则返回队列起始地址;创建失败,返回NULL。
可以看到,队列创建函数xQueueCreate()是基于xQueueGenericCreate()函数创建的,在这个函数中多了一个宏 queueQUEUE_TYPE_BASE,用来代表创建不同功能,介绍如下,
#define queueQUEUE_TYPE_BASE ( ( uint8_t ) 0U ) /* 队列 */
#define queueQUEUE_TYPE_SET ( ( uint8_t ) 0U ) /* 队列集 */
#define queueQUEUE_TYPE_MUTEX ( ( uint8_t ) 1U ) /* 互斥信号量 */
#define queueQUEUE_TYPE_COUNTING_SEMAPHORE ( ( uint8_t ) 2U ) /* 计数型信号量 */
#define queueQUEUE_TYPE_BINARY_SEMAPHORE ( ( uint8_t ) 3U ) /* 二值信号量 */
#define queueQUEUE_TYPE_RECURSIVE_MUTEX ( ( uint8_t ) 4U ) /* 递归互斥信号量 */
队列写入消息
对于队列写入函数,看起来很多,其实分为两类,任务级和中断级,尾缀是FromISR的表示中断级的。
函数如下,
队列写入消息的方式分为三种,尾部写入,头部写入和覆写。
从上述的队列写入消息函数来看,我们可以看到,所有的写于函数都是调用同一个函数xQueueGenericSend(),只是指定了不同的写入位置。
参数:
- xQueue:待写入队列
- pvItemToQueue:待写入的消息
- xTicksToWait:阻塞超时时间
返回值为pdTRUE时,则表示队列写入成功;否则表示写入失败。
备注:覆写时,队列中只有一个队列项目,写入消息时将之前的消息覆盖,所以不需要阻塞时间。
队列读取消息
xQueueReceive():
BaseType_t xQueueReceive( QueueHandle_t xQueue,
void * const pvBuffer,
TickType_t xTicksToWait );
参数:
- xQueue:待读取的队列
- pvBuffer:消息读取缓冲区,其实就是一个内存
- xTicksToWait:阻塞超时时间
返回pdTRUE表示读取成功;反之读取失败。
函数xQueuePeek()的参数和xQueueReceive()一样,区别就是,此函数在成功读取消息后,并不会移除已读取的消息。
函数内部实现
队列创建
队列创建函数内部总结如下,
在前三步以后,最后调用函数prvInitialiseNewQueue()初始化新队列pxNewQueue,过程如下,
- 初始化其他队列结构体成员变量
- 判断要复位的队列是否为新创建的队列,
队列消息写入
队列消息写入时,首先进入临界区(关闭中断),然后判断队列是否已满,如果有空闲位置的话,进行下面的操作,
在函数xTaskRemoveFromEventList中,判断调度器是否挂起,如下,
如果队列已满的话,进行以下操作,
队列消息读取
队列消息读取时,首先进入临界区(关闭中断),然后判断队列是否为空,如果有数据的话,进行下面的操作,
在函数xTaskRemoveFromEventList中,判断调度器是否挂起,如下,
当没有数据,也就是为空时,进行以下操作,
以上就是三个API函数内部源码总结,建议朋友们还是对照源码阅读效果更好哈!!