FreeRTOS学习笔记——三、数据结构——列表与列表项讲解

FreeRTOS学习笔记——三、数据结构——列表与列表项讲解

  • 0 前言
  • 1 C语言链表简介
    • 1.1 单向链表
      • 1.1.1 链表的定义
      • 1.1.2 链表的操作
    • 1.2 双向链表
    • 1.3 链表与数组的对比
  • 2 FreeRTOS中链表的实现
    • 2.1 实现链表节点
      • 2.1.1 定义链表节点数据结构
      • 2.1.2 链表节点初始化
    • 2.2 实现链表根节点
      • 2.2.1 定义链表根节点数据结构
      • 2.2.2 链表根节点初始化
      • 2.2.3 将节点插入到链表的尾部
      • 2.2.4 将节点按照升序排列的插入到链表
      • 2.2.5 将节点从链表删除
      • 2.2.6 节点带参宏小函数
  • 3 链表节点插入实验
    • 3.1 实验现象

0 前言

  1. FreeRTOS中存在着大量的基础数据结构列表和列表项的操作,要想读懂FreeRTOS的源码,必须弄懂列表和列表项的操作
  2. 列表和列表项是从FreeRTOS源码的注释中的 list 和 listi tem 翻译过来的,其实就是C语言中的链表和节点
  3. 后续内容中,链表就是列表节点就是列表项

1 C语言链表简介

  1. 链表作为C 语言中一种基础的数据结构,在平时写程序的时候用的并不多,但在操作系统里面使用的非常多
  2. 链表分为单向链表和双向链表,单向链表很少用,使用最多的还是双向链表

1.1 单向链表

1.1.1 链表的定义

  1. 该链表中共有n 个节点,前一个节点都有一个箭头指向后一个节点,首尾相连,组成一个圈
  2. 节点都是一个自定义类型的数据结构,在这个数据结构里面可以有单个的数据、数组、指针数据和自定义的结构体数据类型等等信息
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第1张图片
  3. 节点结构体定义
1 struct node
2 {
3 		struct node *next; /* 指向链表的下一个节点 */
4 		char data1; /* 单个的数据 */
5 		unsigned char array[]; /* 数组 */
6 		unsigned long *prt /* 指针数据 */
7 		struct userstruct data2; /* 自定义结构体类型数据 */
8 		/* ...... */
9 }
  1. 节点内嵌在一个数据结构中
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第2张图片
1 /* 节点定义 */
2 struct node
3 {
4 		struct node *next; /* 指向链表的下一个节点 */
5 }
6
7 struct userstruct
8 {
9 		/* 在结构体中,内嵌一个节点指针,通过这个节点将数据挂接到链表 */
10 		struct node *next;
11 		/* 各种各样......,要存储的数据 */
12 }

1.1.2 链表的操作

  1. 链表最大的作用是通过节点把离散的数据链接在一起,组成一个表,这大概就是链表的字面解释了
  2. 链表常规的操作就是节点的插入和删除
  3. 为了顺利的插入,通常一条链表我们会人为地规定一个根节点,这个根节点称为生产者
  4. 通常根节点还会有一个节点计数器,用于统计整条链表的节点个数,具体见图 6-4 中的root_node
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第3张图片

1.2 双向链表

  1. 双向链表与单向链表的区别就是节点中有两个节点指针,分别指向前后两个节点,其它完全一样
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第4张图片

1.3 链表与数组的对比

  1. 在很多公司的嵌入式面试中,通常会问到链表和数组的区别。
  2. 在C 语言中,链表与数组确实很像
  3. 两者的示意图具体见图 ,这里以双向链表为例、
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第5张图片
  4. 链表是通过节点把离散的数据链接成一个表,通过对节点的插入和删除操作从而实现对数据的存取。
  5. 数组是通过开辟一段连续的内存来存储数据,这是数组和链表最大的区别
  6. 数组的每个成员对应链表的节点,成员和节点的数据类型可以是标准的C 类型或者是用户自定义的结构体。
  7. 数组有起始地址和结束地址,而链表是一个圈,没有头和尾之分,但是为了方便节点的插入和删除操作会人为的规定一个根节点。

2 FreeRTOS中链表的实现

  1. FreeRTOS 中与链表相关的操作均在list.h 和list.c 这两个文件中实现
  2. list.h 第一次使用需要在include 文件夹下面新建然后添加到工程freertos/source 这个组文件
  3. list.c 第一次使用需要在freertos 文件夹下面新建然后添加到工程freertos/source 这个组文件

2.1 实现链表节点

2.1.1 定义链表节点数据结构

  1. 链表节点的数据结构在list.h 中定义
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第6张图片
1 struct xLIST_ITEM
2 {
3 		TickType_t xItemValue; /* 辅助值,用于帮助节点做顺序排列 */ 		(1)
4 		struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */ 				(2)
5 		struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */ 			(3)
6 		void * pvOwner; /* 指向拥有该节点的内核对象,通常是TCB */				(4)
7 		void * pvContainer; /* 指向该节点所在的链表 */ 							(5)
8 };
9 typedef struct xLIST_ITEM ListItem_t; /* 节点数据类型重定义 */ 		(6)
  1. (1):一个辅助值,用于帮助节点做顺序排列。该辅助值的数据类型为TickType_t
  • 在FreeRTOS 中,凡是涉及到数据类型的地方,FreeRTOS 都会将标准的C 数据类型用typedef 重新取一个类型名

  • 这些经过重定义的数据类型放在portmacro.h(portmacro.h 第一次使用需要在include 文件夹下面新建然后添加到工程freertos/source 这个组文件)这个头文件,中除了TickType_t 外,其它数据类型重定义是本章后面内容需要使用到,这里统一贴出来,后面将不再赘述

    1 #ifndef PORTMACRO_H
    2 #define PORTMACRO_H
    3
    4 #include "stdint.h"
    5 #include "stddef.h"
    6
    7
    8 /* 数据类型重定义 */
    9 #define portCHAR char
    10 #define portFLOAT float
    11 #define portDOUBLE double
    12 #define portLONG long
    13 #define portSHORT short
    14 #define portSTACK_TYPE uint32_t
    15 #define portBASE_TYPE long
    16
    17 typedef portSTACK_TYPE StackType_t;
    18 typedef long BaseType_t;
    19 typedef unsigned long UBaseType_t;
    20
    21 #if( configUSE_16_BIT_TICKS == 1 ) (1)
    22 typedef uint16_t TickType_t;
    23 #define portMAX_DELAY ( TickType_t ) 0xffff
    24 #else
    25 typedef uint32_t TickType_t;
    26 #define portMAX_DELAY ( TickType_t ) 0xffffffffUL
    27 #endif
    28
    29 #endif /* PORTMACRO_H */
    
  • TickType_t 具体表示16 位还是32 位, 由configUSE_16_BIT_TICKS 这个宏决定

    • 当该宏定义为1 时,TickType_t 为16 位,否则为32 位。
    • 该宏在FreeRTOSConfig.h(FreeRTOSConfig.h 第一次使用需要在include 文件夹下面新建然后添加到工程freertos/source 这个组文件)中默认定义为0
      • 具体实现见代码,所以TickType_t 表示32 位
    1 #ifndef FREERTOS_CONFIG_H
    2 #define FREERTOS_CONFIG_H
    3
    4 #define configUSE_16_BIT_TICKS 0
    5
    6 #endif /* FREERTOS_CONFIG_H */
    
  1. (2):用于指向链表下一个节点
  2. (3):用于指向链表前一个节点
  3. (4):用于指向该节点的拥有者,即该节点内嵌在哪个数据结构中,属于哪个数据结构的一个成员
  4. (5):用于指向该节点所在的链表,通常指向链表的根节点
  5. (6):节点数据类型重定义

