Skip to content

列表

列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS 中的任务。与列表相关的全部东西都在文件 list.c 和 list.h 中。在 list.h 中定义了一个叫 List_t 的 结构体,如下:

c
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,这是一个 迷你列表项,

列表项

两种列表项:列表项迷你列表项

列表项

c
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 用来记录此列表项归哪个列表。

迷你列表项

c
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 中的各个成员变量

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 ); (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 的时候才有效。

列表项初始化

同列表一样,列表项在使用的时候也需要初始化

c
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 );
}

列表项插入

c
void vListInsert( List_t * const pxList,
ListItem_t * const pxNewListItem );

列表项末尾插入

c
void vListInsertEnd( List_t * const pxList,
ListItem_t * const pxNewListItem );

列表项的删除

c
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );

列表的遍历

介绍列表结构体的时候说过列表 List_t 中的成员变量 pxIndex 是用来遍历列表的,FreeRTOS 提供了一个函数来完成列表的遍历,这个函数是 listGET_OWNER_OF_NEXT_ENTRY()。每调 用一次这个函数列表的 pxIndex 变量就会指向下一个列表项,并且返回这个列表项的 pxOwner 变量值。这个函数本质上是一个宏,这个宏在文件 list.h 中如下定义:

c
#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。 此函数用于从多个同优先级的就绪任务中查找下一个要运行的任务。

列表任务

c
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 个时钟节拍
    }
}

Released under the GPL License.