【转】FreeRTOS 内核中的链表

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

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

[cpp] view plain copy
  1. typedef struct xLIST  
  2. {  
  3.     volatile unsigned portBASE_TYPE uxNumberOfItems; // 链表中有多少元素  
  4.     volatile xListItem * pxIndex;    
  5.     volatile xMiniListItem xListEnd;   
  6. } xList;  
  7. struct xLIST_ITEM  
  8. {   
  9.     portTickType xItemValue;   
  10.     volatile struct xLIST_ITEM * pxNext;  
  11.     volatile struct xLIST_ITEM * pxPrevious;   
  12.     void * pvOwner;   
  13.     void * pvContainer;   
  14. };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 的前后指针都指向自己。

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

[cpp] view plain copy
  1. void vListInitialise( xList *pxList )  
  2. {   
  3.     pxList->pxIndex = ( xListItem * ) &( pxList->xListEnd );   
  4.     pxList->xListEnd.xItemValue = portMAX_DELAY;   
  5.     pxList->xListEnd.pxNext = ( xListItem * ) &( pxList->xListEnd );   
  6.     pxList->xListEnd.pxPrevious = ( xListItem * ) &( pxList->xListEnd );   
  7.     pxList->uxNumberOfItems = ( unsigned portBASE_TYPE ) 0U;   
  8. }  

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

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

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

[cpp] view plain copy
  1. void vListInsert( xList *pxList, xListItem *pxNewListItem )  
  2. {   
  3.     volatile xListItem *pxIterator;   
  4.     portTickType xValueOfInsertion;   
  5.     xValueOfInsertion = pxNewListItem->xItemValue;   
  6.     if( xValueOfInsertion == portMAX_DELAY )   
  7.     {    
  8.         pxIterator = pxList->xListEnd.pxPrevious;    
  9.     }   
  10.     else   
  11.     {     
  12.         for( pxIterator = ( xListItem * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext );    
  13.     }   
  14.     pxNewListItem->pxNext = pxIterator->pxNext;   
  15.     pxNewListItem->pxNext->pxPrevious = ( volatile xListItem * ) pxNewListItem;   
  16.     pxNewListItem->pxPrevious = pxIterator;   
  17.     pxIterator->pxNext = ( volatile xListItem * ) pxNewListItem;   
  18.     pxNewListItem->pvContainer = ( void * ) pxList;   
  19.     ( pxList->uxNumberOfItems )++;   
  20. }  

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

[cpp] view plain copy
  1. void vListInsertEnd( xList *pxList, xListItem *pxNewListItem )  
  2. {     
  3.     volatile xListItem * pxIndex;  
  4.       
  5.     pxIndex = pxList->pxIndex;      
  6.     pxNewListItem->pxNext = pxIndex->pxNext;      
  7.     pxNewListItem->pxPrevious = pxList->pxIndex;      
  8.     pxIndex->pxNext->pxPrevious = ( volatile xListItem * ) pxNewListItem;      
  9.     pxIndex->pxNext = ( volatile xListItem * ) pxNewListItem;      
  10.     pxList->pxIndex = ( volatile xListItem * ) pxNewListItem;      
  11.     pxNewListItem->pvContainer = ( void * ) pxList;      
  12.     ( pxList->uxNumberOfItems )++;  
  13. }  


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

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

[cpp] view plain copy
  1. void vListRemove( xListItem *pxItemToRemove )  
  2. {      
  3.     xList * pxList;      
  4.     pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;      
  5.     pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;      
  6.     pxList = ( xList * ) pxItemToRemove->pvContainer;          
  7.     if( pxList->pxIndex == pxItemToRemove )      
  8.     {          
  9.         pxList->pxIndex = pxItemToRemove->pxPrevious;          
  10.     }      
  11.     pxItemToRemove->pvContainer = NULL;      
  12.     ( pxList->uxNumberOfItems )--;      
  13. }  


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

[cpp] view plain copy
  1. #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \  
  2. {     
  3.     xList * const pxConstList = ( pxList ); \     
  4.     ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \   
  5.     if( ( pxConstList )->pxIndex == ( xListItem * ) &( ( pxConstList )->xListEnd ) ) \      
  6.     { \       
  7.         ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \       
  8.     } \   
  9.     ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \    
  10. }  

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


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