FreeRTOS学习笔记(1) - 双向循环链表解析

FreeRTOS学习笔记(1) - 双向循环链表解析

链表结构定义


/*
 * 链表项的定义 - 这是链表可以包含的唯一类型的对象。
 */
struct xLIST;
struct xLIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< 如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。 */
	configLIST_VOLATILE TickType_t xItemValue;			/*< 被列出的值。在大多数情况下,这用于按降序对链表进行排序。 */
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;		/*< 指向链表中下一个ListItem_t的指针。 */
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;	/*< 指向链表中前一个ListItem_t的指针。 */
	void * pvOwner;										/*< 指向包含链表项的对象(通常是TCB)的指针。因此在包含链表项的对象和链表项本身之间存在双向链接。 */
	struct xLIST * configLIST_VOLATILE pxContainer;		/*< 指向放置此链表项的链表的指针(如果有)。 */
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< 如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。 */
};
typedef struct xLIST_ITEM ListItem_t;					/* For some reason lint wants this as two separate definitions. */

struct xMINI_LIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< 如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。 */
	configLIST_VOLATILE TickType_t xItemValue;
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

/*
 * 定义调度器使用的队列类型。
 */
typedef struct xLIST
{
	listFIRST_LIST_INTEGRITY_CHECK_VALUE				/*< 如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。 */
	volatile UBaseType_t uxNumberOfItems;
	ListItem_t * configLIST_VOLATILE pxIndex;			/*< 用于遍历链表。指向通过调用listGET_OWNER_OF_NEXT_ENTRY()返回的最后一项。 */
	MiniListItem_t xListEnd;							/*< 包含最大可能项目值的链表项,意味着它始终在链表的末尾,因此用作标记。 */
	listSECOND_LIST_INTEGRITY_CHECK_VALUE				/*< 如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则设置为已知值。 */
} List_t;

有三种结构体:

  • xLIST_ITEM 链表节点数据结构
    • 链表节点需要通过根节点插入到链表中,链表是通过链表根节点进行操作和索引得
    • 成员包含:
      • xItemValue - 辅助值,用于排序
      • pxNext / pxPrevious - 前后节点指针
      • pvOwner - 拥有该节点的(内核)对象,通常是TCB(在链表的实现当中,没有关于该成员的操作,因此是特殊用途)
      • pxContainer - 所在的链表指针
  • xMINI_LIST_ITEM 链表成员结构体简化版
    • 简化版被放置在节点头中,所以该简化节点的所属信息被剔除,只剩下
      • 用于排序的辅助值
      • 前后节点指针
  • xLIST 链表根节点
    • 根节点用于管理链表,需要包含链表成员的个数,遍历位置以及头尾节点
    • 根节点链表中包含一个迷你节点,用作链表的尾节点同时也是头节点

链表的操作

链表节点初始化

void vListInitialiseItem( ListItem_t * const pxItem )
{
	/* 确保列表项未被记录为在列表中。 */
	pxItem->pxContainer = NULL;

	/* 如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,
	则将已知值写入列表项。 */
	listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
	listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

只需要被所属的容器指针(也就是所在的链表指针)置为空即可

链表根节点初始化

void vListInitialise( List_t * const pxList )
{
	/* 列表结构包含一个用于标记列表末尾的列表项。
	要初始化列表,将列表末尾作为唯一的列表条目插入。 */
	pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );			/*lint !e826 !e740 !e9087 使用迷你列表结构作为列表末尾以节省RAM。这已经过检查并有效。 */

	/* 列表末尾值是列表中的最高可能值,
	确保它始终保持在列表的末尾。 */
	pxList->xListEnd.xItemValue = portMAX_DELAY;	// ( TickType_t ) 0xffffffffUL

	/* 列表末尾的next和previous指针指向自身,这样我们就知道
	列表何时为空。 */
	pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );	/*lint !e826 !e740 !e9087 使用迷你列表结构作为列表末尾以节省RAM。这已经过检查并有效。 */
	pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint !e826 !e740 !e9087 使用迷你列表结构作为列表末尾以节省RAM。这已经过检查并有效。 */

	pxList->uxNumberOfItems = ( UBaseType_t ) 0U;

	/* 如果configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,
	则将已知值写入列表。 */
	listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
	listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
  • 将遍历索引指针置为尾节点
  • 将尾节点的索引值设置为最高
  • 将尾节点的前后节点指针指向自己
  • 将链表的数目设置为0

