一 FreeRTOS的链表节点
1. 链表节点数据结构
/* 节点结构体定义 */
struct xLIST_ITEM
{
TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */
struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
void * pvOwner; /* 指向拥有该节点的内核对象,通常是TCB */
void * pvContainer; /* 指向该节点所在的链表 */
};
typedef struct xLIST_ITEM ListItem_t; /* 节点数据类型重定义 */
链表数据结构成员变量分析:
TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */
// 关于TickType_t的定义
#if( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffff
#else
typedef uint32_t TickType_t;
#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif
TickType_t,顾名思义,就是系统滴答的类型。在stm32中,使用的系统滴答定时器为24位,因此要定义成32位类型(16位不够用),TickType_t 为 16 位,否则为32 位。
在 FreeRTOSConfig.h(FreeRTOSConfig.h 第一次使用需要在 include 文件夹下
面新建然后添加到工程 freertos/source 这个组文件)中默认定义为 0
xItemValue中保存的是当前任务需要的延时值。在延时列表中,各任务根据延时大小(xItemValue值)按升序排列
void * pvOwner; /* 指向拥有该节点的内核对象,通常是TCB */
pvOwner
保存当前任务的任务控制块,可以通过访问TCB从延时列表中恢复任务至就绪列表。
void * pvContainer; /* 指向该节点所在的链表 */
== pvContainer==
通过这个变量,可以知道节点位于就绪列表、延时列表或其他。
2 链表节点初始化
链表节点 ListItem_t 总共有 5 个成员,但是初始化的时候只需将
pvContainer 初始化为空即可,表示该节点还没有插入到任何链表。一个初始化好的节点示
/* 节点初始化 */
void vListInitialiseItem(ListItem_t *const pxItem)
{
pxItem->pvContainer = NULL;
}
二 链表根节点
1 链表根节点数据结构体:
链表根节点示意图
根节点是链表的第一个节点,也是最后一个节点,也叫生产者
/* 链表结构体定义 */
typedef struct xLIST
{
UBaseType_t uxNumberOfItems; /* 链表节点计数器 */
ListItem_t * pxIndex; /* 链表节点索引指针 */
MiniListItem_t xListEnd; /* 链表最后一个节点 */
} List_t;
分析链表根节点结构体的成员:
UBaseType_t uxNumberOfItems; 链表节点计数器,用于表示该链表下有多少个节点,根节点除外
ListItem_t * pxIndex; 链表节点索引指针,用于遍历节点。
MiniListItem_t xListEnd; 链表最后一个节点。我们知道,链表是首尾相连的,是一个圈,首
就是尾,尾就是首,这里从字面上理解就是链表的最后一个节点,实际也就是链表的第一
个节点,我们称之为生产者。该生产者的数据类型是一个精简的节点,
链表精简节点结构体定义
struct xMINI_LIST_ITEM
2 {
3 TickType_t xItemValue; /* 辅助值,用于帮助节点做升序排列 */
4 struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
5 struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
6 };
7 typedef struct xMINI_LIST_ITEM MiniListItem_t; /* 精简节点数据类型重定义 */
2 链表根节点初始化
代码看上去非常晦涩难懂,其实所做的工作就是,把索引指针指向最后一个节点,然后把最后一个节点的前指针和后指针都指向自身,并将节点计数器清零,表示链表为空(没有任务)。
/* 链表初始化 */
void vListInitialise(List_t * const pxList)
{
/* 链表初始化,节点指向NULL,或结束地址
FreeRTOS中是指向结束地址,并强制类型转换为指针 */
pxList->pxIndex = (ListItem_t *)&(pxList->xListEnd);
/* 将链表最后一个节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点 */
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* 将最后一个节点的pxNext和pxPrevious指针均指向节点自身,表示链表为空 */
pxList->xListEnd.pxNext = (ListItem_t *)&(pxList->xListEnd);
pxList->xListEnd.pxPrevious = (ListItem_t *)&(pxList->xListEnd);
/* 初始化链表节点计数器的值为0,表示链表为空 */
pxList->uxNumberOfItems = (UBaseType_t) 0U;
}
初始化完成后,根节点如下图所示:
三 链表的尾部插入新节点:
在链表初始化后,pxList->pxIndex指向最小mini节点(链表中没有节点时,mini节点的next和previous均指向自身),而此时如果插入一个节点1,就会变成mini节点的next指向节点1,而由于是双向链表,mini节点的previous也指向节点1。同理,节点1的next和previous都指向mini节点。
整个过程中,由于没有修改pxList->pxIndex,pxList->pxIndex一直指向mini节点。
但pxList->pxIndex->next指向了节点1,pxList->pxIndex->next->next指向节点2…
可以通过pxList->pxIndex来访问链表中的所有节点,怎么实现?
已经知道的是,pxList->pxIndex指向mini节点,如上图所示。如果ListItem_t * const pxIndex = pxList->pxIndex,那么pxIndex的next节点就是节点1(pxIndex->next),节点1的next就是节点2(pxIndex->next->next),这样就可以访问整个链表。
操作起来就是,pxIndex = pxList->pxIndex(此时指向mini最小节点),如果设置pxIndex = pxIndex->next,则pxIndex指向了节点1,继续pxIndex = pxIndex->next,则pxIndex指向了节点2,以此类推遍历列表。
1 插入节点
/**
* @brief 将节点插入到链表的尾部
* @param pxList 列表
* @param pxNewListItem 新列表项
*/
void vListInsertEnd(List_t * const pxList, ListItem_t * const pxNewListItem)
{
// 获取链表的尾部(mini节点)
ListItem_t * const pxIndex = pxList->pxIndex;
// 新节点的下一个节点指向end
pxNewListItem->pxNext = pxIndex;
// 新节点的上一个节点指向原end的上一个节点
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
// 上一个节点的下一个节点和新节点连接
pxIndex->pxPrevious->pxNext = pxNewListItem;
// 尾部节点的上一个节点和新节点连接
pxIndex->pxPrevious = pxNewListItem;
/* 记住该节点所在的链表 */
pxNewListItem->pvContainer = (void *)pxList;
/* 链表节点计数器++ */
(pxList->uxNumberOfItems)++;
}
2 删除节点
/* 将节点从链表中删除 */
UBaseType_t uxListRemove(ListItem_t * const pxItemToRemove)
{
/* 获取节点所在的链表 */
List_t * const pxList = (List_t *) pxItemToRemove->pvContainer;
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
/* Make sure the index is left pointing to a valid item. */
if(pxList->pxIndex == pxItemToRemove)
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
pxItemToRemove->pvContainer = NULL;
/* 链表节点计数器-- */
(pxList->uxNumberOfItems)--;
/* 返回链表中剩余节点的个数 */
return pxList->uxNumberOfItems;
}
main函数
/* main.c */
#include "list.h"
/* 定义根节点 */
struct xLIST List_Test;
/* 定义3个节点 */
struct xLIST_ITEM List_Item1;
struct xLIST_ITEM List_Item2;
struct xLIST_ITEM List_Item3;
int main(void)
{
// 根节点初始化
vListInitialise(&List_Test);
// 节点初始化
vListInitialiseItem(&List_Item1);
vListInitialiseItem(&List_Item2);
vListInitialiseItem(&List_Item3);
vListInsert(&List_Test, &List_Item1);
vListInsert(&List_Test, &List_Item2);
vListInsert(&List_Test, &List_Item3);
for (; ;)
{
/* do nothing */
}
}