FreeRTOS Kernel V10.3.1
FreeRTOS 的 list.c / list.h 文件中有 3 个数据结构、2 个初始化函数、2 个插入函数、1 个移除函数和一些宏函数,链表是 FreeRTOS 中的重要数据结构,下述 “1、数据结构” 和 “2、操作链表” 两个小节内容主要对其原理进行讲解
1、数据结构
1.1、xLIST_ITEM
链表项,即节点,通常用链表项来表示一个任务
struct xLIST_ITEM { // 检验一个 链表项 数据是否完整 listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE // 排序值 configLIST_VOLATILE TickType_t xItemValue; // 下一个 链表项 struct xLIST_ITEM * configLIST_VOLATILE pxNext; // 前一个 链表项 struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; // 记录此 链表项 归谁拥有,通常是 TCB (任务控制块) void * pvOwner; // 拥有该 链表项 的 链表 struct xLIST * configLIST_VOLATILE pxContainer; // 检验一个 链表项 数据是否完整 listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE }; typedef struct xLIST_ITEM ListItem_t;

1.2、XMINI_LIST_ITEM
MINI 链表项,链表项的缩减版,专门用于表示链表尾节点,在 32 位 MCU 上不启用链表项数据完整性校验的情况下相比于普通的链表项节省了 8 个字节(两个指针)
struct xMINI_LIST_ITEM { // 检验一个 MINI链表项 数据是否完整 listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE // 排序值 configLIST_VOLATILE TickType_t xItemValue; // 下一个 链表项 struct xLIST_ITEM * configLIST_VOLATILE pxNext; // 前一个 链表项 struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; }; typedef struct xMINI_LIST_ITEM MiniListItem_t;

1.3、xLIST
由多个链表项构成的链表,常用于区分不同任务状态或任务优先级,比如就绪状态的任务放在就绪链表中,阻塞状态的任务放在阻塞链表中,方便任务管理
typedef struct xLIST { // 检验一个 链表 数据是否完整 listFIRST_LIST_INTEGRITY_CHECK_VALUE // 记录 链表 中 链表项 数目 volatile UBaseType_t uxNumberOfItems; // 遍历 链表 的指针 ListItem_t * configLIST_VOLATILE pxIndex; // 使用 MINI链表项 表示 链表尾部 MiniListItem_t xListEnd; // 检验一个 链表 数据是否完整 listSECOND_LIST_INTEGRITY_CHECK_VALUE } List_t;

2、操作链表
注意:由于不涉及数据校验完整性,因此下述函数中关于校验的所有部分将被删除
2.1、初始化
2.1.1、vListInitialiseItem( )
初始化链表项函数
将链表项的 pxContainer 成员设置为 NULL ,因为初始化的时候该链表项未被任何链表包含
void vListInitialiseItem( ListItem_t * const pxItem ) { // 确保链表项未被记录在链表中 pxItem->pxContainer = NULL; }

2.1.2、vListInitialise( )
初始化链表函数,具体步骤如下
- 将链表当前指针 pxIndex 指向尾链表项 xListEnd
- 确保尾链表项 xListEnd 被排在链表最尾部
- 将尾链表项 xListEnd 的前/后链表项指针均指向自己,因为初始化链表时只有尾链表项
- 链表中有 0 个链表项
void vListInitialise( List_t * const pxList ) { // 链表当前指针指向 xListEnd pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); // 设置链表尾链表项排序值为最大, 保证 xListEnd 会被放在链表的尾部 pxList->xListEnd.xItemValue = portMAX_DELAY; // 尾链表项 xListEnd 的前/后链表项指针均指向自己 pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd ); // 初始化时链表中有 0 个链表项 pxList->uxNumberOfItems = ( UBaseType_t ) 0U; }

2.2、插入
2.2.1、vListInsertEnd( )
将一个链表项插入到链表当前指针 pxIndex 指向的链表项前面,具体步骤如下
- 改变要插入链表项自身的 pxNext 和 pxPrevious
- 改变前一个链表项(也就是 pxIndex 指向的链表项的 Previous)的 pxNext
- 改变后一个链表项(也就是 pxIndex 指向的链表项)的 pxPrevious
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem ) { // 获取链表中当前指针 pxIndex 位置 ListItem_t * const pxIndex = pxList->pxIndex; // 1. 改变自身 pxNext 和 pxPrevious pxNewListItem->pxNext = pxIndex; pxNewListItem->pxPrevious = pxIndex->pxPrevious; // 2. 改变前一个链表项的 pxNext pxIndex->pxPrevious->pxNext = pxNewListItem; // 3. 改变后一个链表项的 pxPrevious pxIndex->pxPrevious = pxNewListItem; // 标记新插入的链表项所在的链表 pxNewListItem->pxContainer = pxList; // 链表数量增加一 ( pxList->uxNumberOfItems )++; }
为方便绘图演示,将链表的结构在图上做了简化,具体如下图所示

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

插入第二个链表项

插入第三个链表项

插入第四个链表项

2.2.2、vListInsert( )
将一个链表项按照链表中所有链表项的 xItemValue 值大小升序插入链表中,具体步骤如下
- 找到应该插入的位置(应该插入到 pxIterator 的后面)
- 改变要插入链表项自身 pxNext 和 pxPrevious
- 改变后一个链表项的 pxPrevious
- 改变前一个链表项的 pxNext (也即 pxIterator 指向的链表项的 pxNext)
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem ) { ListItem_t *pxIterator; // 记录要插入的链表项的排序值 const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; // 如果新插入的链表项排序值为最大值,直接插到尾节点 xListEnd 的前面 if( xValueOfInsertion == portMAX_DELAY ) { pxIterator = pxList->xListEnd.pxPrevious; } else { /* 1. 遍历链表,将当前链表项 pxIterator 与要插入的新的链表项 pxNewListItem 的 xItemValue 值比较,直到 pxIterator 的 xItemValue 大于 pxNewListItem 的 xItemValue 值,此时 pxNewListItem 应该插入到 pxIterator 的后面 */ for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ){} } // 2. 改变要插入链表项自身 pxNext 和 pxPrevious pxNewListItem->pxNext = pxIterator->pxNext; pxNewListItem->pxPrevious = pxIterator; // 3. 改变后一个链表项的 pxPrevious pxNewListItem->pxNext->pxPrevious = pxNewListItem; // 4. 改变前一个链表项的 pxNext pxIterator->pxNext = pxNewListItem; // 标记新插入的链表项所在的链表 pxNewListItem->pxContainer = pxList; // 链表数量增加一 ( pxList->uxNumberOfItems )++; }

2.3、移除
2.3.1、uxListRemove( )
从链表中移除指定的链表项,具体步骤如下
- 改变后一个链表项的 pxPrevious
- 改变前一个链表项的 pxNext
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove ) { List_t * const pxList = pxItemToRemove->pxContainer; // 1. 改变后一个链表项 pxPrevious pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; // 2. 改变前一个链表项 pxNext pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext; // 确保索引指向有效的项目 if( pxList->pxIndex == pxItemToRemove ) { pxList->pxIndex = pxItemToRemove->pxPrevious; } // 从链表中移除链表项后,该链表项不属于任何链表 pxItemToRemove->pxContainer = NULL; // 链表中链表项的数量减一 ( pxList->uxNumberOfItems )--; // 返回链表中链表项的数量 return pxList->uxNumberOfItems; }

2.4、宏函数
2.4.1、设置相关
// 设置 pxListItem 的 pxOwner 成员 #define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner ) // 设置 pxListItem 的 xValue 成员值 #define listSET_LIST_ITEM_VALUE( pxListItem, xValue )
2.4.2、获取相关
// 获取 pxListItem 的 pxOwner 成员 #define listGET_LIST_ITEM_OWNER( pxListItem ) // 获取 pxListItem 的 xValue 成员值 #define listGET_LIST_ITEM_VALUE( pxListItem ) // 获取链表中头链表项的 xItemValue 成员值 #define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList ) // 获取链表中头链表项地址 #define listGET_HEAD_ENTRY( pxList ) // 获取某个链表项的下一个链表项地址 #define listGET_NEXT( pxListItem ) // 获取链表中 xListEnd 的地址 #define listGET_END_MARKER( pxList ) // 获取链表当前长度 #define listCURRENT_LIST_LENGTH( pxList ) // 将链表中 pxIndex 指向下一个链表项,用于获取下一个链表项(任务) #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) // 获取链表中头链表项的 pvOwner 成员 #define listGET_OWNER_OF_HEAD_ENTRY( pxList ) // 获取链表项的 pxContainer 成员 #define listLIST_ITEM_CONTAINER( pxListItem )
2.4.3、判断相关
// 判断链表是否为空 #define listLIST_IS_EMPTY( pxList ) // 判断链表项是否和链表匹配(链表项是否在该链表中) #define listIS_CONTAINED_WITHIN( pxList, pxListItem ) // 判断链表是否被初始化 #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 中统一一些用到基本类型
/* portMacro.h */ #include #define port_CHAR char #define port_FLOAT float #define port_DOUBLE double #define port_LONG long #define port_SHORT short #define port_STACK_TYPE unsigned int #define port_BASE_TYPE long typedef port_STACK_TYPE StackType_t; typedef long BaseType_t; typedef unsigned long UBaseType_t; typedef port_STACK_TYPE* StackType_p; typedef long* BaseType_p; typedef unsigned long* UBaseType_p; #if(configUSE_16_BIT_TICKS == 1) typedef uint16_t TickType_t; #define portMAX_DELAY (TickType_t) 0xffff #else typedef uint32_t TickType_t; #define portMAX_DELAY (TickType_t) 0xffffffffUL #endif #define pdFALSE ((BaseType_t) 0) #define pdTRUE ((BaseType_t) 1) #define pdPASS (pdTRUE) #define pdFAIL (pdFALSE)
configUSE_16_BIT_TICKS 是一个宏,用于 TickType_t 类型位数,具体定义如下
/* FreeRTOSConfig.h */ // 设置 TickType_t 类型位 16 位 #define configUSE_16_BIT_TICKS 0
- 删除掉 list.c 文件中所有的 mtCOVERAGE_TEST_DELAY() 测试函数
- 删除掉 list.h 文件中所有的 PRIVILEGED_FUNCTION 宏
完成后编译工程应该不会出现错误,这样实现 RTOS 简单内核关键的双链表数据结构就完成了