2.1.2 链表节点初始化

  1. 链表节点初始化函数在list.c 中实现
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第7张图片
1 void vListInitialiseItem( ListItem_t * const pxItem )
2 {
3 		/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
4 		pxItem->pvContainer = NULL; (1)
5 }
  • (1):链表节点ListItem_t 总共有5 个成员,但是初始化的时候只需将pvContainer 初始化为空即可,表示该节点还没有插入到任何链表。

2.2 实现链表根节点

2.2.1 定义链表根节点数据结构

  1. 链表根节点的数据结构在list.h 中定义
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第8张图片
1 typedef struct xLIST
2 {
3 		UBaseType_t uxNumberOfItems; /* 链表节点计数器 */ (1)
4 		ListItem_t * pxIndex; /* 链表节点索引指针 */ (2)
5 		MiniListItem_t xListEnd; /* 链表最后一个节点 */ (3)
6	}List_t;
  • (1):链表节点计数器,用于表示该链表下有多少个节点,根节点除外
  • (2):链表节点索引指针,用于遍历节点
  • (3):链表最后一个节点
    • 链表是首尾相连的,是一个圈,首就是尾,尾就是首,这里从字面上理解就是链表的最后一个节点,实际也就是链表的第一个节点,我们称之为生产者
  • 生产者的数据类型是一个精简的节点,也在list.h 中定义
    1 struct xMINI_LIST_ITEM
    2 {
    3 		TickType_t xItemValue; /* 辅助值,用于帮助节点做升序排列 */
    4 		struct xLIST_ITEM * pxNext; /* 指向链表下一个节点 */
    5 		struct xLIST_ITEM * pxPrevious; /* 指向链表前一个节点 */
    6 };
    7 typedef struct xMINI_LIST_ITEM MiniListItem_t; /* 精简节点数据类型重定义 */
    

2.2.2 链表根节点初始化

链表节点初始化函数在list.c 中实现,具体实现见代码,初始化好的根节点示意图具体见
FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第9张图片

1 void vListInitialise( List_t * const pxList )
2 {
3 		/* 将链表索引指针指向最后一个节点 */(1)
4 		pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );
5
6 		/* 将链表最后一个节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点 */(2)
7 		pxList->xListEnd.xItemValue = portMAX_DELAY;
8
9 		/* 将最后一个节点的pxNext 和pxPrevious 指针均指向节点自身,表示链表为空 */(3)
10 		pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd );
11 		pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );
12
13 		/* 初始化链表节点计数器的值为0,表示链表为空 */(4)
14 		pxList->uxNumberOfItems = ( UBaseType_t ) 0U;
15 }
  • (1):将链表索引指针指向最后一个节点,即第一个节点,或者第零个节点更准确,因为这个节点不会算入节点计数器的值。
  • (2):将链表最后(也可以理解为第一)一个节点的辅助排序的值设置为最大,确保该节点就是链表的最后节点(也可以理解为第一)。
  • (3):将最后一个节点(也可以理解为第一)的pxNext 和pxPrevious 指针均指向节点自身,表示链表为空。
  • (4):初始化链表节点计数器的值为0,表示链表为空。

2.2.3 将节点插入到链表的尾部

  1. 将节点插入到链表的尾部(可以理解为头部)就是将一个新的节点插入到一个空的链表
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第10张图片
1 void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
2 {
3 		ListItem_t * const pxIndex = pxList->pxIndex;
4
5 		pxNewListItem->pxNext = pxIndex;6 		pxNewListItem->pxPrevious = pxIndex->pxPrevious;7 		pxIndex->pxPrevious->pxNext = pxNewListItem;8 		pxIndex->pxPrevious = pxNewListItem;9
10 		/* 记住该节点所在的链表 */
11 		pxNewListItem->pvContainer = ( void * ) pxList;12
13 		/* 链表节点计数器++ */
14 		( pxList->uxNumberOfItems )++;15 }

2.2.4 将节点按照升序排列的插入到链表

将节点按照升序排列插入到链表,如果有两个节点的值相同,则新节点在旧节点的后面插入
FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第11张图片

