您的位置:首页 > 其它

FreeRTOS 内核中的链表

2012-05-13 17:11 113 查看
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
的最后,这时就要用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内核 ,上面这些已经够了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: