列表
列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS 中的任务。与列表相关的全部东西都在文件 list.c 和 list.h 中。在 list.h 中定义了一个叫 List_t 的 结构体,如下:
typedef struct xLIST {
listFIRST_LIST_INTEGRITY_CHECK_VALUE (1)
configLIST_VOLATILE UBaseType_t uxNumberOfItems; (2)
ListItem_t * configLIST_VOLATILE pxIndex; (3)
MiniListItem_t xListEnd; (4)
listSECOND_LIST_INTEGRITY_CHECK_VALUE (5)
} List_t;
(1) 和 (5) 、这两个都是用来检查列表完整性的,需要将宏 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为 1,开启以后会向这两个地方分别 添加一个变量 xListIntegrityValue1 和 xListIntegrityValue2,在初始化列表的时候会这两个变量中
写入一个特殊的值,默认不开启这个功能。
(2)、uxNumberOfItems 用来记录列表中列表项的数量。
(3)、pxIndex 用来记录当前列表项索引号,用于遍历列表。
(4)、列表中最后一个列表项,用来表示列表结束,此变量类型为
MiniListItem_t
,这是一个 迷你列表项,
列表项
两种列表项:列表项
和迷你列表项
列表项
struct xLIST_ITEM {
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE (1)
configLIST_VOLATILE TickType_t xItemValue; (2)
struct xLIST_ITEM * configLIST_VOLATILE pxNext; (3)
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; (4)
void * pvOwner; (5)
void * configLIST_VOLATILE pvContainer; (6)
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE (7)
};
typedef struct xLIST_ITEM ListItem_t;
(1)和(7)、用法和列表一样,用来检查列表项完整性的。以后我们在学习列表项的时候不讨 论这个功能!
(2)、xItemValue 为列表项值。
(3)、pxNext 指向下一个列表项。
(4)、pxPrevious 指向前一个列表项,和 pxNext 配合起来实现类似双向链表的功能。
(5)、pvOwner 记录此链表项归谁拥有,通常是任务控制块。
(6)、pvContainer 用来记录此列表项归哪个列表。
迷你列表项
struct xMINI_LIST_ITEM {
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE (1)
configLIST_VOLATILE TickType_t xItemValue; (2)
struct xLIST_ITEM * configLIST_VOLATILE pxNext; (3)
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; (4)
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;
列表初始化
新创建或者定义的列表需要对其做初始化处理,列表的初始化其实就是初始化列表结构体 List_t 中的各个成员变量
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 ); (7)
}
(1)、xListEnd 用来表示列表的末尾,而 pxIndex 表示列表项的索引号,此时列表只有一个 列表项,那就是 xListEnd,所以 pxIndex 指向 xListEnd。
(2)、xListEnd 的列表项值初始化为 portMAX_DELAY, portMAX_DELAY 是个宏,在文件 portmacro.h 中有定义。根据所使用的 MCU 的不同,portMAX_DELAY 值也不相同,可以为 0xffff 或者 0xffffffffUL
(3)、初始化列表项 xListEnd 的 pxNext 变量,因为此时列表只有一个列表项 xListEnd,此 pxNext 只能指向自身。
(4)、同(3)一样,初始化 xListEnd 的 pxPrevious 变量,指向 xListEnd 自身。 (5)、由于此时没有其他的列表项,因此 uxNumberOfItems 为 0,注意,这里没有算 xListEnd。
(6) 和 (7) 、初始化列表项 中用于完整性检查字段,只有宏 configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 为 1 的时候才有效。
列表项初始化
同列表一样,列表项在使用的时候也需要初始化
void vListInitialiseItem( ListItem_t * const pxItem ) {
// 初始化 pvContainer 为 NUL
pxItem->pvContainer = NULL; L
// 初始化用于完整性检查的变量,如果开启了这个功能的话。
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}
列表项插入
void vListInsert( List_t * const pxList,
ListItem_t * const pxNewListItem );
列表项末尾插入
void vListInsertEnd( List_t * const pxList,
ListItem_t * const pxNewListItem );
列表项的删除
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );
列表的遍历
介绍列表结构体的时候说过列表 List_t 中的成员变量 pxIndex 是用来遍历列表的,FreeRTOS 提供了一个函数来完成列表的遍历,这个函数是 listGET_OWNER_OF_NEXT_ENTRY()。每调 用一次这个函数列表的 pxIndex 变量就会指向下一个列表项,并且返回这个列表项的 pxOwner 变量值。这个函数本质上是一个宏,这个宏在文件 list.h 中如下定义:
#define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \ (1)
{ \
List_t * const pxConstList = ( pxList ); \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ (2)
if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )\ (3)
{ \
( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; \ (4)
} \
( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; \ (5)
}
(1)、pxTCB 用来保存 pxIndex 所指向的列表项的 pvOwner 变量值,也就是这个列表项属于 谁的?通常是一个任务的任务控制块。pxList 表示要遍历的列表。
(2)、列表的 pxIndex 变量指向下一个列表项。
(3)、如果 pxIndex 指向了列表的 xListEnd 成员变量,表示到了列表末尾。 (4)、如果到了列表末尾的话就跳过 xListEnd,pxIndex 再一次重新指向处于列表头的列表 项,这样就完成了一次对列表的遍历。
(5)、将 pxIndex 所指向的新列表项的 pvOwner 赋值给 pxTCB。 此函数用于从多个同优先级的就绪任务中查找下一个要运行的任务。
列表任务
void list_task(void *pvParameters) {
// 第一步:初始化列表和列表项
vListInitialise(&TestList);
vListInitialiseItem(&ListItem1);
vListInitialiseItem(&ListItem2);
vListInitialiseItem(&ListItem3);
ListItem1.xItemValue=40; //ListItem1 列表项值为 40
ListItem2.xItemValue=60; //ListItem2 列表项值为 60
ListItem3.xItemValue=50; //ListItem3 列表项值为 50
// 第二步:打印列表和其他列表项的地址
printf("/**********列表和列表项地址***********/\r\n");
printf("项目 地址 \r\n");
printf("TestList %#x \r\n",(int)&TestList);
printf("TestList->pxIndex %#x \r\n",(int)TestList.pxIndex);
printf("TestList->xListEnd %#x \r\n",(int)(&TestList.xListEnd));
printf("ListItem1 %#x \r\n",(int)&ListItem1);
printf("ListItem2 %#x \r\n",(int)&ListItem2);
printf("ListItem3 %#x \r\n",(int)&ListItem3);
printf("/****************结束*****************/\r\n");
printf("按下 KEY_UP 键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=WKUP_PRES) delay_ms(10); //等待 KEY_UP 键按下
// 第三步:向列表 TestList 添加列表项 ListItem1,并通过串口打印所有
// 列表项中成员变量 pxNext 和 pxPrevious 的值,通过这两个值观察列表项在列表中的连接情况。
vListInsert(&TestList, &ListItem1); //插入列表项 ListItem1
printf("/*********添加列表项 ListItem1**********/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("/**********前后向连接分割线***********/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("/*****************结束****************/\r\n");
printf("按下 KEY_UP 键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=WKUP_PRES) delay_ms(10); //等待 KEY_UP 键按下
// 第四步:向列表 TestList 添加列表项 ListItem2,并通过串口打印所有
// 列表项中成员变量 pxNext 和 pxPrevious 的值,通过这两个值观察列表项在列表中的连接情况。
vListInsert(&TestList,&ListItem2); //插入列表项 ListItem2
printf("/*********添加列表项 ListItem2**********/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("/***********前后向连接分割线**********/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("/****************结束*****************/\r\n");
printf("按下 KEY_UP 键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=WKUP_PRES) delay_ms(10); //等待 KEY_UP 键按下
// 第五步:向列表 TestList 添加列表项 ListItem3,并通过串口打印所有
// 列表项中成员变量 pxNext 和 pxPrevious 的值,通过这两个值观察列表项在列表中的连接情况。
vListInsert(&TestList,&ListItem3); //插入列表项 ListItem3
printf("/*********添加列表项 ListItem3**********/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("/**********前后向连接分割线***********/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("/*****************结束****************/\r\n");
printf("按下 KEY_UP 键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=WKUP_PRES) delay_ms(10); //等待 KEY_UP 键按下
// 第六步:删除 ListItem2,并通过串口打印所有列表项中成员变量 pxNext 和 pxPrevious 的值,通过这两个值观察列表项在列表中的连接情况。
uxListRemove(&ListItem2); //删除 ListItem2
printf("/**********删除列表项 ListItem2*********/\r\n");
printf("项目 地址 \r\n");
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("/***********前后向连接分割线**********/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("/****************结束*****************/\r\n");
printf("按下 KEY_UP 键继续!\r\n\r\n\r\n");
while(KEY_Scan(0)!=WKUP_PRES) delay_ms(10); //等待 KEY_UP 键按下
// 第七步:删除 ListItem2,并通过串口打印所有列表项中成员变量 pxNext 和 pxPrevious 的值,通过这两个值观察列表项在列表中的连接情况。
TestList.pxIndex=TestList.pxIndex->pxNext;//pxIndex 向后移一项,
// 这样 pxIndex 就会指向 ListItem1。
vListInsertEnd(&TestList,&ListItem2); //列表末尾添加列表项 ListItem2
printf("/******在末尾添加列表项 ListItem2*******/\r\n");
printf("项目 地址 \r\n");
printf("TestList->pxIndex %#x \r\n",(int)TestList.pxIndex);
printf("TestList->xListEnd->pxNext %#x \r\n",(int)(TestList.xListEnd.pxNext));
printf("ListItem2->pxNext %#x \r\n",(int)(ListItem2.pxNext));
printf("ListItem1->pxNext %#x \r\n",(int)(ListItem1.pxNext));
printf("ListItem3->pxNext %#x \r\n",(int)(ListItem3.pxNext));
printf("/***********前后向连接分割线**********/\r\n");
printf("TestList->xListEnd->pxPrevious %#x \r\n",(int)(TestList.xListEnd.pxPrevious));
printf("ListItem2->pxPrevious %#x \r\n",(int)(ListItem2.pxPrevious));
printf("ListItem1->pxPrevious %#x \r\n",(int)(ListItem1.pxPrevious));
printf("ListItem3->pxPrevious %#x \r\n",(int)(ListItem3.pxPrevious));
printf("/****************结束*****************/\r\n\r\n\r\n");
while(1) {
LED1=!LED1;
vTaskDelay(1000); //延时 1s,也就是 1000 个时钟节拍
}
}