1 void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
2 {
3 		ListItem_t *pxIterator;
4
5 		/* 获取节点的排序辅助值 */
6 		const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; (1)
7
8 		/* 寻找节点要插入的位置 */ (2)
9 		if ( xValueOfInsertion == portMAX_DELAY )
10 		{
11 				pxIterator = pxList->xListEnd.pxPrevious;
12 		}
13 		else
14 		{
15 				for ( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
16 						pxIterator->pxNext->xItemValue <= xValueOfInsertion;
17 						pxIterator = pxIterator->pxNext )
18 				{
19 						/* 没有事情可做,不断迭代只为了找到节点要插入的位置 */
20 				}
21 		}
22 		/* 根据升序排列,将节点插入 */ (3)
23 		pxNewListItem->pxNext = pxIterator->pxNext;24 		pxNewListItem->pxNext->pxPrevious = pxNewListItem;25 		pxNewListItem->pxPrevious = pxIterator;26 		pxIterator->pxNext = pxNewListItem;27
28 		/* 记住该节点所在的链表 */
29 		pxNewListItem->pvContainer = ( void * ) pxList;30
31 		/* 链表节点计数器++ */
32 		( pxList->uxNumberOfItems )++;33 }
  • (1):获取节点的排序辅助值
  • (2):根据节点的排序辅助值,找到节点要插入的位置,按照升序排列
  • (3):按照升序排列,将节点插入到链表
    • 假设将一个节点排序辅助值是2 的节点插入到有两个节点的链表中,这两个现有的节点的排序辅助值分别是1 和3,那么插入过程的示意图具体见图

2.2.5 将节点从链表删除

  1. 将节点从链表删除具体实现见代码清单
  2. 假设将一个有三个节点的链表中的中间节点节点删除,删除操作的过程示意图具体可见图
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第12张图片
1 UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
2 {
3 		/* 获取节点所在的链表 */
4 		List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;
5 		/* 将指定的节点从链表删除*/
6 		pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;7 		pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;8
9 		/*调整链表的节点索引指针 */
10 		if ( pxList->pxIndex == pxItemToRemove )
11 		{
12 				pxList->pxIndex = pxItemToRemove->pxPrevious;
13 		}
14
15 		/* 初始化该节点所在的链表为空,表示节点还没有插入任何链表 */
16 		pxItemToRemove->pvContainer = NULL;17
18 		/* 链表节点计数器-- */
19 		( pxList->uxNumberOfItems )--;20
21 		/* 返回链表中剩余节点的个数 */
22 		return pxList->uxNumberOfItems;
23 }

2.2.6 节点带参宏小函数

在list.h 中,还定义了各种各样的带参宏,方便对节点做一些简单的操作

