FreeRTOS调试与源码解析(3)--列表与就绪列表

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值