FreeRTOS解析:List
受博客限制,如果您想获得更好的阅读体验,请前往https://github.com/Nrusher/FreeRTOS-Book或者https://gitee.com/nrush/FreeRTOS-Book下载PDF版本阅读,如果您觉得本文不错也可前往star,以示对作者的鼓励。如发现问题欢迎交流。PDF版效果如下
相关博客:
FreeRTOS解析:TCB_t结构体及重要变量说明(Task-1)
FreeRTOS解析:任务的创建(TASK-2)
链表这一数据结构是FreeRTOS的核心数据结构,有关任务调度、延时、阻塞、事件等操作都是通过对链表进行操作进而实现的。本章将详细分析源码文件list.c,list.h的内容,为后续的任务队列等的实现奠定基础。
链表及链表项结构体定义
FreeRTOS使用的链表结构是环形的双向链表,其链表项ListItem_t的定义如下
struct xLIST_ITEM
{
// 第一个和最后一个成员值当configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES被使能的时候会被设定为一个固定值,用来检验一个列表项数据是否完整
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE
// 用来给链表排序的值,在不同的应用场景下有不同的含义。
configLIST_VOLATILE TickType_t xItemValue;
// 指向前一个链表项
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
// 指向后一个链表项
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
// 类似侵入式链表,指向包含该链表项的对象的地址
void * pvOwner;
// 指向拥有此链表项的链表
struct xLIST * configLIST_VOLATILE pxContainer;
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE
};
typedef struct xLIST_ITEM ListItem_t;
这里如果使用configLIST_VOLATILE,其会被替换为volatile关键字,volatile关键字是让编译器不对该变量进行优化,所谓的优化可以理解为其在生成汇编时,若多次读取该变量时其可能会将变量值放入寄存器中以加快读取速度,而不是真正的读取,这使得当某个变量会快速变化时,优化后"读取"的值并不是变量真实的值。当使用volatile关键字时,其会强迫编译器每次使用该变量时都真正的对它进行一次读取。
#define configLIST_VOLATILE volatile
在链表项的结构体体构造中值得注意的是pvOwner和pxContainer这两个成员变量。pvOwner提供了一种可以快速访问由此链表项代表的对象的方法,pxContainer则提供了一种快速访问其所属链表的方法。这种处理方式大大提高了链表在任务调度等应用中的处理速度,提高系统效率。
Linux内核中也有侵入式的链表的设计,但其与FreeRTOS有不同之处,如果您对此感兴趣可以阅读相关问题: FreeRTOS相关:linux中的侵入式链表设计
由于在链表的尾部(头部),其链表项一般不需要有具体的内容,只需要提供下一个或前一个链表项的地址即可,因此为了节省RAM,FreeRTOS还定义了一个Mini链表项MiniListItem_t,其具体定义如下
struct xMINI_LIST_ITEM
{
// 校验值
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;
链表List_t的定义
typedef struct xLIST
{
// 校验值
listFIRST_LIST_INTEGRITY_CHECK_VALUE
// 记录该链表里有多少成员
volatile UBaseType_t uxNumberOfItems;
// 用来遍历链表的指针,listGET_OWNER_OF_NEXT_ENTRY()会使它指向下一个链表项
ListItem_t * configLIST_VOLATILE pxIndex;
// 链表尾部,为节省内存使用Mini链表项
MiniListItem_t xListEnd;
// 校验值
listSECOND_LIST_INTEGRITY_CHECK_VALUE
} List_t;
链表中的链表项间的连接如下图所示
FreeRTOS中链表的一般结构如下所示结构
链表的相关操作
初始化
链表项的初始化vListInitialiseItem()函数如下
void vListInitialiseItem( ListItem_t * const pxItem )
{
// 使该链表项不被任何链表拥有
pxItem->pxContainer = NULL;
// 写入校验值
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
链表项的初始化十分简单只是将pxContainer置为NULL,设置一下校验值。
链表的初始化函数vListInitialise()如下所示
void vListInitialise( List_t * const pxList )
{
// step1 把当前指针指向链表尾部
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
// step2 设置排序值为最大,也就是保证xListEnd会被放在链表的尾部
pxList->xListEnd.xItemValue = portMAX_DELAY;
// step3 xListEnd的下一项和前一项都设置为尾部,表示链表为空。
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
// step4 链表项数目为0
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
// step5 写入校验值,用于后续检验,为了保证链表结构体是正确的,没有被覆写
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList );
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
插入
链表的插入操作有两个版本的函数,分别是vListInsertEnd()和vListInsert()。其中vListInsertEnd()可以理解为是一种无序的插入方法,也就是直接在链表pxIndex指向的前一位置插入一个链表项,而vListInsert()是有序的插入方法,链表项将会按xItemValue的值进行升序插入到链表中去。 先看vListInsertEnd()插入的实现
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
// step1 校验链表和链表项
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
// step2 将链表项嵌入到pxIndex指向的链表项前
pxNewListItem->pxNext = pxIndex;
pxNewListItem->pxPrevious = pxIndex->pxPrevious;
// 调试测试用的函数,具体功能不知道,对代码逻辑理解无影响。
mtCOVERAGE_TEST_DELAY();
pxIndex->pxPrevious->pxNext = pxNewListItem;
pxIndex->pxPrevious = pxNewListItem;
// step3 记录链表项属于该链表
pxNewListItem->pxContainer = pxList;
// step5 记录链表中的链表项数目
( pxList->uxNumberOfItems )++;
}
vListInsert()的实现比vListInsertEnd()稍微复杂一些,其先要查找插入位置再进行插入操作
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
// step1 校验链表和链表项
listTEST_LIST_INTEGRITY( pxList );
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
// step2 寻找插入位置
if( xValueOfInsertion == portMAX_DELAY )
{
// 如果链表项的排序数最大,直接在尾部插入,这里相当于做了一个小小的优化。
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
// 升序寻找插入位置
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext )
{
// 无操作
}
}
// step3 进行插入操作
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;
// step4 记录链表项属于该链表
pxNewListItem->pxContainer = pxList;
// step5 记录链表中的链表项数目
( pxList->uxNumberOfItems )++;
}
移除
FreeRTOS的链表项移除由函数uxListRemove()实现,在链表移除的过程中需要注意的一点是当链表当前的pxIndex指向该待移除的链表项时,需要更改pxIndex以保证其指向一个合法的值,其具体实现过程如下
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
List_t * const pxList = pxItemToRemove->pxContainer;
// step1 调整前后项指针
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;
mtCOVERAGE_TEST_DELAY();
// step2 保证当前的链表索引指向有效项
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
// step3 移除的链表项不再被链表拥有
pxItemToRemove->pxContainer = NULL;
// step4 减少链表项数目
( pxList->uxNumberOfItems )--;
// step5 返回链表的链表项数目
return pxList->uxNumberOfItems;
}
遍历
FreeRTOS中链表的遍历操作由宏listGET_OWNER_OF_NEXT_ENTRY()提供,宏的具体实现如下
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList )\
{\
List_t * const pxConstList = ( pxList );\
// 寻找下一个链表项
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;\
// 判断是不是不是到了xListEnd,如果是,跳过这一项,因为没有意义
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )\
{\
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;\
}\
// 返回拥有该链表项的对象的地址
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner;\
}
可以看到,这里定义的变量名是pxTCB(Task Control Block 任务控制块),这个遍历操作主要是给任务调度管理用的。通过多次调用这一宏可以对链表进行遍历操作。
其它操作
在list.h中FreeRTOS还以宏的方式实现了其它的一些基本操作,例如listLIST_IS_INITIALISED()获取链表是否被初始化等操作。这些宏的实现都较为简单,不再详细列写。
FreeRTOS的list.h(其它文件中也有)中定义了大量的宏定义函数。单单从形式看宏定义的函数和普通函数并无太大的区别,但事实上两者还是有很大不同。如果您对此感兴趣可以阅读相关问题:FreeRTOS相关:宏定义函数与普通函数的区别