找回密码
 立即注册
首页 业界区 安全 FreeRTOS简单内核实现2 双向链表

FreeRTOS简单内核实现2 双向链表

簧横 3 天前
FreeRTOS Kernel V10.3.1
FreeRTOS 的 list.c / list.h 文件中有 3 个数据结构、2 个初始化函数、2 个插入函数、1 个移除函数和一些宏函数,链表是 FreeRTOS 中的重要数据结构,下述 “1、数据结构” 和 “2、操作链表” 两个小节内容主要对其原理进行讲解
1、数据结构

1.1、xLIST_ITEM

链表项,即节点,通常用链表项来表示一个任务
  1. struct xLIST_ITEM
  2. {
  3.         // 检验一个 链表项 数据是否完整
  4.     listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
  5.     // 排序值
  6.     configLIST_VOLATILE TickType_t xItemValue;
  7.     // 下一个 链表项
  8.     struct xLIST_ITEM * configLIST_VOLATILE pxNext;
  9.     // 前一个 链表项
  10.     struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
  11.     // 记录此 链表项 归谁拥有,通常是 TCB (任务控制块)
  12.     void * pvOwner;
  13.     // 拥有该 链表项 的 链表
  14.     struct xLIST * configLIST_VOLATILE pxContainer;
  15.     // 检验一个 链表项 数据是否完整
  16.     listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
  17. };
  18. typedef struct xLIST_ITEM ListItem_t;
复制代码
1.png

1.2、XMINI_LIST_ITEM

MINI 链表项,链表项的缩减版,专门用于表示链表尾节点,在 32 位 MCU 上不启用链表项数据完整性校验的情况下相比于普通的链表项节省了 8 个字节(两个指针)
  1. struct xMINI_LIST_ITEM
  2. {
  3.         // 检验一个 MINI链表项 数据是否完整
  4.     listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
  5.     // 排序值
  6.     configLIST_VOLATILE TickType_t xItemValue;
  7.     // 下一个 链表项
  8.     struct xLIST_ITEM * configLIST_VOLATILE pxNext;
  9.     // 前一个 链表项
  10.     struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
  11. };
  12. typedef struct xMINI_LIST_ITEM MiniListItem_t;
复制代码
2.png

1.3、xLIST

由多个链表项构成的链表,常用于区分不同任务状态或任务优先级,比如就绪状态的任务放在就绪链表中,阻塞状态的任务放在阻塞链表中,方便任务管理
  1. typedef struct xLIST
  2. {
  3.         // 检验一个 链表 数据是否完整
  4.     listFIRST_LIST_INTEGRITY_CHECK_VALUE
  5.     // 记录 链表 中 链表项 数目
  6.     volatile UBaseType_t uxNumberOfItems;
  7.     // 遍历 链表 的指针
  8.     ListItem_t * configLIST_VOLATILE pxIndex;
  9.     // 使用 MINI链表项 表示 链表尾部
  10.     MiniListItem_t xListEnd;
  11.     // 检验一个 链表 数据是否完整
  12.     listSECOND_LIST_INTEGRITY_CHECK_VALUE
  13. } List_t;
复制代码
3.png

2、操作链表

注意:由于不涉及数据校验完整性,因此下述函数中关于校验的所有部分将被删除
2.1、初始化

2.1.1、vListInitialiseItem( )

初始化链表项函数
将链表项的 pxContainer 成员设置为 NULL ,因为初始化的时候该链表项未被任何链表包含
  1. void vListInitialiseItem( ListItem_t * const pxItem )  
  2. {  
  3.     // 确保链表项未被记录在链表中
  4.     pxItem->pxContainer = NULL;
  5. }
复制代码
4.png

2.1.2、vListInitialise( )

初始化链表函数,具体步骤如下

  • 将链表当前指针 pxIndex 指向尾链表项 xListEnd
  • 确保尾链表项 xListEnd 被排在链表最尾部
  • 将尾链表项 xListEnd 的前/后链表项指针均指向自己,因为初始化链表时只有尾链表项
  • 链表中有 0 个链表项
  1. void vListInitialise( List_t * const pxList )  
  2. {  
  3.     // 链表当前指针指向 xListEnd
  4.     pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
  5.    
  6.     // 设置链表尾链表项排序值为最大, 保证 xListEnd 会被放在链表的尾部
  7.     pxList->xListEnd.xItemValue = portMAX_DELAY;
  8.   
  9.     // 尾链表项 xListEnd 的前/后链表项指针均指向自己
  10.     pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
  11.     pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
  12.         // 初始化时链表中有 0 个链表项
  13.     pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
  14. }
复制代码
5.png

2.2、插入

2.2.1、vListInsertEnd( )

