FreeRTOS --(1)链表

Based On FreeRTOS Kernel V10.3.1

1、相关文件

链表结构是 OS 内部经常使用到的,FreeRTOS 自然也不例外,在深入分析各个模块的工作原理之前,首先来分析 FreeRTOS 的链表结构,和链表相关的代码被定义在:

list.h

list.c

 

2、数据结构

不得不说,FreeRTOS 另一个成功的因素,在于他的代码注释,非常的完备,有的时候,代码、结构等的定义,和具体的场景相关性很强,也就是说,没有分析到更后面的使用场景,那么可能便很难理解当前看到的结构定义;

FreeRTOS 这一点做得非常的棒,它有很多很棒的注释,能够帮助大家在初期能够把握一些全局性的东西;

FreeRTOS 使用双向链表来描述链表结构,FreeRTOS 中定义了 3 个相关的结构:

ListItem_t:用来表示链表中的一个元素;

MiniListItem_t:用来表示链表中初始的那个元素;

List_t:用来表示一个链表;

2.1、ListItem_t

ListItem_t 用于描述链表中的一个元素,它的定义为:

/*
 * Definition of the only type of object that a list can contain.
 */
struct xLIST;
struct xLIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	configLIST_VOLATILE TickType_t xItemValue;			/*< The value being listed.  In most cases this is used to sort the list in descending order. */
	struct xLIST_ITEM * configLIST_VOLATILE pxNext;		/*< Pointer to the next ListItem_t in the list. */
	struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;	/*< Pointer to the previous ListItem_t in the list. */
	void * pvOwner;										/*< Pointer to the object (normally a TCB) that contains the list item.  There is therefore a two way link between the object containing the list item and the list item itself. */
	struct xLIST * configLIST_VOLATILE pxContainer;		/*< Pointer to the list in which this list item is placed (if any). */
	listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
};
typedef struct xLIST_ITEM ListItem_t;	

结构体中的各个成员的描述为:

listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE :

这个是新版本加上的,用于链表是否有效的判断,当定义了 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 这个为 1 的时候,链表的这个成员便有效,否则是空定义;初始化的时候,这个占位的 Tag 被设置成为固定的 0x5a5a5a5aUL,作用是,在使用链表的时候,判断这个成员是否可能被踩;

TickType_t xItemValue

这个成员用于排序,看得出来,被定义成为了 TickType_t 类型,也就是按照时间的值来排序;

struct xLIST_ITEM * configLIST_VOLATILE pxNext

指向下一个成员的指针;

struct xLIST_ITEM * configLIST_VOLATILE pxPrevious

指向上一个成员的指针;

void * pvOwner

指向拥有这个 Item 成员的结构体,通常是描述进程 TCB 的指针;

struct xLIST * configLIST_VOLATILE pxContainer

指向这个 Item 所在的链表的指针;

listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE:

含义和第一个成员一样

 

2.2、MiniListItem_t

从名字就看得出来,是一个迷你型的 Item,它的定义为:

struct xMINI_LIST_ITEM
{
	listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE			/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 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;

看得出来,它和 ListItem_t 的定义非常类似,关键成员少了 pvOwner、pxContainer;为什么少定义这两个呢?因为这个成员根本用不到这两个,后面我们在谈原因;

 

2.3、List_t

主角登场,List_t 用于描述一个链表,它的定义如下:

/*
 * Definition of the type of queue used by the scheduler.
 */
typedef struct xLIST
{
	listFIRST_LIST_INTEGRITY_CHECK_VALUE				/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	volatile UBaseType_t uxNumberOfItems;
	ListItem_t * configLIST_VOLATILE pxIndex;			/*< Used to walk through the list.  Points to the last item returned by a call to listGET_OWNER_OF_NEXT_ENTRY (). */
	MiniListItem_t xListEnd;							/*< List item that contains the maximum possible item value meaning it is always at the end of the list and is therefore used as a marker. */
	listSECOND_LIST_INTEGRITY_CHECK_VALUE				/*< Set to a known value if configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
} List_t;

可以看到,FreeRTOS 对它的注释是:

Definition of the type of queue used by the scheduler

被调度器使用的队列,也就是说进程调度要用到它;

结构体一前一后两个定义不再多说,和前面的一样,为了检测结构是否被踩的可能性;

volatile UBaseType_t uxNumberOfItems

定义了当前这个链表中有多少个 Item ,增加一个链表元素,这个值加1,反之,减1;

ListItem_t * configLIST_VOLATILE pxIndex

用于链表遍历的节点,怎么个遍历法,后面马上献上;

MiniListItem_t xListEnd

用于链表的最后的元素,相当于一个标记;

 

3、函数

既然数据结构介绍完毕(虽然看上去比较抽象,包括几个不容易理解的数据结构),那么接下来一边分析函数,一边分析数据结构的使用方式;

3.1、vListInitialise

一个链表的初始化函数为:

void vListInitialise( List_t * const pxList )
{
	/* The list structure contains a list item which is used to mark the
	end of the list.  To initialise the list the list end is inserted
	as the only list entry. */
	pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );			/*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM.  This is checked and valid. */

