【STM32FreeRTOS】【内部机制】二、FreeRTOS中的链表机制

前言

FreeRTOS 都会将标准的 C 数据类型用 typedef 重新取一个类型名。这些经过重定义的数据类型放在 portmacro.h(portmacro.h 第一次使用需要在 include 文件夹下面新建然后添加到工程 freertos/source 这个组文件)这个头文件

#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	long

typedef 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
#else
	typedef uint32_t TickType_t;
	#define portMAX_DELAY ( TickType_t ) 0xffffffffUL
#endif

#endif /* PORTMACRO_H */

1. 链表节点的实现

1.1 链表节点的结构体定义

链表节点的结构体在 list.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;  /* 节点数据类型重定义 */
  • xItemValue:一个辅助值,用于帮助节点做顺序排列,该值越大表示节点中的值越大。该辅助值的数据类型为TickType_t(原C中的uint16_t\uint32_t),TickType_t 具 体 表 示 16 位 还是 32 位 ,由configUSE_16_BIT_TICKS 这个宏决定, 当该宏定义为 1 时,TickType_t 为 16位,否则为32 位。 宏configUSE_16_BIT_TICKS在 FreeRTOSConfig.h(FreeRTOSConfig.h第一次使用需要在 include 文件夹下面新建然后添加到工程 freertos/source 这个组文件)中默认定义为 0。
#ifndef FREERTOS_CONFIG_H
#define FREERTOS_CONFIG_H

#define configUSE_16_BIT_TICKS		0


#endif /* FREERTOS_CONFIG_H */
  • pxNext:用于指向链表下一个节点。
  • pxPrevious:用于指向链表前一个节点。
  • pvOwner:用于指向该节点的拥有者, 即该节点内嵌在哪个数据结构中, 属于哪个数据结构的一个成员。
  • pvContainer:用于指向该节点所在的链表,通常指向定义的链表。
  • ListItem_t:xLIST_ITEM节点数据类型重定义为ListItem_t。

1.2 链表节点的初始化

链表节点初始化函数在 list.c 中实现:

/* 节点初始化 */
void vListInitialiseItem( ListItem_t * const pxItem )
{
	/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
	pxItem->pvContainer = NULL;
}

链表节点 ListItem_t 总共有 5 个成员,但是初始化的时候只需将pvContainer 初始化为空即可, 表示该节点还没有插入到任何链表。

2. 链表的实现

2.1 链表结构体定义

链表的结构体在 list.h 中定义:

/* 链表结构体定义 */
typedef struct xLIST
{
	UBaseType_t uxNumberOfItems;    /* 链表节点计数器 */
	ListItem_t *  pxIndex;			/* 链表节点索引指针 */
	MiniListItem_t xListEnd;		/* 链表最后一个节点 */
} List_t;
  • uxNumberOfItems:链表节点计数器, 用于表示该链表下有多少个节点, 头节点除外。
  • pxIndex:链表节点索引指针, 指向当前正在使用的节点用于遍历节点。
  • xListEnd:链表最后一个节点,也可以说是第0个节点,是一个虚拟节点,为了方便操作而设计。我们知道,链表是首尾相连的,是一个圈,首就是尾,尾就是首,这里从字面上理解就是链表的最后一个节点,实际也就是链表的第一个节点,我们称之为生产者。这里暂且称为头节点。
    该生产者的数据类型是一个精简的节点,与链表节点相比除去了几个成员变量,节省了一些内存,也在 list.h 中定义:
/* mini节点结构体定义,作为双向链表的结尾
   因为双向链表是首尾相连的,头即是尾,尾即是头 */
struct xMINI_LIST_ITEM
{
	TickType_t xItemValue;                      /* 辅助值,用于帮助节点做升序排列 */
	struct xLIST_ITEM *  pxNext;                /* 指向链表下一个节点 */
	struct xLIST_ITEM *  pxPrevious;            /* 指向链表前一个节点 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;  /* 最小节点数据类型重定义 */

头节点的作用:

  • 简化链表插入操作:当插入新节点时,只需要判断插入位置是否为xListEnd,如果是则无需执行插入操作,直接将新节点添加到尾部即可;
  • 避免指针为空的情况:由于xListEnd节点始终存在于链表中,因此不存在指向空节点的情况,从而避免了因指针为空而导致的程序异常或崩溃;
  • 改善代码可读性:通过将尾节点单独定义并命名为xListEnd,可以提高代码的可读性和可维护性,使代码更加清晰易懂。

2.2 链表的初始化

链表初始化函数在 list.c 中实现:

/* 链表根节点初始化 */
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;
}

在这里插入图片描述

  • ①将链表索引指针指向头节点xListEnd, 或者第零个节点更准确,因为这个节点不会算入节点计数器的值。
  • ②将链表头节点xListEnd的辅助排序的值xItemValue设置为最大,确保该节点就是链表的最后节点(也可以理解为第一)。
  • ③将头节点xListEnd的 pxNext 和 pxPrevious 指针均指向头节点xListEnd自身,表示链表为空。
  • ④初始化链表成员变量计数器uxNumberOfItems的值为 0,表示链表为空。

3. 链表的相关操作

由上面的介绍可以知道一个链表包含一个用于表示该链表下有多少个有效节点的计数器uxNumberOfItems、一个指向当前正在使用的节点的索引指针pxIndex、一个头节点xListEnd和许多链接节点,头节点内部又包含一个表示该节点是该链表中的第几个节点的值xItemValue、一个指向下一个节点的指针pxNext和一个指向上一个节点的指针pxPrevious。链接节点就是将新节点链接到该链表的节点,是有效节点。
链表初始化就是将索引指针pxIndex指向头节点xListEnd,将头节点xListEnd中的xItemValue设置为最大,表示这是链表中最后一个节点,也可以说是第0个节点,再将头节点xListEnd中指向下一个节点的指针pxNext和上一个节点的指针pxPrevious都指向头节点xListEnd本身,表示这是空链表,这与数据结构的中带头循环双向链表基本相同。
链表节点初始化就是将创造的新节点的pvContainer指向空,表示该节点还未插入任何链表。