将一个链表项插入到链表当前指针 pxIndex 指向的链表项前面,具体步骤如下

  • 改变要插入链表项自身的 pxNext 和 pxPrevious
  • 改变前一个链表项(也就是 pxIndex 指向的链表项的 Previous)的 pxNext
  • 改变后一个链表项(也就是 pxIndex 指向的链表项)的 pxPrevious
  1. void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )  
  2. {
  3.         // 获取链表中当前指针 pxIndex 位置
  4.         ListItem_t * const pxIndex = pxList->pxIndex;
  5.          
  6.         // 1. 改变自身 pxNext 和 pxPrevious
  7.     pxNewListItem->pxNext = pxIndex;
  8.     pxNewListItem->pxPrevious = pxIndex->pxPrevious;
  9.    
  10.         // 2. 改变前一个链表项的 pxNext
  11.     pxIndex->pxPrevious->pxNext = pxNewListItem;
  12.     // 3. 改变后一个链表项的 pxPrevious
  13.     pxIndex->pxPrevious = pxNewListItem;
  14.           
  15.     // 标记新插入的链表项所在的链表
  16.     pxNewListItem->pxContainer = pxList;
  17.         // 链表数量增加一
  18.     ( pxList->uxNumberOfItems )++;
  19. }
复制代码
为方便绘图演示,将链表的结构在图上做了简化,具体如下图所示
6.png

注意:这里 pxList->pxIndex 自初始化以来从未修改过,保持指向链表 xListEnd 链表项,下图所有演示中,橙色虚线表示该步骤做了修改,黑色实线表示与上一步骤相比无修改
插入一个链表项
7.png

插入第二个链表项
8.png

插入第三个链表项
9.png

插入第四个链表项
10.png

2.2.2、vListInsert( )

将一个链表项按照链表中所有链表项的 xItemValue 值大小升序插入链表中,具体步骤如下

  • 找到应该插入的位置(应该插入到 pxIterator 的后面)
  • 改变要插入链表项自身 pxNext 和 pxPrevious
  • 改变后一个链表项的 pxPrevious
  • 改变前一个链表项的 pxNext (也即 pxIterator 指向的链表项的 pxNext)
  1. void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )  
  2. {
  3.         ListItem_t *pxIterator;
  4.         // 记录要插入的链表项的排序值
  5.         const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
  6.        
  7.         // 如果新插入的链表项排序值为最大值,直接插到尾节点 xListEnd 的前面
  8.         if( xValueOfInsertion == portMAX_DELAY )  
  9.         {  
  10.             pxIterator = pxList->xListEnd.pxPrevious;  
  11.         }
  12.         else  
  13.         {
  14.                 /*
  15.                 1. 遍历链表,将当前链表项 pxIterator 与要插入的新的链表项 pxNewListItem
  16.                 的 xItemValue 值比较,直到 pxIterator 的 xItemValue 大于 pxNewListItem
  17.                 的 xItemValue 值,此时 pxNewListItem 应该插入到 pxIterator 的后面
  18.                 */
  19.                 for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
  20.                          pxIterator->pxNext->xItemValue <= xValueOfInsertion;
  21.                          pxIterator = pxIterator->pxNext ){}
  22.         }
  23.         // 2. 改变要插入链表项自身 pxNext 和 pxPrevious
  24.         pxNewListItem->pxNext = pxIterator->pxNext;
  25.         pxNewListItem->pxPrevious = pxIterator;
  26.         // 3. 改变后一个链表项的 pxPrevious
  27.         pxNewListItem->pxNext->pxPrevious = pxNewListItem;
  28.         // 4. 改变前一个链表项的 pxNext
  29.         pxIterator->pxNext = pxNewListItem;
  30.        
  31.         // 标记新插入的链表项所在的链表
  32.         pxNewListItem->pxContainer = pxList;
  33.         // 链表数量增加一
  34.         ( pxList->uxNumberOfItems )++;
  35. }
复制代码
11.png

2.3、移除

2.3.1、uxListRemove( )

从链表中移除指定的链表项,具体步骤如下

  • 改变后一个链表项的 pxPrevious
  • 改变前一个链表项的 pxNext
  1. UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )  
  2. {  
  3.         List_t * const pxList = pxItemToRemove->pxContainer;  
  4.        
  5.         // 1. 改变后一个链表项 pxPrevious
  6.     pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
  7.     // 2. 改变前一个链表项 pxNext
  8.     pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
  9.           
  10.     // 确保索引指向有效的项目
  11.     if( pxList->pxIndex == pxItemToRemove )  
  12.     {  
  13.        pxList->pxIndex = pxItemToRemove->pxPrevious;  
  14.     }
  15.        
  16.         // 从链表中移除链表项后,该链表项不属于任何链表
  17.     pxItemToRemove->pxContainer = NULL;  
  18.     // 链表中链表项的数量减一
  19.     ( pxList->uxNumberOfItems )--;  
  20.        
  21.         // 返回链表中链表项的数量
  22.     return pxList->uxNumberOfItems;  
  23. }