	/* The list end value is the highest possible value in the list to
	ensure it remains at the end of the list. */
	pxList->xListEnd.xItemValue = portMAX_DELAY;

	/* The list end next and previous pointers point to itself so we know
	when the list is empty. */
	pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );	/*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM.  This is checked and valid. */
	pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM.  This is checked and valid. */

	pxList->uxNumberOfItems = ( UBaseType_t ) 0U;

	/* Write known values into the list if
	configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
	listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}

传入一个表征链表的结构体指针 List_t * const pxList

请注意一点,在 List_t 结构中,用于标记链表最后的 xListEnd 结构是一个定义,而不是指针,这里首先将传入链表的

pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );  // MiniListItem_t 结构强转

强转为 ListItem_t 结构,并赋值给了 pxIndex,也就是给 pxIndex 内容为这个 List 的 xListEnd 的地址;

接下来便将 xListEnd 的 xItemValue 写入最大值 0xFFFFFFFF(32位CPU);

然后便将 xListEnd 的 next 和 prev 指针全部指向它自己,已达到初始化的目的;

最后初始化该链表中有效元素的个数为 0 个,即 uxNumberOfItems = ( UBaseType_t ) 0U;

最后是,如果使能了 Check 链表有效性的那个宏,那么这里给链表结构的前后两个 TAG 位赋值成为固定的 0x5a5a5a5aUL;

初始化过程为:

 

3.2、vListInitialiseItem

初始化一个链表元素:

void vListInitialiseItem( ListItem_t * const pxItem )

它的实现非常简单:

void vListInitialiseItem( ListItem_t * const pxItem )
{
	/* Make sure the list item is not recorded as being on a list. */
	pxItem->pxContainer = NULL;

	/* Write known values into the list item if
	configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES is set to 1. */
	listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
	listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

仅仅是将元素的容器指针给赋值成为 NULL;

 

3.3、vListInsertEnd

这个 API 是往指定的链表的后部插入一个 Item:

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

	/* Only effective when configASSERT() is also defined, these tests may catch
	the list data structures being overwritten in memory.  They will not catch
	data errors caused by incorrect configuration or use of FreeRTOS. */
	listTEST_LIST_INTEGRITY( pxList );
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

	/* Insert a new list item into pxList, but rather than sort the list,
	makes the new list item the last item to be removed by a call to
	listGET_OWNER_OF_NEXT_ENTRY(). */
	pxNewListItem->pxNext = pxIndex;
	pxNewListItem->pxPrevious = pxIndex->pxPrevious;

	/* Only used during decision coverage testing. */
	mtCOVERAGE_TEST_DELAY();

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

	/* Remember which list the item is in. */
	pxNewListItem->pxContainer = pxList;

	( pxList->uxNumberOfItems )++;
}

入参 pxList 是指定的链表指针,pxNewListItem 是待插入的 Item;

前面两行不多说了,配置用于检查链表和 Item 被踩的 Tag;

首先获取链表的 pxIndex 结构指针,此指针在链表初始化的时候,是指向了 xListEnd;

