FreeRTOS 内核中的链表

来源:互联网 发布:怎样让淘宝店铺排名靠前 编辑:程序博客网 时间:2024/05/10 16:25

FreeRTOS 内核中采用双向循环链表来进行任务调度,对任务总数没有限制,同一优先级的任务数也没有限制。相对于uC/OS-II 来说是一个大的优点。不过,有利必有弊。采用双向链表后代码相对来说要复杂一些。

FreeRTOS 中的链表和链表元素的定义如下:

typedef struct xLIST{    volatile unsigned portBASE_TYPE uxNumberOfItems; // 链表中有多少元素    volatile xListItem * pxIndex;      volatile xMiniListItem xListEnd; } xList;struct xLIST_ITEM{     portTickType xItemValue;     volatile struct xLIST_ITEM * pxNext;    volatile struct xLIST_ITEM * pxPrevious;     void * pvOwner;     void * pvContainer; };typedef struct xLIST_ITEM xListItem;


先从链表元素开始分析,FreeRTOS 中的 xListItem 在其他代码中通常被称为 Node,也就是链表节点。

结构体中各个部分的含义如下:

xItemValue:存放链表节点的值,在 FreeRTOS 中,用这个值来存放时间,用于延时相关的功能。因此,这个变量的类型是 portTickType。

pxNext 和 pxPrevious: 是双向链表两个方向的指针。分别指向后一个元素和前一个节点。

pvOwner:xListItem 本身是包含在 tskTCB 结构中的(tskTCB结构存放任务的各种信息,一个tskTCB 对应的就是一个任务),pvOwner 指向所在的 tskTCB 结构。因此,通过xListItem 就知道对应的是哪个任务了。

pvContainer:pvContainer 指向它所在的xList,根据这个指针,就能知道任务是在就绪任务链表中还是阻塞任务链表中。当xListItem不在任何链表中时,pvContainer的值为 NULL。

链表节点只有初始化后采用使用,初始化函数是:void vListInitialiseItem( xListItem *pxItem ),初始化函数只做了一件事,就是将pvContainer 设为 NULL。

xList 表示对应的链表。各个元素的含义如下:

uxNumberOfItems:记录链表中有多少个节点(不包含xListEnd节点)。

pxIndex:是一个指针,用来遍历链表。

xListEnd:是链表中一个不能删除的节点。xListEnd 的后续节点指向的就是真正有用的链表节点。当没有其他链表元素时,xListEnd 的前后指针都指向自己。

链表初始化函数比较简单,下面列出代码:

void vListInitialise( xList *pxList ){     pxList->pxIndex = ( xListItem * ) &( pxList->xListEnd );     pxList->xListEnd.xItemValue = portMAX_DELAY;     pxList->xListEnd.pxNext = ( xListItem * ) &( pxList->xListEnd );     pxList->xListEnd.pxPrevious = ( xListItem * ) &( pxList->xListEnd );     pxList->uxNumberOfItems = ( unsigned portBASE_TYPE ) 0U; }

链表操作主要有两大方面:将节点插入到链表,和从链表中删除节点。下面分别介绍。

FreeRTOS 将节点插入到链表又有两种操作。一个是将节点按照升序插入到链表的合适的位置。第二个是将节点插入到链表的结尾。

vListInsert 函数将节点按照升序插入到链表的合适的位置,前提是链表原本就是按照升序排序的。算法很简单,就是从第一个元素(xListEnd.pxNext)开始往后遍历,直到找到合适的位置,然后将节点插进去。

void vListInsert( xList *pxList, xListItem *pxNewListItem ){     volatile xListItem *pxIterator;     portTickType xValueOfInsertion;     xValueOfInsertion = pxNewListItem->xItemValue;     if( xValueOfInsertion == portMAX_DELAY )     {          pxIterator = pxList->xListEnd.pxPrevious;      }     else     {           for( pxIterator = ( xListItem * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext );      }     pxNewListItem->pxNext = pxIterator->pxNext;     pxNewListItem->pxNext->pxPrevious = ( volatile xListItem * ) pxNewListItem;     pxNewListItem->pxPrevious = pxIterator;     pxIterator->pxNext = ( volatile xListItem * ) pxNewListItem;     pxNewListItem->pvContainer = ( void * ) pxList;     ( pxList->uxNumberOfItems )++; }

vListInsertEnd 函数将节点插入到链表的最后。所谓的最后,其实就是让  pxList->pxIndex 指向它就行了,因为遍历时是从 pxIndex->pxNext 的开始的,遍历一圈之后最后才能到 pxIndex。算法很简单,下面是代码。

void vListInsertEnd( xList *pxList, xListItem *pxNewListItem ){       volatile xListItem * pxIndex;        pxIndex = pxList->pxIndex;        pxNewListItem->pxNext = pxIndex->pxNext;        pxNewListItem->pxPrevious = pxList->pxIndex;        pxIndex->pxNext->pxPrevious = ( volatile xListItem * ) pxNewListItem;        pxIndex->pxNext = ( volatile xListItem * ) pxNewListItem;        pxList->pxIndex = ( volatile xListItem * ) pxNewListItem;        pxNewListItem->pvContainer = ( void * ) pxList;        ( pxList->uxNumberOfItems )++;}


从这两种将节点插入到链表的方法也能看出它们是不能混用的,将节点插入到链表最后是不能保证链表是升序排列的。FreeRTOS 的作者当然明白这个道理。在 FreeRTOS 中这两个函数各有自己的用途,当我们需要将一个任务延时一定时间时,使用vListInsert函数将其插入到xDelayedTaskList1 或xDelayedTaskList2。具体插入到那个链表要看延时值是否溢出了。而将一个任务设置为就绪状态时要将其按照优先级n 插入到pxReadyTasksLists[n]的最后,这时就要用vListInsertEnd函数了。插入到xPendingReadyList 和 xTasksWaitingTermination 链表时也要插入到最后,因此也用vListInsertEnd函数。总结一下,只有涉及到延时时才会用到vListInsert函数。

vListRemove 将节点从链表中删除,下面是代码。

void vListRemove( xListItem *pxItemToRemove ){        xList * pxList;        pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;        pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;        pxList = ( xList * ) pxItemToRemove->pvContainer;            if( pxList->pxIndex == pxItemToRemove )        {                pxList->pxIndex = pxItemToRemove->pxPrevious;            }        pxItemToRemove->pvContainer = NULL;        ( pxList->uxNumberOfItems )--;    }


链表的另一个重要操作就是遍历链表,在 FreeRTOS 源码中用宏来实现的。算法很简单,就是将 pxIndex 指向的下一个节点的对应的 TCB 结构指针返回来,然后pxIndex向后挪移个位置。需要注意的就是pxIndex的下一个节点是 xListEnd 的时候要再向后挪一下。

#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \{ xList * const pxConstList = ( pxList ); \( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \if( ( pxConstList )->pxIndex == ( xListItem * ) &( ( pxConstList )->xListEnd ) ) \{ \( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \} \( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \}

至此,链表有关的操作就都分析完了。实际上 FreeRTOS 源码中只用到了双向循环链表的最基本的功能。上面的分析也非常粗略,如果想更深入的学习链表的相关知识,可以找本算法或数据结构的教材来看看。不过,对于理解 FreeRTOS内核 ,上面这些已经够了。