目录
在上篇文章,我们完成了 FreeRTOS 的移植。在 FreeRTOS 中,所有功能被划分为任务。而任务则需要挂载链表上面。在本章,我们将掌握链表的各种节点类型。
在创建任务之前,我们需要先了解FreeRTOS的运转机制。FreeRTOS是一个多任务系统,由操作系统来管理执行每个任务。这些任务全都挂载到一个双向循环链表上。同时链表的每个节点都能挂载多个任务。
节点
节点定义
在 FreeRTOS 中,关于链表的定义在 list.h 中实现。下面代码为链表节点的结构体定义。
PS:在 list.h 中,我的宏 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为0,当其为0时,listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE 和 listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE 这两个宏不代表实际值,为提高可读性所以我在后续出现这两个宏的地方都将他们删去了。
struct xLIST_ITEM
{
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
void * pvOwner;
void * configLIST_VOLATILE pvContainer;
};
typedef struct xLIST_ITEM ListItem_t;
configLIST_VOLATILE TickType_t xItemValue:这里的 configLIST_VOLATILE 就是C语言中的VOLATILE类型,在 FreeRTOS 中,所有的数据类型都被 typedef 进行了重新定义。同理,TickType_tuint32_t,所有的数据类型都在 portmacro.h 中进行了定义。再说回 xItemValue ,这个参数的作用就是做节点的序号。
struct xLIST_ITEM * configLIST_VOLATILE pxNext:指向下一个节点。
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious:指向上一个节点。
void * pvOwner:指向该节点的数据结构。指向包含列表项的对象(通常是 TCB)的指针。 因此,包含列表项的对象与列表项本身之间存在双向链接。
在FreeRTOS中,TCB(Task Control Block)是任务控制块的缩写。它是一个数据结构,用于存储和管理任务的所有相关信息。每个任务都有一个对应的TCB,FreeRTOS通过TCB来管理和调度任务。之后会在其他文章中进行解释。
void * configLIST_VOLATILE pvContainer:指向该节点所在的链表,通常指向链表的根节点。
typedef struct xLIST_ITEM ListItem_t:将该结构体的数据类型重新定义。
节点实现
在 list.c 中,通过定义函数来实现节点的创建。下面为节点初始化函数。
void vListInitialiseItem( ListItem_t * const pxItem )
{
pxItem->pvContainer = NULL;
}
由于该节点仅完成了初始化,未插入到链表中,所以初始化链表为空。实际情况如下图。
根节点
根节点定义
上文提到,pvContainer 通常指向链表的根节点。下面为根节点的结构体定义。
typedef struct xLIST
{
volatile UBaseType_t uxNumberOfItems;
ListItem_t * configLIST_VOLATILE pxIndex;
MiniListItem_t xListEnd;
} List_t;
uxNumberOfItems:节点计数器。表示除根节点外,链表的节点数量。
pxIndex:链表节点索引指针,用于遍历节点。
xListEnd: 链表最后一个节点。由于 FreeRTOS 是双向循环链表,所以最后一个节点也就是第一个节点。
精简节点定义
精简节点,就是根节点中 xListEnd 的数据类型,个人认为是对根节点的节点类型补充。
struct xMINI_LIST_ITEM
{
configLIST_VOLATILE TickType_t xItemValue;
struct xLIST_ITEM * configLIST_VOLATILE pxNext;
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious;
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
从定义上看,精简节点就是由指向上一个和下一个节点的指针和序号组成,结构体成员都是构成节点的最基本要素。
根节点实现
void vListInitialise( List_t * const pxList )
{
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.xItemValue = portMAX_DELAY;
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
}
这个根节点初始化函数做了四件事:
1. 将索引指针指向最后一个节点。
2. 将序号值设为最大(即portMAX_DELAY,其在 portmacro.h 中进行了定义),以确保根节点是链表最后一个节点。
3. 将该节点的前一个和后一个节点都指向自己。
4. 将计数器的值初始为0。
如下图所示,其为一个根节点。根节点除了包含承担节点作用的精简节点外,还有索引指针和节点计数器组成。但在初始化中,pxNext 和 pxPrevious 都指向自身。
在了解上述节点的定义和初始化之后,将根节点和节点组合在一起形成的链表如下图所示。在下一篇文章中,我们来分析如何将节点添加到链表中。