所以对于一个新的链表创建后,依次插入两个元素 NewItem_1 和 NewItem_2 的过程如下所示:

可以看到,pxIndex 始终处于最开始的那个 xListEnd 结构,而 xListEnd 结构在 List 中有定义成为只有 next 和 prev 的结构,这就凸显出 FreeRTOS 在设计的时候,一点点空间都在节约的精益求精的准则;

 

3.4、vListInsert

这个 API 和前一个不同的是,前一个是插入到尾部,这个 API 按照 Item 中的 xItemValue 排序插入,xItemValue 越大,越靠近 xListEnd:

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

	/* Only effective when configASSERT() is also defined, these tests may catch
	the list data structures being overwritten in memory.  They will not catch
	data errors caused by incorrect configuration or use of FreeRTOS. */
	listTEST_LIST_INTEGRITY( pxList );
	listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );

	/* Insert the new list item into the list, sorted in xItemValue order.

	If the list already contains a list item with the same item value then the
	new list item should be placed after it.  This ensures that TCBs which are
	stored in ready lists (all of which have the same xItemValue value) get a
	share of the CPU.  However, if the xItemValue is the same as the back marker
	the iteration loop below will not end.  Therefore the value is checked
	first, and the algorithm slightly modified if necessary. */
	if( xValueOfInsertion == portMAX_DELAY )
	{
		pxIterator = pxList->xListEnd.pxPrevious;
	}
	else
	{
		/* *** NOTE ***********************************************************
		If you find your application is crashing here then likely causes are
		listed below.  In addition see https://www.freertos.org/FAQHelp.html for
		more tips, and ensure configASSERT() is defined!
		https://www.freertos.org/a00110.html#configASSERT

			1) Stack overflow -
			   see https://www.freertos.org/Stacks-and-stack-overflow-checking.html
			2) Incorrect interrupt priority assignment, especially on Cortex-M
			   parts where numerically high priority values denote low actual
			   interrupt priorities, which can seem counter intuitive.  See
			   https://www.freertos.org/RTOS-Cortex-M3-M4.html and the definition
			   of configMAX_SYSCALL_INTERRUPT_PRIORITY on
			   https://www.freertos.org/a00110.html
			3) Calling an API function from within a critical section or when
			   the scheduler is suspended, or calling an API function that does
			   not end in "FromISR" from an interrupt.
			4) Using a queue or semaphore before it has been initialised or
			   before the scheduler has been started (are interrupts firing
			   before vTaskStartScheduler() has been called?).
		**********************************************************************/

		for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM.  This is checked and valid. *//*lint !e440 The iterator moves to a different value, not xValueOfInsertion. */
		{
			/* There is nothing to do here, just iterating to the wanted
			insertion position. */
		}
	}

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

	/* Remember which list the item is in.  This allows fast removal of the
	item later. */
	pxNewListItem->pxContainer = pxList;

	( pxList->uxNumberOfItems )++;
}

如果 xItemValue 为 portMAX_DELAY(32bit CPU 中,这个值是 0xFFFFFFFF),那么直接插入到 xListEnd 右边:

否则,按照从 xListEnd 开始,从最右边遍历,从最右往左,依次排列 xItemValue 最小的,比如;

 

3.5、uxListRemove

该接口用于将链表中的特定元素摘除:

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* The list item knows which list it is in.  Obtain the list from the list
item. */
List_t * const pxList = pxItemToRemove->pxContainer;

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

	/* Only used during decision coverage testing. */
	mtCOVERAGE_TEST_DELAY();

	/* Make sure the index is left pointing to a valid item. */
	if( pxList->pxIndex == pxItemToRemove )
	{
		pxList->pxIndex = pxItemToRemove->pxPrevious;
	}
	else
	{
		mtCOVERAGE_TEST_MARKER();
	}

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

	return pxList->uxNumberOfItems;
}

首先从指定元素中的 pxContainer 获取到该元素所属的链表结构;再将元素从链表中摘除;

 

4、宏

除了上面几个常用到的 API,在 list.h 中还以宏的方式提供了一些常用的宏以及宏函数,下面我们依次看下:

4.1、listSET_LIST_ITEM_OWNER、listGET_LIST_ITEM_OWNER

