freertos内核走读1——链表和heap

Freertos内核代码走读之基础数据结构。

本文为jorhai原创,转载请注明,谢谢!

Freertos作为一个小巧精致的实时内核,开源免费,有着不错的性能,易于集成,越来越受欢迎,本系列走读freertos 823源码,深入了解freertos的内核机制,本文先从基础数据结构介绍,主要包括链表和heap。

Freertos使用的是改良的双向链表;其定义如下:
其list item 增加了完整性检测(configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 配置),增加了list item的owner(因为list在任务中被使用,owner一般指的就是任务上下午TCB),增加了list的item的容器(指定item所在的list),xItemValue则是链表进行排序时的参考值,list在不同地方引用,一般需要按照xItemValue进行排序(一般task中引用按照delay时间排序,queue中按照优先级排序,timer中按照超时时间排序)。Freertos代码很精简高效,这种改良的list在额外开销和效率之间做了一个很好的平衡,除此之外,还专门定义缩减的list item,用于表示链表结构中的首尾链接item,xMINI_LIST_ITEM前面字节定义和xLIST_ITEM一致,可以直接转型为xLIST_ITEM,以读取链表的首尾元素。xMINI_LIST_ITEM在别的地方没有使用,可能仅仅是为了减少内存占用。

/*
 * Definition of the only type of object that a list can contain.
 */
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. */
    void * configLIST_VOLATILE pvContainer;             /*< 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;                   /* For some reason lint wants this as two separate definitions. */

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;

/*
 * 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. */
    configLIST_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;

List操作和一般双向链表大同小异。个人认为需要注意的有一下几点:
listGET_OWNER_OF_NEXT_ENTRY:编译整个链表,返回链表每个元素的owner。

#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;                                          \
}

vListInsertEnd插入一个元素,但是不是插入到链表的结尾,而是插入到listGET_OWNER_OF_NEXT_ENTRY遍历的最后一次结果前,也就是说按照遍历的顺序插入到最后,而不是链表本身的最后。

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->pvContainer = ( void * ) pxList;

    ( pxList->uxNumberOfItems )++;
}

vListInsert这个函数是按照排序插入的,排序值为xItemValue。因此freertos下的链表其实就是排序过的链表了。

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->pvContainer = ( void * ) pxList;

    ( pxList->uxNumberOfItems )++;
}

Heap操作:
任何操作几乎都涉及到内存的管理,Freertos下的大概分为两种:1.c标准库malloc和free;2.heap_x.c。
C标准库的内存管理一般由编译器实现,具体实现的质量就不好定义了,我接触过的就出现过内存碎片化严重,最后malloc失败的情况。如果你想使用C标准库进行内存获取释放,建议你还是写个小程序在实际环境中进行下测试。除此之外你还可以选着freertos提供的heap_x.c算法或者自己实现。

Heap_x.c:
Heap_x.c实现了5中“堆”管理方法,注意堆加上了引号,是因为有些“堆”被定义为全局数组,并且没有初始化,实际上实在bss段,加载时会被清零。
Heap_1的实现:支持从heap中动态分配不同大小的内存,但是不支持释放,因为不支持释放,因此也没有增加buffer控制头,没有额外的开销。
Heap_2的实现:支持从heap中动态分配不同大小内存,支持释放,但是不支持碎片化管理。Heap_2在每个buffer的实际地址开始前,增加了控制头来管理buffer。结构如下:

typedef struct A_BLOCK_LINK
{
struct A_BLOCK_LINK pxNextFreeBlock; /<< The next free block in the list. */
size_t xBlockSize; /<< The size of the free block. /
} BlockLink_t;

Heap_3使用了前面提到的c标准库里的malloc和free。
Heap_4和heap_5几乎一致:都是在heap2的基础上增加了碎片化管理,其中heap_5较heap_4增加了外部定义heap区的功能vPortDefineHeapRegions,用户可以自己指定多个地址段作为heap区。

这里简单画了一个图解释Heap_4的算法实现。
Heap4使用一个简单的单项链表free block list完成了管理,每次分配的内存前面都会加上BlockLink_t标记该内存块的大小,pxNextFreeBlock则指向下一个可用的内存块。内存块的Free list很巧妙的按照地址大小进行排序,pxNextFreeBlock指向下一个buffer空间的地址一定比自己的地址大。
整个碎片管理的流程如下图:
freertos内核走读1——链表和heap_第1张图片
依据freelist的地址排序可以精确查找到需要释放内存的位置,并且根据pxNextFreeBlock和插入位置的元素地址加xBlockSize可以判断出是否为连续地址,然后进行合并。Buffer申请时则遍历freelist,如果free block大小满足,则进行分裂(free block大小和申请一致则不用分裂),生成新的free list。
具体可以详细阅读器代码实现,代码太长这里就不贴了。

Heap_4的缺点是:如果分配较多的buffer,无论释放或者申请都需要遍历整个freelist,效率可能受到影响,碎片化的管理相对比较简单。可以考虑别的替代方法。比较经典的就是buffer pool。
以链表的方式管理n个大小一致的内存池:
freertos内核走读1——链表和heap_第2张图片
内存池的大小往往为支持分配的最大值,这种方式比较浪费内存,改进的方案是,分配x个大小不同的内存池。
freertos内核走读1——链表和heap_第3张图片
这样减少了内存浪费。如果要实现内存池的动态扩充,buffer的添加和删除,buffer pool的结构优化(分配多少大小的池,每个池的buffer总数等等),buffer的泄露检测等等功能,还需要自己额外实现了。

你可能感兴趣的:(freertos内核)