1 /* 初始化节点的拥有者 */
2 #define listSET_LIST_ITEM_OWNER( pxListItem, pxOwner )\
3 				( ( pxListItem )->pvOwner = ( void * ) ( pxOwner ) )
4
5 /* 获取节点拥有者 */
6 #define listGET_LIST_ITEM_OWNER( pxListItem )\
7 				( ( pxListItem )->pvOwner )
8
9 /* 初始化节点排序辅助值 */
10 #define listSET_LIST_ITEM_VALUE( pxListItem, xValue )\
11 				( ( pxListItem )->xItemValue = ( xValue ) )
12
13 /* 获取节点排序辅助值 */
14 #define listGET_LIST_ITEM_VALUE( pxListItem )\
15 				( ( pxListItem )->xItemValue )
16
17 /* 获取链表根节点的节点计数器的值 */
18 #define listGET_ITEM_VALUE_OF_HEAD_ENTRY( pxList )\
19 				( ( ( pxList )->xListEnd ).pxNext->xItemValue )
20
21 /* 获取链表的入口节点 */
22 #define listGET_HEAD_ENTRY( pxList )\
23 				( ( ( pxList )->xListEnd ).pxNext )
24
25 /* 获取节点的下一个节点 */
26 #define listGET_NEXT( pxListItem )\
27 				( ( pxListItem )->pxNext )
28
29 /* 获取链表的最后一个节点 */
30 #define listGET_END_MARKER( pxList )\
31 				( ( ListItem_t const * ) ( &( ( pxList )->xListEnd ) ) )
32
33 /* 判断链表是否为空 */
34 #define listLIST_IS_EMPTY( pxList )\
35 				( ( BaseType_t ) ( ( pxList )->uxNumberOfItems == ( UBaseType_t ) 0 ) )
36
37 /* 获取链表的节点数 */
38 #define listCURRENT_LIST_LENGTH( pxList )\
39 				( ( pxList )->uxNumberOfItems )
40
41 /* 获取链表第一个节点的OWNER,即TCB */
42 #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) 
43 { 
44 		List_t * const pxConstList = ( pxList ); 45 /* 节点索引指向链表第一个节点 */ \
46 		( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; 47 /* 这个操作有啥用? */ \
48 		if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) ) \
49 		{ 
50 				( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext; 
51 		} 
52 /* 获取节点的OWNER,即TCB */ \
53 		( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; 54 }

3 链表节点插入实验

  1. 新建一个根节点(也可以理解为链表)和三个普通节点,然后将这三个普通节点按照节点的排序辅助值做升序排列插入到链表中
1 /*
2 *************************************************************************
3 * 包含的头文件
4 *************************************************************************
5 */
6 #include "list.h"
7
8 /*
9 *************************************************************************
10 * 全局变量
11 *************************************************************************
12 */
13
14 /* 定义链表根节点 */
15 struct xLIST List_Test; (1)
16
17 /* 定义节点 */
18 struct xLIST_ITEM List_Item1; (2)
19 struct xLIST_ITEM List_Item2;
20 struct xLIST_ITEM List_Item3;
21
22
23
24 /*
25 ************************************************************************
26 * main 函数
27 ************************************************************************
28 */
29 /*
30 int main(void)
31 {
32
33 		/* 链表根节点初始化 */
34 		vListInitialise( &List_Test ); (3)
35
36 		/* 节点1 初始化 */
37 		vListInitialiseItem( &List_Item1 ); (4)
38 		List_Item1.xItemValue = 1;
39
40 		/* 节点2 初始化 */
41 		vListInitialiseItem( &List_Item2 );
42 		List_Item2.xItemValue = 2;
43
44 		/* 节点3 初始化 */
45 		vListInitialiseItem( &List_Item3 );
46 		List_Item3.xItemValue = 3;
47
48 		/* 将节点插入链表,按照升序排列 */ (5)
49 		vListInsert( &List_Test, &List_Item2 );
50 		vListInsert( &List_Test, &List_Item1 );
51 		vListInsert( &List_Test, &List_Item3 );
52
53 		for (;;)
54 		{
55 				/* 啥事不干 */
56 		}
57 }
  • (1):定义链表根节点,有根了,节点才能在此基础上生长

  • (2):定义3 个普通节点

  • (3):链表根节点初始化,初始化完毕之后,根节点示意图见图 6-13
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第13张图片

  • (4):节点初始化,初始化完毕之后节点示意图见图,其中xItemValue 等于你的初始化值
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第14张图片

  • (5):将节点按照他们的排序辅助值做升序排列插入到链表,插入完成后链表的示意图见图。
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第15张图片

3.1 实验现象

  1. 实验现象如上图所示,但这好像是我得出的结论,是否有准确的数据支撑?有的,我们可以通过软件仿真来证实。
  • 将程序编译好之后,点击调试按钮,然后全速运行
  • 再然后把List_Test、List_Item1 、List_Item2 和 List_Item3 这四个全局变量添加到观察窗口
  • 然后查看这几个数据结构中pxNext 和pxPrevious 的值即可证实图是正确的
  • 具体的仿真数据见下图
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第16张图片
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第17张图片
    FreeRTOS学习笔记——三、数据结构——列表与列表项讲解_第18张图片

你可能感兴趣的:(FreeRTOS学习笔记,学习,笔记,数据结构)