freertos 双向循环链表插入删除的实现与直观理解
- main.c
- list.h
- 其他头文件
- FreeRTOS.h
- FreeRTOSConfig.h
- portable.h
- portmacro.h
- list.c
- vListInsertEnd函数
- vListInsert函数
- uxListRemove函数
main.c
用debug之后,查看观察窗口,结果如下。这个实验目的就是,建一个根节点,三个普通节点的链表,并且这三个普通节点按照xitemValue的值进行升序排列。
main.c里面
首先看节点的类型:链表根节点类型是xLIST、普通节点类型是xLIST_ITEM。
然后看main函数:vListInitialise这个是链表根节点初始化函数、vListInitialiseItem这个是普通节点初始化函数、xItemValue这个是普通节点里面的成员变量、vListInsert这个函数实现将节点插入链表,按照升序排列功能。
接下来分别研究上面这些东西。
#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.h
直接看下面这个图,就可以知道根节点结构体和其他节点结构体里面的成员变量了。
#ifndef LIST_H
#define LIST_H
#include "FreeRTOS.h"/* 节点结构体定义 */
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; /* 节点数据类型重定义 *//* mini节点结构体定义,作为双向链表的结尾因为双向链表是首尾相连的,头即是尾,尾即是头 */
struct xMINI_LIST_ITEM
{TickType_t xItemValue; /* 辅助值,用于帮助节点做升序排列 */struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t; /* 最小节点数据类型重定义 *//* 链表结构体定义 */
typedef struct xLIST
{UBaseType_t uxNumberOfItems; /* 链表节点计数器,用于表示该链表下有多少个节点,根节点除外*/ListItem_t * pxIndex; /* 链表节点索引指针,用于遍历节点 */MiniListItem_t xListEnd; /* 链表最后一个节点 */
} List_t;/*
************************************************************************
* 宏定义
************************************************************************
*/
/* 初始化节点的拥有者 */
#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 )/* 获取链表节点的OWNER,即TCB */
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \List_t * const pxConstList = ( pxList ); \/* 节点索引指向链表第一个节点调整节点索引指针,指向下一个节点,如果当前链表有N个节点,当第N次调用该函数时,pxInedex则指向第N个节点 */\( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \/* 当前链表为空 */ \if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \{ \( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \} \/* 获取节点的OWNER,即TCB */ \( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}#define listGET_OWNER_OF_HEAD_ENTRY( pxList ) ( (&( ( pxList )->xListEnd ))->pxNext->pvOwner )/*
************************************************************************
* 函数声明
************************************************************************
*/
void vListInitialise( List_t * const pxList );
void vListInitialiseItem( ListItem_t * const pxItem );
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem );
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem );
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );#endif /* LIST_H */
其他头文件
FreeRTOS.h
#ifndef INC_FREERTOS_H
#define INC_FREERTOS_H#include "FreeRTOSConfig.h"
#include "portable.h"#endif /* INC_FREERTOS_H */
FreeRTOSConfig.h
这个里面由于下一句话,
#define configUSE_16_BIT_TICKS 0
使得portmacro.h里面进行下面的操作,也就是让TickType_t表示32位。
typedef uint32_t TickType_t;
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H#define configUSE_16_BIT_TICKS 0#endif /* FREERTOS_CONFIG_H */
portable.h
#ifndef PORTABLE_H
#define PORTABLE_H#include "portmacro.h"#endif /* PORTABLE_H */
portmacro.h
这里面是一些freertos里面的数据类型重定义。
#ifndef PORTMACRO_H
#define PORTMACRO_H#include "stdint.h"
#include "stddef.h"/* 数据类型重定义 */
#define portCHAR char
#define portFLOAT float
#define portDOUBLE double
#define portLONG long
#define portSHORT short
#define portSTACK_TYPE uint32_t
#define portBASE_TYPE longtypedef portSTACK_TYPE StackType_t;
typedef long BaseType_t;
typedef unsigned long UBaseType_t;#if( configUSE_16_BIT_TICKS == 1 )typedef uint16_t TickType_t;#define portMAX_DELAY ( TickType_t ) 0xffff
#elsetypedef uint32_t TickType_t;#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif#endif /* PORTMACRO_H */
list.c
vListInitialise函数进行链表根节点初始化。初始化有什么效果可以看下面这个图。
根节点初始化后,里面的指针全指向了一个地方。
直白点表述:
vListInsertEnd函数
vListInsertEnd函数将节点插入到链表的尾部。将一个节点插入后,效果如下。
用图表示的话:
更直白点:
如果按下面这个来编写插入的代码,是错误的。
pxNewListItem->pxNext = pxIndex;pxNewListItem->pxPrevious = pxIndex;pxIndex->pxNext = pxNewListItem;pxIndex->pxPrevious = pxNewListItem;
试一试就知道了,如果按上面这个代码来,插入三个节点,最后效果如下。这个后果就是,新插上来的节点,他们之间是没有建立联系的,只是和根节点建立了联系。
下面将三个节点插入,正确的变量变化情况如下:
这里面就很清楚了,这个是属于循环双向链表。用图表示的话就是:
如果说新加进来一个item3,在item3没加进来的时候图的表示:
结合上面的两幅图,编代码。下面这个代码就考虑到了节点之间的联系。
pxNewListItem->pxNext = pxIndex;//上图右边的pxNext连到最左边的pxIndex
pxNewListItem->pxPrevious = pxIndex->pxPrevious;//第二行代码
pxIndex->pxPrevious->pxNext = pxNewListItem;//第三行代码
pxIndex->pxPrevious = pxNewListItem;//第四行代码
第一行代码直观表示如下面绿线:
pxIndex->pxPrevious一开始指向的是item2,加了item3,那么item3的pxPrevious就得指向item2,也就是pxIndex->pxPrevious。于是有了下面第二行代码。第二行代码直观表示如下。
第三行代码,pxIndex->pxPrevious->pxNext也就相当于item2的pxNext,加了item3,那么item2的pxNext应该指向item3。第三行代码直观表示如下。
此时可以看出原先的item2的pxNext已经和pxIndex彻底断了联系。整个链表可以表示成下面样子。
那么很明显,第四行代码就是要把item3给pxIndex的pxPrevious了。
vListInsert函数
函数功能是将节点按照升序排列插入链表。
先从xListEnd开始找,其实也相当于从第一个根节点找,依次找pxNext所指向节点的xItemValue值,如果这个值大于当前待插入节点的xItemValue值,就把这个节点插到当前的这个节点后面(当前这个节点的pxNext所指向节点的前面)。
核心代码如下。
pxNewListItem->pxNext = pxIterator->pxNext;pxNewListItem->pxNext->pxPrevious = pxNewListItem;pxNewListItem->pxPrevious = pxIterator;pxIterator->pxNext = pxNewListItem;
举个例子,item2插入到item1后面。进行到第三步的时候,直观表示如下。
此时可以发现,item3的pxPrevious已经和item1断了联系。所以上图可表示如下。
那么最后一步显而易见,把item1的pxNext指向item2即可。此时item2就插进去了。
uxListRemove函数
这个函数功能是将节点从链表中删除。核心代码如下。
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
如果删除item2,直观表示如下:
#include "FreeRTOS.h"
#include <stdlib.h>
#include "list.h"/* 链表根节点初始化 */
void vListInitialise( List_t * const pxList )
{/* 将链表索引指针指向最后一个节点 */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;
}/* 节点初始化 */
void vListInitialiseItem( ListItem_t * const pxItem )
{/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */pxItem->pvContainer = NULL;
}/* 将节点插入到链表的尾部 */
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{ListItem_t * const pxIndex = pxList->pxIndex;pxNewListItem->pxNext = pxIndex;pxNewListItem->pxPrevious = pxIndex->pxPrevious;pxIndex->pxPrevious->pxNext = pxNewListItem;pxIndex->pxPrevious = pxNewListItem;/* 记住该节点所在的链表 */pxNewListItem->pvContainer = ( void * ) pxList;/* 链表节点计数器++ */( pxList->uxNumberOfItems )++;
}/* 将节点按照升序排列插入到链表 */
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{ListItem_t *pxIterator;/* 获取节点的排序辅助值 */const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;/* 寻找节点要插入的位置 */if( xValueOfInsertion == portMAX_DELAY ){pxIterator = pxList->xListEnd.pxPrevious;}else{for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ){/* 没有事情可做,不断迭代只为了找到节点要插入的位置 */ }}pxNewListItem->pxNext = pxIterator->pxNext;pxNewListItem->pxNext->pxPrevious = pxNewListItem;pxNewListItem->pxPrevious = pxIterator;pxIterator->pxNext = pxNewListItem;/* 记住该节点所在的链表 */pxNewListItem->pvContainer = ( void * ) pxList;/* 链表节点计数器++ */( pxList->uxNumberOfItems )++;
}/* 将节点从链表中删除 */
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;
}