链表尾部插入

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;

	/* 仅在configASSERT()也被定义时有效,这些测试可能会捕获
	列表数据结构在内存中被覆盖的情况。它们不会捕获由FreeRTOS的
	错误配置或使用导致的数据错误。 */
	listTEST_LIST_INTEGRITY( pxList );
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

	/* 将新的列表项插入pxList,但不是对列表进行排序,
	而是使新的列表项成为通过调用listGET_OWNER_OF_NEXT_ENTRY()
	要移除的最后一个项。 */
	pxNewListItem->pxNext = pxIndex;
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;

	/* 仅在决策覆盖测试期间使用。 */
	mtCOVERAGE_TEST_DELAY();

	pxIndex->pxPrevious->pxNext = pxNewListItem;
	pxIndex->pxPrevious = pxNewListItem;

	/* 记住该项所在的列表。 */
	pxNewListItem->pxContainer = pxList;

	( pxList->uxNumberOfItems )++;
}

插入尾部是根据链表的节点索引指针,在节点索引指针前插入节点,大致步骤:

  1. 将需要插入的节点的前后节点指针指向索引节点的前一个节点和索引节点
  2. 将索引节点的前一个节点的后节点指针指向需要插入的节点
  3. 将所有节点的前节点指针指向需要插入的节点
  4. 更新需要插入节点所在的链表指针

链表插入(降序排序)


void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

	/* 仅在configASSERT()也被定义时有效,这些测试可能会捕获
	列表数据结构在内存中被覆盖的情况。它们不会捕获由FreeRTOS的
	错误配置或使用导致的数据错误。 */
	listTEST_LIST_INTEGRITY( pxList );
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

	/* 将新的列表项插入列表,按xItemValue排序。

	如果列表已经包含具有相同项值的列表项,则新的列表项
	应该放在它之后。这确保了存储在就绪列表中的TCB(都具有
	相同的xItemValue值)能够获得CPU的份额。但是,如果xItemValue
	与后标记相同,下面的迭代循环将不会结束。因此,首先检查
	该值,并在必要时稍微修改算法。 */
	if( xValueOfInsertion == portMAX_DELAY )
	{
		pxIterator = pxList->xListEnd.pxPrevious;
	}
	else
	{
		/* *** 注意 ***********************************************************
		如果您发现应用程序在这里崩溃,可能的原因如下。
		此外,请访问 https://www.freertos.org/FAQHelp.html 获取更多提示,
		并确保定义了configASSERT()!
		https://www.freertos.org/a00110.html#configASSERT

			1) 栈溢出 -
			   参见 https://www.freertos.org/Stacks-and-stack-overflow-checking.html
			2) 中断优先级分配不正确,特别是在Cortex-M部件上,
			   其中数值高的优先级值表示实际中断优先级低,这可能看起来
			   违反直觉。参见 https://www.freertos.org/RTOS-Cortex-M3-M4.html
			   和 https://www.freertos.org/a00110.html 上的
			   configMAX_SYSCALL_INTERRUPT_PRIORITY 定义
			3) 在临界区或调度器被挂起时调用API函数,或从中断中调用
			   不以"FromISR"结尾的API函数。
			4) 在队列或信号量初始化之前或调度器启动之前使用它们
			   (中断是否在调用vTaskStartScheduler()之前触发?)。
		**********************************************************************/

		for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 !e9087 使用迷你列表结构作为列表末尾以节省RAM。这已经过检查并有效。 *//*lint !e440 迭代器移动到不同的值,而不是xValueOfInsertion。 */
		{
			/* 这里不需要做任何事情,只是迭代到想要的插入位置。 */
		}
	}

	pxNewListItem->pxNext = pxIterator->pxNext;
	pxNewListItem->pxNext->pxPrevious = pxNewListItem;
	pxNewListItem->pxPrevious = pxIterator;
	pxIterator->pxNext = pxNewListItem;

	/* 记住该项所在的列表。这允许稍后快速移除该项。 */
	pxNewListItem->pxContainer = pxList;

	( pxList->uxNumberOfItems )++;
}

主要做两件事:

  1. 根据需要插入的节点辅助值信息,找到需要插入的位置
  2. 在找到的位置上插入