复制代码
12.png

2.4、宏函数

2.4.1、设置相关
  1. // 设置 pxListItem 的 pxOwner 成员
  2. #define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )
  3. // 设置 pxListItem 的 xValue 成员值
  4. #define listSET_LIST_ITEM_VALUE( pxListItem, xValue )
复制代码
2.4.2、获取相关
  1. // 获取 pxListItem 的 pxOwner 成员
  2. #define listGET_LIST_ITEM_OWNER( pxListItem )
  3. // 获取 pxListItem 的 xValue 成员值
  4. #define listGET_LIST_ITEM_VALUE( pxListItem )
  5. // 获取链表中头链表项的 xItemValue 成员值
  6. #define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )
  7. // 获取链表中头链表项地址
  8. #define listGET_HEAD_ENTRY( pxList )
  9. // 获取某个链表项的下一个链表项地址
  10. #define listGET_NEXT( pxListItem )
  11. // 获取链表中 xListEnd 的地址
  12. #define listGET_END_MARKER( pxList )
  13. // 获取链表当前长度
  14. #define listCURRENT_LIST_LENGTH( pxList )
  15. // 将链表中 pxIndex 指向下一个链表项,用于获取下一个链表项(任务)
  16. #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )
  17. // 获取链表中头链表项的 pvOwner 成员
  18. #define listGET_OWNER_OF_HEAD_ENTRY( pxList )
  19. // 获取链表项的 pxContainer 成员
  20. #define listLIST_ITEM_CONTAINER( pxListItem )
复制代码
2.4.3、判断相关
  1. // 判断链表是否为空
  2. #define listLIST_IS_EMPTY( pxList )
  3. // 判断链表项是否和链表匹配(链表项是否在该链表中)
  4. #define listIS_CONTAINED_WITHIN( pxList, pxListItem )
  5. // 判断链表是否被初始化
  6. #define listLIST_IS_INITIALISED( pxList )
复制代码
3、链表移植

下面直接将 FreeRTOS 内核中 list.c / list.h 文件移植到自己的工程中使用(当然,如果你已经懂得双向链表数据结构的原理,你也可以构建属于你自己的 list.c / list.h 文件)
移植可以总结为以下 4 个步骤

  • 用 FreeRTOS Kernel V10.3.1 内核中 list.c / list.h 替换掉 RTOS 文件夹中的同名文件
  • 在 portMacro.h 中统一一些用到基本类型
  1. /* portMacro.h */
  2. #include <stdint.h>
  3. #define port_CHAR                   char
  4. #define port_FLOAT                  float
  5. #define port_DOUBLE                 double
  6. #define port_LONG                   long
  7. #define port_SHORT                  short
  8. #define port_STACK_TYPE             unsigned int
  9. #define port_BASE_TYPE              long
  10. typedef port_STACK_TYPE             StackType_t;
  11. typedef long                        BaseType_t;
  12. typedef unsigned long               UBaseType_t;
  13. typedef port_STACK_TYPE*            StackType_p;
  14. typedef long*                       BaseType_p;
  15. typedef unsigned long*              UBaseType_p;
  16. #if(configUSE_16_BIT_TICKS == 1)
  17.     typedef uint16_t                TickType_t;
  18.     #define portMAX_DELAY           (TickType_t) 0xffff
  19. #else
  20.     typedef uint32_t                TickType_t;
  21.     #define portMAX_DELAY           (TickType_t) 0xffffffffUL
  22. #endif
  23. #define pdFALSE                     ((BaseType_t) 0)
  24. #define pdTRUE                      ((BaseType_t) 1)
  25. #define pdPASS                      (pdTRUE)
  26. #define pdFAIL                      (pdFALSE)
复制代码
configUSE_16_BIT_TICKS 是一个宏,用于 TickType_t 类型位数,具体定义如下
  1. /* FreeRTOSConfig.h */
  2. // 设置 TickType_t 类型位 16 位
  3. #define configUSE_16_BIT_TICKS                  0
复制代码

  • 删除掉 list.c 文件中所有的 mtCOVERAGE_TEST_DELAY() 测试函数
  • 删除掉 list.h 文件中所有的 PRIVILEGED_FUNCTION 宏
完成后编译工程应该不会出现错误,这样实现 RTOS 简单内核关键的双链表数据结构就完成了

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册