1 前言
本文主要介绍FreeRTOS就绪列表的创建与使用,将从FreeRTOS列表与列表项开始介绍,列表和列表项是FreeRTOS的一个数据结构,FreeRTOS大量使用到了列表和列表项,它是FreeRTOS 的基石。要想深入学习并理解FreeRTOS,那么列表和列表项就必须首先掌握,否则后面根本就没法进行。
示例源码基于FreeRTOS V202212.01
2 列表与列表项
2.1列表与列表项结构体介绍
列表项就是存放在列表中的项目,FreeRTOS提供了两种列表项:列表项和迷你列表项。这两个都在文件list.h中有定义,先来看一下列表项,定义如下:
#if ( configUSE_16_BIT_TICKS == 1 )
typedef uint16_t TickType_t;
#else
typedef uint32_t TickType_t;
#endif
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; /*< 指向链表的下一个节点. */
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; /*< 指向链表的上一个节点. */
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;
有些情况下不需要列表项这么全的功能,为了避免造成内存浪费,定义了迷你列表项。迷你列表项的结构体 MiniListItem_t 在 list.h 文件中被定义:
#if ( configUSE_MINI_LIST_ITEM == 1 )
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;
#else
typedef struct xLIST_ITEM MiniListItem_t;
#endif
列表是 FreeRTOS 中的一个数据结构,概念上和双向链表有点类似,列表被用来跟踪 FreeRTOS中的任务。
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /*< 需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,开启以后会向这两个地方分别添加一个变量xListIntegrityValue1.*/
volatile UBaseType_t uxNumberOfItems; /*<用来记录列表中列表项的数量.*/
ListItem_t * configLIST_VOLATILE pxIndex; /*< 用来记录当前列表项索引号,用于遍历列表. */
MiniListItem_t xListEnd; /*< 包含最大可能项目值的列表项,这意味着它总是位于列表的末尾,因此用作标记. */
listSECOND_LIST_INTEGRITY_CHECK_VALUE /*< 需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,开启以后会向这两个地方分别添加一个变量xListIntegrityValue2. */
} List_t;
列表被 FreeRTOS 调度器使用,用于跟踪任务,处于就绪、挂起、延时的任务,都会被挂接到各自的列表中。用户程序如果有需要,也可以使用列表。
FreeRTOS 列表使用指针指向列表项。一个列表(list)下面可能有很多个列表项(list item),每个列表项都有一个指针指向列表,如图2-1所示。
图2-1
2.2 列表与列表项的初始化
列表项的初始比较简单,只要确保列表项不在任何列表中即可。
void vListInitialiseItem( ListItem_t * const pxItem )
{
/* 列表项成员变量 pvContainer初始化为NUL. */
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 );
}
假设禁止列表项数据完整性检查,初始化后的列表项如图2-2所示。仅是将指针 pvContainer 设置为空指针,该指针用于指向包含该列表项的列表,这里设置为 NULL 表示这个列表项不属于任何列表。
图2-2
列表的初始化,主要是让列表指针指向自身,以表示不存在下一个列表项,同时链表计数器的值设置为0,表示链表为空。如图2-3。
void vListInitialise( List_t * const pxList )
{
/*将链表索引指针指向最后一个节点. */
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
/*需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为1,给增加的变量赋值,用于检测列表项的完整性.*/
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( &( pxList->xListEnd ) );
/*将链表最后一个节点的辅助排序值设置为最大,确保该节点就是链表的最后节点*/
pxList->xListEnd.xItemValue = portMAX_DELAY;
/* 将最后一个节点的pxNext与pxPrevious指针均指向节点自身,表示链表为空 */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
/* 当xListEnd是一个可用的ListItem_t时,初始化xListEnd的剩余字段*/
#if ( configUSE_MINI_LIST_ITEM == 0 )
{
pxList->xListEnd.pvOwner = NULL;
pxList->xListEnd.pxContainer = NULL;
/*需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为1,给增加的变
*量赋值,用于检测列表项的完整性.*/
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( &( pxList->xListEnd ) );
}
#endif
/*初始化链表节点计数器的值为0,表示链表为空*/
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 );
}
图 2-3
2.3列表项的插入
列表项的插入,FreeRTOS提供了两个函数进行操作,vListInsertEnd用于插入链表头部(尾部),主要用于将一个新的节点插入一个空的链表中,源码如下:
void vListInsertEnd( List_t * const pxList,
ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
/* 需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为1,给增加的变
*量赋值,用于检测列表项的完整性. */
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
/* 记住该节点所在的链表. */
pxNewListItem->pxContainer = pxList;
/*列表节点计数器+1*/
( pxList->uxNumberOfItems )++;
}
图 2-3
vListInsert用于将列表项按照升序排列插入到链表中,如果两个节点的值相同,则新节点在旧节点的后面插入,具体实现如下:
void vListInsert( List_t * const pxList,
ListItem_t * const pxNewListItem )
{
ListItem_t * pxIterator;
/* 获取节点的排序辅助值. */
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
/* 需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为1,给增加的变
*量赋值,用于检测列表项的完整性. */
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
/* 寻找节点要插入的位置.
*当排序辅助值为最大时,插入队尾*/
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->pxContainer = pxList;
/*列表数量+1. */
( pxList->uxNumberOfItems )++;
}
图 2-4
2.4列表项的删除
列表项的删除通过函数uxListRemove()来完成,需要注意的是,列表项的删除只是将指定的列表项从列表中删除掉,并不会将这个列表项的内存给释放掉!
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
List_t * const pxList = pxItemToRemove->pxContainer;
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;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxItemToRemove->pxContainer = NULL;
( pxList->uxNumberOfItems )--;
return pxList->uxNumberOfItems;
}
2.5小结
本小结主要介绍了FreeRTOS的列表与列表项,基本上分以下几个部分:
1、什么是列表和列表项
2、列表和列表项的初始化
3、列表项的插入
4、列表项的删除
5、列表的遍历与宏操作
关于列表项的遍历,可参考list.h中的源码部分,其中需要重点介绍一下获取链表第一个节点的OWNER,及TCB,调度器主要调用此方法获取优先级最高的就绪任务。
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \
{ \
List_t * const pxConstList = ( pxList ); \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
{ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \
} \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \
}
3 就绪列表
任务创建时,会将新增的任务插入就绪列表中,表示任务已经就绪,系统随时可以调用,源码实现如下:
static void prvAddNewTaskToReadyList( TCB_t * pxNewTCB )
{
taskENTER_CRITICAL();
{
uxCurrentNumberOfTasks++;
if( pxCurrentTCB == NULL )
{
/* 判断是第一个任务时,则初始化就绪列表 */
pxCurrentTCB = pxNewTCB;
if( uxCurrentNumberOfTasks == ( UBaseType_t ) 1 )
{
/* 初始化就绪列表 */
prvInitialiseTaskLists();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
/* 判断是否开启了调度器 */
if( xSchedulerRunning == pdFALSE )
{
/*未开启则通过优先级切换当前任务*/
if( pxCurrentTCB->uxPriority <= pxNewTCB->uxPriority )
{
pxCurrentTCB = pxNewTCB;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
uxTaskNumber++;
#if ( configUSE_TRACE_FACILITY == 1 )
{
/*< 存储一个数值,每次创建一个TCB(任务控制块)时会自增。这使得调试器能够识别出任务何时被删除然后重新创建。*/
pxNewTCB->uxTCBNumber = uxTaskNumber;
}
#endif /* configUSE_TRACE_FACILITY */
traceTASK_CREATE( pxNewTCB );
/*添加新任务到就绪列表*/
prvAddTaskToReadyList( pxNewTCB );
portSETUP_TCB( pxNewTCB );
}
taskEXIT_CRITICAL();
if( xSchedulerRunning != pdFALSE )
{
/* 如果创建的任务优先级高于当前任务,则应该运行当前任务 */
if( pxCurrentTCB->uxPriority < pxNewTCB->uxPriority )
{
taskYIELD_IF_USING_PREEMPTION();
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
当前如果是系统中的第一个任务时,则初始化就绪列表pxReadyTasksLists,就绪列表实际上就是List_t类型的数组,数组的大小由决定最大任务优先级的宏configMAX_PRIORITIES确定,configMAX_PRIORITIES在FreeRTOSConfig.h中默认定义为5,最大支持256个优先级。
PRIVILEGED_DATA static List_t pxReadyTasksLists[ configMAX_PRIORITIES ]; /*< 就绪列表 */
static void prvInitialiseTaskLists( void )
{
UBaseType_t uxPriority;
for( uxPriority = ( UBaseType_t ) 0U; uxPriority < ( UBaseType_t ) configMAX_PRIORITIES; uxPriority++ )
{
vListInitialise( &( pxReadyTasksLists[ uxPriority ] ) );
}
vListInitialise( &xDelayedTaskList1 );
vListInitialise( &xDelayedTaskList2 );
vListInitialise( &xPendingReadyList );
#if ( INCLUDE_vTaskDelete == 1 )
{
vListInitialise( &xTasksWaitingTermination );
}
#endif /* INCLUDE_vTaskDelete */
#if ( INCLUDE_vTaskSuspend == 1 )
{
vListInitialise( &xSuspendedTaskList );
}
#endif /* INCLUDE_vTaskSuspend */
/* Start with pxDelayedTaskList using list1 and the pxOverflowDelayedTaskList
* using list2. */
pxDelayedTaskList = &xDelayedTaskList1;
pxOverflowDelayedTaskList = &xDelayedTaskList2;
}
就绪列表初始化的工作在函数prvInitialiseTaskLists中实现,初始化完成后,其示意图如图3-1所示。
图3-1
新增任务将刚在prvInitialiseNewTask()函数中初始化完成的xStateListItem列表项,根据任务优先级插入就绪列表的尾部。
#define prvAddTaskToReadyList( pxTCB ) \
traceMOVED_TASK_TO_READY_STATE( pxTCB ); \
taskRECORD_READY_PRIORITY( ( pxTCB )->uxPriority ); \
listINSERT_END( &( pxReadyTasksLists[ ( pxTCB )->uxPriority ] ), &( ( pxTCB )->xStateListItem ) ); \
tracePOST_MOVED_TASK_TO_READY_STATE( pxTCB )
#define listINSERT_END( pxList, pxNewListItem ) \
{ \
ListItem_t * const pxIndex = ( pxList )->pxIndex; \
\
listTEST_LIST_INTEGRITY( ( pxList ) ); \
listTEST_LIST_ITEM_INTEGRITY( ( pxNewListItem ) ); \
\
( pxNewListItem )->pxNext = pxIndex; \
( pxNewListItem )->pxPrevious = pxIndex->pxPrevious; \
\
pxIndex->pxPrevious->pxNext = ( pxNewListItem ); \
pxIndex->pxPrevious = ( pxNewListItem ); \
\
( pxNewListItem )->pxContainer = ( pxList ); \
\
( ( pxList )->uxNumberOfItems )++; \
}
xStateListItem列表项的初始化,主要是让pvOwner指向新的任务块。
#define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner ) ( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )
static void prvInitialiseNewTask(…)
{
…
vListInitialiseItem( &( pxNewTCB->xStateListItem ) );
listSET_LIST_ITEM_OWNER( &( pxNewTCB->xStateListItem ), pxNewTCB );
…
}
下图是插入就绪列表的事例,同一优先级的任务放在相同的就绪列表中:
图 3-2