FreeRTOS源码解析--链表

链表是Freertos中的重要数据结构。任务管理中使用了大量的链表结构。了解FreeRTOS中的链表结构是阅读FreeRTOS源码的基础。

相关结构体

  • 链表节点结构体struct xLIST_ITEM
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 ascending 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;

FreeRTOS内核中使用struct xLIST_ITEM表征一个链表节点,从上述代码可以看出,该结构体中具有一个前继节点指针和一个后继节点指针,可以猜测FreeRTOS中使用的双向链表(这在后文中可以得以证实)。另外,void *pvOwner作为该链表节点结构体的成员,该指针指向拥有该链表节点的结构体,可以猜测,该链表节点是内嵌在其他结构体中的(这很类似Linux内核中的处理),在任务管理相关源码中可以看到任务控制块结构体中就有内嵌链表节点结构体。TickType_t xItemValue成员可以理解为链表节点的value字段,对于FreeRTOS内核中的部分链表(后面的任务管理相关源码中会涉及到),在插入链表节点时会根据该字段的大小决定节点插入的位置。

  • 链表尾部标识结构体struct xMINI_LIST_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;

FreerRTOS内核中使用该结构体作为链表的尾部节点标识。

  • 链表描述符结构体struct xLIST
    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;

FreerRTOS内核中使用该结构体描述一个链表,该结构体中记录了链表节点数以及尾部节点标识。

相关API函数

  • 链表初始化:void vListInitialise(List_t *const pxList),即初始化一个链表描述符结构体。函数具体内容如下:
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. */
    
    listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE(&(pxList->xListEnd));

    /* The list end value is the highest possible value in the list to
     * ensure it remains at the end of the list. */
    /* 将链表尾部标识的节点数据设置为最大值 保证尾部标识始终处于尾部
    在后面的分析中可以看出节点按xItemValue的值升序排列 */
    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. */

/* Initialize the remaining fields of xListEnd when it is a proper ListItem_t */
#if (configUSE_MINI_LIST_ITEM == 0)
    {
        pxList->xListEnd.pvOwner = NULL;
        pxList->xListEnd.pxContainer = NULL;
        listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE(&(pxList->xListEnd));
    }
#endif

    pxList->uxNumberOfItems = (UBaseType_t)0U;    /* 当前链表中的节点数是0 不包括尾部节点 */

    /* 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);
}

在这里插入图片描述

上图所示即为初始化后的链表结构。

  • 链表节点初始化: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,因为节点在初始化时并未插入任意链表中。

  • 链表节点插入到链表尾部:void vListInsertEnd(List_t *const pxList,ListItem_t *const pxNewListItem)
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)++;
}

以链表中只有尾部标识节点时为例,调用该API插入节点后,链表结构如下图所示
在这里插入图片描述
可以看出,FreerRTOS内核使用双向环形链表

  • 链表节点插入到链表:void vListInsert(List_t *const pxList, ListItem_t *const pxNewListItem)
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?).
         *   5) If the FreeRTOS port supports interrupt nesting then ensure that
         *      the priority of the tick interrupt is at or below
         *      configMAX_SYSCALL_INTERRUPT_PRIORITY.
         **********************************************************************/
        /* 遍历整个链表 寻找待插入节点应该插入的位置 */
        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. */
        {
        	/* 这个for循环的作用是,找到一个已经处于链表中的节点,
        	该节点的节点值小于新节点的节点值,该节点当前的后继节点的节点值大于新节点的节点值 */
            /* There is nothing to do here, just iterating to the wanted
             * insertion position. */
        }
    }
    /* 将待插入节点插入到上面的for循环所找到的节点的next位置 */
    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)++;            /* 更新链表中的节点数 */
}

在这里插入图片描述
整个链表的结构如上图所示,FreeRTOS内核中的链表为双向环形链表,我们将尾部标识节点称为链表的尾节点,将尾节点的后继节点称为链表的头节点,顺着pxNext的方向看,将新节点插入到链表中时,按照xItemValue值升序的顺序插入,即头节点的xItemValue应该是链表中最小的,尾节点的xItemValue是portMAX_DELAY,是系统里的最大值。
这样做的好处是,在后续进行任务调度时,无需遍历延时等待链表,直接访问延时等待链表的头节点就可以获取即将最先进行调度的任务(这部分内容将在任务调度部分详述)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值