listSET_LIST_ITEM_OWNER 用于设置一个 Item 的 Owner:

/*
 * Access macro to set the owner of a list item.  The owner of a list item
 * is the object (usually a TCB) that contains the list item.
 *
 * \page listSET_LIST_ITEM_OWNER listSET_LIST_ITEM_OWNER
 * \ingroup LinkedList
 */
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )		( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )

listGET_LIST_ITEM_OWNER 用于获取一个 Item 的 Owner:

/*
 * Access macro to get the owner of a list item.  The owner of a list item
 * is the object (usually a TCB) that contains the list item.
 *
 * \page listGET_LIST_ITEM_OWNER listSET_LIST_ITEM_OWNER
 * \ingroup LinkedList
 */
#define listGET_LIST_ITEM_OWNER( pxListItem )	( ( pxListItem )->pvOwner )

4.2、listSET_LIST_ITEM_VALUE、listGET_LIST_ITEM_VALUE

listSET_LIST_ITEM_VALUE 用于设置 Item 的 xItemValue 值:

/*
 * Access macro to set the value of the list item.  In most cases the value is
 * used to sort the list in descending order.
 *
 * \page listSET_LIST_ITEM_VALUE listSET_LIST_ITEM_VALUE
 * \ingroup LinkedList
 */
#define listSET_LIST_ITEM_VALUE( pxListItem, xValue )	( ( pxListItem )->xItemValue = ( xValue ) )

listGET_LIST_ITEM_VALUE 用于获取 Item 的 xItemValue 值:

/*
 * Access macro to retrieve the value of the list item.  The value can
 * represent anything - for example the priority of a task, or the time at
 * which a task should be unblocked.
 *
 * \page listGET_LIST_ITEM_VALUE listGET_LIST_ITEM_VALUE
 * \ingroup LinkedList
 */
#define listGET_LIST_ITEM_VALUE( pxListItem )	( ( pxListItem )->xItemValue )

 

4.3、listGET_ITEM_VALUE_OF_HEAD_ENTRY

listGET_ITEM_VALUE_OF_HEAD_ENTRY 用于获取指定链表的 Entry 的 xItemValue,这里我们需要知道 Entry 的定义,其实就是 xListEnd->pxNext 的那个元素:

/*
 * Access macro to retrieve the value of the list item at the head of a given
 * list.
 *
 * \page listGET_LIST_ITEM_VALUE listGET_LIST_ITEM_VALUE
 * \ingroup LinkedList
 */
#define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )	( ( ( pxList )->xListEnd ).pxNext->xItemValue )

 

4.4、listGET_HEAD_ENTRY

listGET_HEAD_ENTRY 用于获取指定链表的 Entry 的 Item 指针:

/*
 * Return the list item at the head of the list.
 *
 * \page listGET_HEAD_ENTRY listGET_HEAD_ENTRY
 * \ingroup LinkedList
 */
#define listGET_HEAD_ENTRY( pxList )	( ( ( pxList )->xListEnd ).pxNext )

 

4.5、listGET_NEXT

listGET_NEXT 用户获取传入 Item 的 next 指针:

/*
 * Return the next list item.
 *
 * \page listGET_NEXT listGET_NEXT
 * \ingroup LinkedList
 */
#define listGET_NEXT( pxListItem )	( ( pxListItem )->pxNext )

 

4.6、listGET_END_MARKER

listGET_END_MARKER 用来获取指定链表的 xListEnd 标记位置:

/*
 * Return the list item that marks the end of the list
 *
 * \page listGET_END_MARKER listGET_END_MARKER
 * \ingroup LinkedList
 */
#define listGET_END_MARKER( pxList )	( ( ListItem_t const * ) ( &( ( pxList )->xListEnd ) ) )

 

4.6、listLIST_IS_EMPTY

listLIST_IS_EMPTY 用来获取指定链表中是否有 Item,主要是通过查看链表的数据统计的变量来获取:

/*
 * Access macro to determine if a list contains any items.  The macro will
 * only have the value true if the list is empty.
 *
 * \page listLIST_IS_EMPTY listLIST_IS_EMPTY
 * \ingroup LinkedList
 */