链表节点删除

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* 列表项知道它在哪个列表中。从列表项获取列表。 */
List_t * const pxList = pxItemToRemove->pxContainer;

	pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
	pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

	/* 仅在决策覆盖测试期间使用。 */
	mtCOVERAGE_TEST_DELAY();

	/* 确保索引指向有效的项。 */
	if( pxList->pxIndex == pxItemToRemove )
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

	pxItemToRemove->pxContainer = NULL;
	( pxList->uxNumberOfItems )--;

	return pxList->uxNumberOfItems;
}

删除只需要链表节点即可,应为链表节点包含了所属链表的指针,删除流程如下:

  1. 获取需要删除节点的所在链表
  2. 将删除节点的前后节点连接
  3. 如果删除的节点正好位于索引项中,则索引项指向删除节点的前一个节点
  4. 更新删除节点的所在链表信息,更新所在链表的节点个数

其他函数


/*
 * 访问宏,用于设置链表项的所有者。链表项的所有者是包含该链表项的对象
 * (通常是TCB)。
 *
 * \page listSET_LIST_ITEM_OWNER listSET_LIST_ITEM_OWNER
 * \ingroup LinkedList
 */
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )		( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )

/*
 * 访问宏,用于获取链表项的所有者。链表项的所有者是包含该链表项的对象
 * (通常是TCB)。
 *
 * \page listGET_LIST_ITEM_OWNER listSET_LIST_ITEM_OWNER
 * \ingroup LinkedList
 */
#define listGET_LIST_ITEM_OWNER( pxListItem )	( ( pxListItem )->pvOwner )

/*
 * 访问宏,用于设置链表项的值。在大多数情况下,该值用于按降序对链表进行排序。
 *
 * \page listSET_LIST_ITEM_VALUE listSET_LIST_ITEM_VALUE
 * \ingroup LinkedList
 */
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue )	( ( pxListItem )->xItemValue = ( xValue ) )

/*
 * 访问宏,用于检索链表项的值。该值可以表示任何内容 - 例如任务的优先级,
 * 或任务应该解除阻塞的时间。
 *
 * \page listGET_LIST_ITEM_VALUE listGET_LIST_ITEM_VALUE
 * \ingroup LinkedList
 */
#define listGET_LIST_ITEM_VALUE( pxListItem )	( ( pxListItem )->xItemValue )

/*
 * 访问宏,用于检索给定链表头部链表项的值。
 *
 * \page listGET_LIST_ITEM_VALUE listGET_LIST_ITEM_VALUE
 * \ingroup LinkedList
 */
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )	( ( ( pxList )->xListEnd ).pxNext->xItemValue )

/*
 * 返回链表头部的链表项。
 *
 * \page listGET_HEAD_ENTRY listGET_HEAD_ENTRY
 * \ingroup LinkedList
 */
#define listGET_HEAD_ENTRY( pxList )	( ( ( pxList )->xListEnd ).pxNext )

/*
 * 返回下一个链表项。
 *
 * \page listGET_NEXT listGET_NEXT
 * \ingroup LinkedList
 */
#define listGET_NEXT( pxListItem )	( ( pxListItem )->pxNext )

/*
 * 返回标记链表末尾的链表项
 *
 * \page listGET_END_MARKER listGET_END_MARKER
 * \ingroup LinkedList
 */
#define listGET_END_MARKER( pxList )	( ( ListItem_t const * ) ( &( ( pxList )->xListEnd ) ) )

/*
 * 访问宏,用于确定链表是否包含任何项目。仅当链表为空时,
 * 该宏的值才为true。
 *
 * \page listLIST_IS_EMPTY listLIST_IS_EMPTY
 * \ingroup LinkedList
 */
#define listLIST_IS_EMPTY( pxList )	( ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) ? pdTRUE : pdFALSE )

/*
 * 访问宏,用于返回链表中的项目数。
 */
#define listCURRENT_LIST_LENGTH( pxList )	( ( pxList )->uxNumberOfItems )

行为分析

头节点中 pxIndex 如何变化

在函数API中,pxIndex 只在删除节点为 pxIndex 时修改

listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) 宏函数中,会更新 pxIndexpxIndex->pxNext,并返回 pxIndex->pvOwner

posted @ 2025-05-08 14:59  c17VV  阅读(48)  评论(0)    收藏  举报