3.1 链表节点尾插

将节点插入到链表的尾部就是将一个新的节点pxNewListItem插入到pxIndex之前,也就是在pxIndex->pxPrevious的后面插入,因为遍历时新加入的节点pxNewListItem应该最后才能被pxIndex指向。
空链表时,pxIndex指向头节点xListEnd,pxIndex->pxPrevious的后面也就是头节点xListEnd之后;
非空链表插入新节点pxNewListItem时,pxIndex指向xListEnd,pxIndex->pxPrevious的后面也就是最后一个有效节点后面插入:

/* 将节点插入到链表的尾部 */
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 )++;		//7
}
  • ①首先获取链表的索引节点pxIndex,此时它指向头节点xListEnd。
  • ②将新节点pxNewListItem的pxNext指针指向索引节点pxIndex,即成为链表中最后一个有效节点。
  • ③将新节点pxNewListItem的pxPrevious指针指向索引节点pxIndex的前一个节点(pxPrevious),空链表时,即指向索引节点pxIndex自身;非空链表时,指向原来最后一个有效节点。
  • ④将原来最后一个有效节点的pxNext指针指向新节点pxNewListItem,即将新节点pxNewListItem是最后一个有效节点。
  • ⑤将索引节点pxIndex的pxPrevious指针指向新节点(pxNewListItem),即更新索引节点pxIndex的前一个节点为新节点pxNewListItem。
  • ⑥记住该节点所在的链表。
  • ⑦链表节点计数器uxNumberOfItems++,表示该链表的有效节点个数加一。

在这里插入图片描述

3.2 链表节点升序排列插入链表

将节点按照升序排列插入到链表,如果有两个节点的值相同,则新节点在旧节点的后面插入:

/* 将节点按照升序排列插入到链表 */
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 )++;
}
  • ①首先获取待插入节点的排序辅助值xValueOfInsertion。
  • ②如果xValueOfInsertion等于portMAX_DELAY,则表明该节点应当被插入到链表的末尾,因此将pxIterator初始化为pxList->xListEnd.pxPrevious。
  • ③否则,使用一个for循环来寻找节点要插入的位置。初始时,pxIterator被设置为链表的头节点(哨兵节点),并且每次迭代都将pxIterator指向其下一个节点,直到找到一个节点的排序辅助值大于待插入节点的xValueOfInsertion为止。
  • ④将pxNewListItem插入到pxIterator和pxIterator->pxNext之间,即将pxNewListItem的前一个节点指针pxPrevious指向pxIterator,将pxNewListItem的后一个节点指针pxNext指向pxIterator->pxNext,再将pxIterator的后一个节点指针pxNext指向pxNewListItem,最后将pxIterator->pxNext的前一个节点指针pxPrevious指向pxNewListItem。这样就完成了将节点pxNewListItem插入到链表中的操作。
  • ⑤将该节点所在的链表记录到pvContainer中。
  • ⑥将链表节点计数器uxNumberOfItems加一,表示链表中有效节点的个数+1。

在这里插入图片描述

3.3 链表节点删除

将节点从链表删除具体实现如下:

/* 将节点从链表中删除 */
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;
}

这段代码比较好理解,具体来说,该函数接收一个参数 pxItemToRemove,表示需要被删除的节点。函数首先获取该节点所在的链表,然后通过修改被删除的节点前驱和后继节点的指针来将该节点从链表中移除。如果该节点是索引节点pxIndex正在使用的那一项,即索引节点指向被删除节点,则把索引指向被删除节点的前驱节点,即索引节点向前挪动一位。最后,将该节点的 pvContainer 指针设置为 NULL,表示节点不再属于任何链表。同时,链表计数器减一,并返回链表中剩余节点的个数。
在这里插入图片描述

3.4 链表节点带参宏函数

在 list.h 中,还定义了各种各样的带参宏,方便对节点做一些简单的操作(具体操作见注释):

/*
************************************************************************
*                                宏定义
************************************************************************
*/
/* 初始化节点的拥有者 */
#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 )

4. 实验

说明:在做实验前要先新建一个工程,然后将需要的文件添加到工程中:
在这里插入图片描述
实验内容:新建一个链表和三个普通节点,然后将这三个普通节点的排序值设为123,最后将这三个普通节点按照节点的排序辅助值做升序排列插入到链表中。
具体实现方式如下:

#include "list.h"

//设置节点辅助排序值
void XItemValue(ListItem_t* item, int x)
{
    item->xItemValue = x;
}

void ListTest()
{
    //定义三个链表节点
    ListItem_t Item1;
    ListItem_t Item2;
    ListItem_t Item3;
    //定义链表
    List_t list;
    //链表初始化
    vListInitialise(&list);
    //设置节点排序值
    XItemValue(&Item1, 1);
    XItemValue(&Item2, 2);
    XItemValue(&Item3, 3);
    //三个链表节点初始化
    vListInitialiseItem(&Item1);
    vListInitialiseItem(&Item2);
    vListInitialiseItem(&Item3);
    //将三个节点升序插入链表
    vListInsert(&list, &Item1);
    vListInsert(&list, &Item2);
    vListInsert(&list, &Item3);
}

int main()
{
    
    ListTest();
    for(; ;)
    {
        
    }
}

执行调试结果如下:
在这里插入图片描述

参考资料:[野火]《FreeRTOS内核实现与应用开发实战指南》

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值