FreeRTOS 内核中的链表

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内核 ,上面这些已经够了。

你可能感兴趣的:(FreeRTOS 内核中的链表)