#define listLIST_IS_EMPTY( pxList )	( ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) ? pdTRUE : pdFALSE )

 

4.7、listCURRENT_LIST_LENGTH

listCURRENT_LIST_LENGTH 获取链表元素的个数:

/*
 * Access macro to return the number of items in the list.
 */
#define listCURRENT_LIST_LENGTH( pxList )	( ( pxList )->uxNumberOfItems )

 

4.8、listGET_OWNER_OF_NEXT_ENTRY

listGET_OWNER_OF_NEXT_ENTRY 用于获取指定链表的下一个 TCB 结构:

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )										\
{																							\
List_t * const pxConstList = ( pxList );													\
	/* Increment the index to the next item and return the item, ensuring */				\
	/* we don't return the marker used at the end of the list.  */							\
	( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;							\
	if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )	\
	{																						\
		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;						\
	}																						\
	( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;											\
}

 

4.9、listGET_OWNER_OF_HEAD_ENTRY

listGET_OWNER_OF_HEAD_ENTRY 用于获取 Entry 节点的 Owner:

/*
 * Access function to obtain the owner of the first entry in a list.  Lists
 * are normally sorted in ascending item value order.
 *
 * This function returns the pxOwner member of the first item in the list.
 * The pxOwner parameter of a list item is a pointer to the object that owns
 * the list item.  In the scheduler this is normally a task control block.
 * The pxOwner parameter effectively creates a two way link between the list
 * item and its owner.
 *
 * @param pxList The list from which the owner of the head item is to be
 * returned.
 *
 * \page listGET_OWNER_OF_HEAD_ENTRY listGET_OWNER_OF_HEAD_ENTRY
 * \ingroup LinkedList
 */
#define listGET_OWNER_OF_HEAD_ENTRY( pxList )  ( (&( ( pxList )->xListEnd ))->pxNext->pvOwner )

 

4.10、listIS_CONTAINED_WITHIN

listIS_CONTAINED_WITHIN 用于判断给定的 Item 是否属于一个指定的链表:

/*
 * Check to see if a list item is within a list.  The list item maintains a
 * "container" pointer that points to the list it is in.  All this macro does
 * is check to see if the container and the list match.
 *
 * @param pxList The list we want to know if the list item is within.
 * @param pxListItem The list item we want to know if is in the list.
 * @return pdTRUE if the list item is in the list, otherwise pdFALSE.
 */
#define listIS_CONTAINED_WITHIN( pxList, pxListItem ) ( ( ( pxListItem )->pxContainer == ( pxList ) ) ? ( pdTRUE ) : ( pdFALSE ) )

 

4.11、listLIST_ITEM_CONTAINER

listLIST_ITEM_CONTAINER 用于获取 Item 属于的链表结构:

/*
 * Return the list a list item is contained within (referenced from).
 *
 * @param pxListItem The list item being queried.
 * @return A pointer to the List_t object that references the pxListItem
 */
#define listLIST_ITEM_CONTAINER( pxListItem ) ( ( pxListItem )->pxContainer )

 

4.12、listLIST_IS_INITIALISED

listLIST_IS_INITIALISED 用于判断一个指定的链表是否被初始化过:

/*
 * This provides a crude means of knowing if a list has been initialised, as
 * pxList->xListEnd.xItemValue is set to portMAX_DELAY by the vListInitialise()
 * function.
 */
#define listLIST_IS_INITIALISED( pxList ) ( ( pxList )->xListEnd.xItemValue == portMAX_DELAY )

 

到这里,链表相关的函数和结构,以及常用到的宏也就分析完毕了,更多的在后面分析任务以及任务调度的时候,会经常涉及这些;

 

值得注意的一点是,List 中的 xListEnd 作为一个 marker,是不动的,而 pxIndex 成员,用来做链表遍历,初始化的时候,指向 xListEnd 位置,但是每次调用 listGET_OWNER_OF_NEXT_ENTRY 后,pxIndex 都会往当前的 pxIndex->pxNext 索引一次;这也是为后面的进程相关的逻辑做准备,提高效率;

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章