声明:本文为博主的学习篇章,欢迎大家指错,共同学习
在FreeRTOS中最最最主要的部分就是任务,而任务创建的过程中会遇到有关于列表和列表项的问题,所有我首先要解决的就是列表和列表项的问题。
FreeRTOS中使用指针指向列表项。一个列表可能包含多个列表项,并且每个列表项都有一个指针指向列表。具体是什么样子如下图:
在FreeRTOS中的列表项有两张形式:xLIST_ITEM和xMINI_LIST_ITEM。xMINI_LIST_ITEM是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; /* 指向上一个列表项的指针 */
void * pvOwner; /* 指向一个任务TCB */
struct xLIST * configLIST_VOLATILE pxContainer; /* 指向列表 */
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE /* 用于检测列表项数据是否完整 */
};
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; /* 指向下一个列表项的指针 */
};
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE是定义在projdefs.h中的宏。在文件projdefs.h中还有一个宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES,如果将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则使能列表项数据完整性检查,则宏listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE会被两个已知的数值代替。
xItemValue是列表项的值,通常是一个被追踪任务的优先级或一个调度事件的计数值。如果任务因为等待从队列中取数据进入阻塞态,则任务事件列表项的xItemValue值会保存任务优先级相关的信息,状态事件列表项的xItemValue值会保存阻塞事件相关的信息。xItemValue被configLIST_VOLATILE修饰,configLIST_VOLATILE是一个定义在list.h的宏,通常情况下configLIST_VOLATILE被映射为volatile关键字。
pxNext和pxPrevious都是指向列表项的指针,用于指向下一个或者上一个列表项,由于这两个指针的加入,列表项也就类似于双向链表的结构。
pvOwner是一个指向任务TCB的指针。
pxContainer是一个指向包含该列表项的列表指针。
那既然有了xLIST_ITEM,为什么还要有xMINI_LIST_ITEM呢?这是因为列表结构体需要一个列表项成员,但又不需要列表项中的所有字段,所以才有了迷你版列表项。列表List_t又有哪些参数呢?
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE /* 用于检测列表数据是否完整 */
volatile UBaseType_t uxNumberOfItems; /* 列表下包含的列表项的数量 */
ListItem_t * configLIST_VOLATILE pxIndex; /* 用于遍历列表 */
MiniListItem_t xListEnd; /* 用于标记列表项结束 */
listSECOND_LIST_INTEGRITY_CHECK_VALUE /* 用于检测列表数据是否完整 */
} List_t;
listFIRST_LIST_INTEGRITY_CHECK_VALUE和listSECOND_LIST_INTEGRITY_CHECK_VALUE是两个定义在list.h的宏,跟列表项的那两个宏的功能一样。在projdefs.h中,如果将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES设置为1,则使能列表项数据完整性检查,则宏listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE和listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE会被两个已知的数值代替。
uxNumberOfItems用于记录列表下包含列表项的数量。
pxIndex同样也被configLIST_VOLATILE修饰,用于遍历列表下包含的列表项,初始化后这个指针指向xListEnd。通过在list.h中定于的宏listGET_OWNER_OF_NEXT_ENTRY()来获取列表中的下一个列表项。
xListEnd用于标记列表结束。MiniListItem_t就是xMINI_LIST_ITEM,它也是一个列表项,它总是位于列表下包含的列表项中的最后一项后面。xMINI_LIST_ITEM作用就体现出来了,它不需要指向任务TCB的pvOwner,也不需要指向列表的pxContainer,仅仅用来标志该列表的结束位置。xListEnd.xItemValue会被初始化为一个常数,其值与硬件架构相关,为0xFFFF(16位架构)或者0xFFFFFFFF(32位架构)。
知道了列表,知道了列表项,那就需要知道如何操作它们。FreeRTOS提供了一些API函数用于列表和列表项的操作。这些API都位于list.c文件中。
初始化列表
为了方便查看代码,我把代码中的注释都删除了。
void vListInitialise( List_t * const pxList )
{
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); /* 1 */
pxList->xListEnd.xItemValue = portMAX_DELAY; /* 2 */
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); /* 3 */
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd ); /* 4 */
pxList->uxNumberOfItems = ( UBaseType_t ) 0U; /* 5 */
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ); /* 6 */
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList );
}
初始化列表的API如上,具体可以分为六步:
1、初始化列表的pxIndex,指向xListEnd。
2、初始化列表的xListEnd.xItemValue为portMAX_DELAY。(不同的机器portMAX_DELAY值不同,32位的机器值为0xFFFFFFFF,十六位的机器值为0xFFFF)。
3、初始化列表的xListEnd.pxNext,指向xListEnd。
4、初始化列表的xListEnd.pxPrevious,指向xListEnd。
5、初始化列表的uxNumberOfItems为0,表示列表下包含的列表项的个数为0。
6、检测列表数据是否完整。(具体的流程我也不知道,有知道的小伙伴麻烦提示我一下)
初始化后列表的模样大概如下图:
初始化列表项
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 );
}
初始化列表项相对简单,只需要把列表项中指向的列表指针清空即可。
后面两行也是用于检测列表项的数据是否完整。
将列表项插入列表的尾部
void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex;
listTEST_LIST_INTEGRITY( pxList ); /* 1 */
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
pxNewListItem->pxNext = pxIndex; /* 2 */
pxNewListItem->pxPrevious = pxIndex->pxPrevious; /* 3 */
mtCOVERAGE_TEST_DELAY();
pxIndex->pxPrevious->pxNext = pxNewListItem; /* 4 */
pxIndex->pxPrevious = pxNewListItem; /* 5 */
pxNewListItem->pxContainer = pxList; /* 6 */
( pxList->uxNumberOfItems )++; /* 7 */
}
具体的步骤与双向链表的尾端插入操作类似,可分为七步:
黄色部分为操作部分。
将列表项插入列表中
void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;
listTEST_LIST_INTEGRITY( pxList ); /* 1 */
listTEST_LIST_ITEM_INTEGRITY( pxNewListItem );
if( xValueOfInsertion == portMAX_DELAY ) /* 2 */
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
/* 3 */
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); pxIterator->pxNext->xItemValue <= xValueOfInsertion; pxIterator = pxIterator->pxNext ) /*lint !e826 !e740 !e9087 The mini list structure is used as the list end to save RAM. This is checked and valid. *//*lint !e440 The iterator moves to a different value, not xValueOfInsertion. */
{
}
}
pxNewListItem->pxNext = pxIterator->pxNext; /* 4 */
pxNewListItem->pxNext->pxPrevious = pxNewListItem; /* 5 */
pxNewListItem->pxPrevious = pxIterator; /* 6 */
pxIterator->pxNext = pxNewListItem; /* 7 */
pxNewListItem->pxContainer = pxList; /* 8 */
( pxList->uxNumberOfItems )++; /* 9 */
}
从代码中可以看出,这不是一个随机插入,这是一个排序插入,并且是根据列表项中的xItemValue值进行升序排序,即越大的值排在越后面。这个排序操作主要分为九步:
1、检测列表和列表项的数据完整性。
2、对于需插入的列表项中的xItemValue进行特殊判断,如果其值为portMAX_DELAY,直接将此列表项插入到列表的尾部。
3、如果列表项中的xItemValue不为portMAX_DELAY,则需要找到插入的位置。
4-8、这五步就是将列表项插入列表的操作,与上个列表项插入列表的尾部的操作类似,这里就不费口舌了。
9、列表下包含的列表数加一。
将列表项从列表中删除
这个API的作用是将一个列表项从它所属的列表中剥离出来。
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
List_t * const pxList = pxItemToRemove->pxContainer;
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious; /* 1 */
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext; /* 2 */
mtCOVERAGE_TEST_DELAY();
if( pxList->pxIndex == pxItemToRemove ) /* 3 */
{
pxList->pxIndex = pxItemToRemove->pxPrevious;
}
else
{
mtCOVERAGE_TEST_MARKER();
}
pxItemToRemove->pxContainer = NULL; /* 4 */
( pxList->uxNumberOfItems )--; /* 5 */
return pxList->uxNumberOfItems;
}
其实这里面有一点我不是很明白,为什么这里面不对列表项指向的列表进行判断,如果这个列表项的列表指针指向的是空,这里面不就会报错吗?还是说进来这个API之前会对参数进行判断?希望知道的小伙伴可以提示我一下。假设传进来的列表项是包含在某个列表(这个列表下面通常为列表A)下的,该函数分为五个步骤:
1、将参数列表项指向的下一个列表项指向的上一个列表项指向参数列表项指向的上一个列表项(有点拗口)。
用新增的线代替原来黑色该删除的线。
2、将参数列表项指向的上一个列表项指向的下一个列表项指向参数列表项指向的下一个列表项(与上面的类似)。
3、如果列表A的pxIndex指向参数列表项,就修改pxIndex的指向,改为指向参数列表项的上一个列表项。
4、将参数列表项的列表指针清空。
5、将列表A的uxNumberOfItems值减一。
返回值为列表A的uxNumberOfItems值。
至此,有关列表和列表项的部分已经分析完了。