目录
1 FreeRTOS中链表的实现
1.1 实现链表节点
在FreeRTOS操作系统中,任务、事件和定时器等经常需要被添加、移除或重新排序,链表能够高效且灵活的方式来管理动态变化的数据集合。
xLIST_ITEM结构体定义了一个双向链表节点,它包含一个用于排序的辅助值、指向链表中前一个和后一个节点的指针、一个指向拥有该节点的内核对象的指针(通常是任务控制块TCB),以及一个指向该节点所属链表的指针。
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;/*节点数据类型重定义*/
MiniListItem_t是ListItem_t的一个简化版本,通常用于表示链表的起始和结束节点。
struct xMINI_LIST_ITEM
{
TickType_t xItemValue; /*辅助值,用于帮助节点做升序排列*/
struct xLIST_ITEM * pxNext; /*指向链表下一个节点*/
struct xLIST_ITEM * pxPrevious; /*指向链表前一个节点*/
};
typedef struct xMINI_LIST_ITEMMiniListItem_t; /*精简节点数据类型重定义*/
节点示意图如图:
xItemValue:TickType_t类型的变量,通常用于存储与节点相关的值,比如任务的优先级或者任务的等待时间等,可以帮助在排序链表时确定节点的顺序。TickType_t具体表示16位还是32位,由configUSE_16_BIT_TICKS这个宏决定,当该宏定义为1时,TickType_t为16位,否则为32位。
pxNext:xLIST_ITEM类型的指针,它指向链表中的下一个节点。在双向链表中,每个节点都通过pxNext和pxPrevious指针与相邻节点相连。
pxPrevious:xLIST_ITEM类型的指针,它指向链表中的前一个节点,使得链表可以双向遍历。
pvOwner:指向void类型的指针,它通常用来指向拥有该节点的内核对象。在FreeRTOS中,这通常是任务控制块(TaskControlBlock,TCB),表示这个节点是由哪个任务拥有的。
pvContainer:指向void类型的指针,它指向包含该节点的容器,比如任务列表或者队列。
链表节点初始化函数在list.c中实现,初始化的时候只需将pvContainer初始化为空即可,表示该节点还没有插入到任何链表,具体实现见代码清单。
void vListInitialiseItem(ListItem_t*constpxItem)
{
/*初始化该节点所在的链表为空,表示节点还没有插入任何链表*/
pxItem->pvContainer=NULL;
}
1.2 实现链表根节点
链表根节点是链表的头部,它包含了链表的元数据和指向链表中第一个和最后一个节点的指针。
typedef structxLIST
{
UBaseType_t uxNumberOfItems; /*链表节点计数器*/
ListItem_t * pxIndex; /*链表节点索引指针*/
MiniListItem_t xListEnd; /*链表最后一个节点*/
}List_t;
链表根结点和完整的数据结构示意图如图:
uxNumberOfItems:UBaseType_t类型的变量,用于存储链表中当前的节点数量。
pxIndex:指向ListItem_t类型的指针,它用作链表的索引指针。用于快速访问链表中的节点,特别是在需要遍历链表时。
xListEnd:MiniListItem_t类型的结构体,它代表链表的最后一个节点。
链表节点初始化函数在list.c中实现,具体实现见代码清单6-10,初始化好的根节点示意图具体见。
void vListInitialise(List_t*constpxList)
{
/*将链表索引指针指向最后一个节点*/
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=(ListItem_t*) &(pxList->xListEnd):这行代码将链表的索引指针pxIndex设置为指向链表的最后一个节点xListEnd。在初始化时,由于链表为空,索引指针指向最后一个节点,这样在添加第一个节点时,可以很容易地将其插入到链表的末尾。
pxList->xListEnd.xItemValue=portMAX_DELAY:这行代码将链表最后一个节点的xItemValue设置为portMAX_DELAY。portMAX_DELAY是FreeRTOS中定义的一个常量,表示最大的延迟时间。由于xItemValue用于排序,将最后一个节点的值设置为最大值可以确保它始终位于链表的末尾。
pxList->xListEnd.pxNext=(ListItem_t*) &(pxList->xListEnd):这行代码将链表最后一个节点的pxNext指针设置为指向自身。在链表为空时,最后一个节点的pxNext和pxPrevious指针都指向自身,形成一个循环,表示链表没有其他节点。
pxList->xListEnd.pxPrevious=(ListItem_t*) &(pxList->xListEnd):这行代码将链表最后一个节点的pxPrevious指针也设置为指向自身。这样,无论是向前遍历还是向后遍历链表,都会遇到这个节点,从而知道已经到达链表的末尾。
pxList->uxNumberOfItems=(UBaseType_t)0U:这行代码将链表的节点计数器uxNumberOfItems初始化为0,表示链表在开始时是空的。
1.3 将节点插入到链表的尾部
将节点插入到链表的尾部(可以理解为头部)就是将一个新的节点插入到一个空的链表,具体代码实现见代码。
void vListInsertEnd(List_t* constpxList, ListItem_t* constpxNewListItem)
{
ListItem_t* constpxIndex = pxList->pxIndex;
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
/*记住该节点所在的链表*/
pxNewListItem->pvContainer = (void*) pxList;
/*链表节点计数器++*/
(pxList->uxNumberOfItems)++;
}
1.4 将节点按照升序排列插入到链表
将一个新节点按照其排序值插入到链表中,如果新节点的排序值为portMAX_DELAY
,则将其插入到链表末尾;否则,遍历链表找到适当的位置,然后按照升序插入新节点,并更新链表的节点计数器。
void vListInsert(List_t * const pxList, ListItem_t * const pxNewListItem)
{
ListItem_t *pxIterator;
/*获取节点的排序辅助值*/
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
/* 确定新节点的插入位置 */
if (xValueOfInsertion == portMAX_DELAY) {
/* 如果排序值是portMAX_DELAY,则插入到链表末尾 */
pxIterator = pxList->xListEnd.pxPrevious;
} else {
/* 否则,遍历链表以找到正确的插入位置 */
for (pxIterator = (ListItem_t *)&(pxList->xListEnd);
pxIterator->pxNext->xItemValue <= xValueOfInsertion;
pxIterator = pxIterator->pxNext) {
/* 遍历直到找到第一个xItemValue大于新节点值的节点 */
}
}
/* 将新节点插入链表 */
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
/* 记录新节点所在的链表 */
pxNewListItem->pvContainer = (void *)pxList;
/* 更新链表的节点计数 */
(pxList->uxNumberOfItems)++;
}
1.5 将节点从链表删除
uxListRemove
函数用于从的链表中移除一个指定的节点。首先获取节点所在的链表,然后更新链表中相邻节点的指针以移除目标节点。如果移除的节点是链表的索引节点,还会更新链表的索引。最后,将节点的容器指针设置为NULL
以标记它不再属于任何链表,并减少链表的节点计数,函数返回链表中剩余的节点数量。
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;
/* 如果删除的节点是链表索引节点,则更新索引 */
if (pxList->pxIndex == pxItemToRemove) {
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
/* 将节点的容器指针设置为空,表示节点已从链表中移除 */
pxItemToRemove->pvContainer = NULL;
/* 更新链表的节点计数 */
(pxList->uxNumberOfItems)--;
/* 返回链表中剩余的节点数量 */
return pxList->uxNumberOfItems;
}
1.6 节点带参宏小函数
FreeRTOS定义了一系列宏用于操作节点,包括设置和获取节点拥有者、排序值、链表头节点、链表结束标记、判断链表是否为空、获取链表长度以及获取链表第一个节点的拥有者等功能。
/* 设置节点的拥有者(通常是任务控制块TCB) */
#define listSET_LIST_ITEM_OWNER(pxListItem, pxOwner) \
((pxListItem)->pvOwner = (void*)(pxOwner))
/* 获取节点的拥有者 */
#define listGET_LIST_ITEM_OWNER(pxListItem) \
((pxListItem)->pvOwner)
/* 设置节点的排序辅助值 */
#define listSET_LIST_ITEM_VALUE(pxListItem, xValue) \
((pxListItem)->xItemValue = (xValue))
/* 获取节点的排序辅助值 */
#define listGET_LIST_ITEM_VALUE(pxListItem) \
((pxListItem)->xItemValue)
/* 获取链表头节点的排序辅助值 */
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY(pxList) \
(((pxList)->xListEnd).pxNext->xItemValue)
/* 获取链表的头节点 */
#define listGET_HEAD_ENTRY(pxList) \
(((pxList)->xListEnd).pxNext)
/* 获取节点的下一个节点 */
#define listGET_NEXT(pxListItem) \
((pxListItem)->pxNext)
/* 获取链表的结束标记节点 */
#define listGET_END_MARKER(pxList) \
((ListItem_t const*)(&((pxList)->xListEnd)))
/* 判断链表是否为空 */
#define listLIST_IS_EMPTY(pxList) \
((BaseType_t)(((pxList)->uxNumberOfItems) == (UBaseType_t)0))
/* 获取链表的节点数 */
#define listCURRENT_LIST_LENGTH(pxList) \
((pxList)->uxNumberOfItems)
/* 获取链表第一个节点的拥有者(TCB) */
#define listGET_OWNER_OF_NEXT_ENTRY(pxTCB, pxList) \
do { \
List_t * const pxConstList = (pxList); \
/* 将节点索引指向链表的第一个节点 */ \
(pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext; \
/* 确保索引不指向结束标记 */ \
if ((void*)(pxConstList)->pxIndex == (void*)&((pxConstList)->xListEnd)) { \
(pxConstList)->pxIndex = (pxConstList)->pxIndex->pxNext; \
} \
/* 获取节点的拥有者,即TCB */ \
(pxTCB) = (pxConstList)->pxIndex->pvOwner; \
} while (0)
2 链表操作实验
通过下面的代码定义和初始化一个链表及其节点,然后将这些节点按照升序插入到链表中。
#include "stm32f4xx.h"
#include "list.h"
/* 定义链表根节点 */
struct xLIST List_Test;
/* 定义节点 */
struct xLIST_ITEM List_Item1;
struct xLIST_ITEM List_Item2;
struct xLIST_ITEM List_Item3;
int main(void)
{
/* 链表根节点初始化 */
vListInitialise(&List_Test);
/* 节点 1 初始化 */
vListInitialiseItem(&List_Item1);
List_Item1.xItemValue = 1;
/* 节点 2 初始化 */
vListInitialiseItem(&List_Item2);
List_Item2.xItemValue = 2;
/* 节点 3 初始化 */
vListInitialiseItem(&List_Item3);
List_Item3.xItemValue = 3;
/* 将节点插入链表,按照升序排列 */
vListInsert(&List_Test, &List_Item2);
vListInsert(&List_Test, &List_Item1);
vListInsert(&List_Test, &List_Item3);
for (;;)
{
}
}
程序编译好之后,配置好硬件环境,点击调试按钮,然后全速运行,再然后把 List_Test、 List_Item1 、List_Item2 和 List_Item3 这四个全局变量添加到观察窗口,然后查看这几个数据结构中pxNext 和 pxPrevious 的值和我们理解的数据结构一致。
在运行过程中,可能会报如下的错误,根据报错位置,如果我们调用了list.h头文件,那还必须在调用FreeRTOS.h才行,否则编译时候会直接出现相应的预处理错误。
..\..\FreeRTOS\include\list.h(62): error: #35: #error directive: "FreeRTOS.h must be included before list.h" |