FreeRTOS---进阶知识1---列表的创建
在 FreeRTOS 中,列表(List) 是一种核心数据结构,用于高效管理任务、事件和其他内核对象。它本质上是一个 双向链表(Doubly Linked List)。
1. FreeRTOS 列表的特点
(1) 双向链表结构
每个列表(
List_t
)由多个 列表项(ListItem_t
) 组成,列表项之间通过 前驱(pxPrevious
) 和 后继(pxNext
) 指针连接。包含一个 尾节点(
xListEnd
) 作为哨兵节点(Sentinel Node),使遍历和插入操作更高效。
(2) 线程安全
FreeRTOS 列表通常用于任务调度(如就绪列表
pxReadyTasksLists
),因此其操作(插入、删除、遍历)必须保证 线程安全。在 SMP(多核)版本 的 FreeRTOS 中,列表可能涉及 自旋锁(Spin Lock) 保护。
(3) 支持按优先级排序
FreeRTOS 的任务调度依赖优先级,因此列表支持 按优先级排序,确保高优先级任务能快速被检索到。
2. FreeRTOS 列表的关键结构
(1) List_t
(列表结构体)
typedef struct xLIST {volatile UBaseType_t uxNumberOfItems; // 当前列表项数量ListItem_t *pxIndex; // 遍历指针(用于遍历列表)MiniListItem_t xListEnd; // 尾节点(哨兵节点)
} List_t;
uxNumberOfItems
:记录当前列表中的有效项数。pxIndex
:用于遍历列表(如listGET_OWNER_OF_NEXT_ENTRY()
)。xListEnd
:固定尾节点,使列表操作更高效(如判断是否到达末尾)。
(2) ListItem_t
(列表项结构体)
struct xLIST_ITEM {TickType_t xItemValue; // 存储排序值(如任务优先级)struct xLIST_ITEM *pxNext; // 指向下一个节点struct xLIST_ITEM *pxPrevious; // 指向前一个节点void *pvOwner; // 指向拥有者(如任务TCB)struct xLIST *pxContainer; // 指向所属的列表
};
xItemValue
:用于排序(例如任务的阻塞时间或优先级)。pvOwner
:通常指向任务的 TCB(任务控制块)。pxContainer
:记录该项属于哪个列表(便于快速移除)。
(3) MiniListItem_t
(简化版列表项,用于尾节点)
typedef struct xMINI_LIST_ITEM {TickType_t xItemValue; // 通常设为最大值(portMAX_DELAY)struct xLIST_ITEM *pxNext; // 指向第一个真实节点struct xLIST_ITEM *pxPrevious; // 指向最后一个真实节点
} MiniListItem_t;
仅用于
xListEnd
,不存储实际数据,仅作为列表遍历的边界。
3. 简单的链表示例
#include "usart.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>struct person{char *name;int age;int sex;struct person *couple;
};struct person w;
struct person h;int main(int argc, char **arg)
{HAL_Init();MX_USART1_UART_Init();w.name = 'w';w.age = 30;w.couple = &h;h.name = 'h';h.age = 30;h.couple = &w;printf ("w's couple is %s\r\n", w.couple ->name );while(1){}
}void Error_Handler(void)
{printf ("Error\r\n");while(1){}
代码运行结果如下:
4. 简单的列表示例:
#include "usart.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>struct person{char *name;int age;int sex;struct person *next;/*指向下一个人*/
};struct list{char *name;/*列表名称*/struct person *next;/*指向某个人*/
}int main(int argc, char **arg)
{HAL_Init();MX_USART1_UART_Init();struct list a_list;struct person p1;a_list.name = "A";a_list.next = NULL;/*空链表*/p1.name ="a1";p1.next = NULL;a_list.next = &p1;printf ("a list: %s\r\n", a_list.next ->name );while(1){}
}void Error_Handler(void)
{printf ("Error\r\n");while(1){}
数据结构定义:
struct person
:表示一个人的结构体,包含姓名、年龄、性别和指向下一个人的指针struct list
:表示一个链表,包含链表名称和指向第一个人的指针
主函数:
初始化HAL库和USART1
创建一个链表
a_list
和一个人p1
将
p1
添加到a_list
中打印链表中第一个人的名字
其中:
链表头部如下:
链表项如下:
代码运行结果如下:
5. 代码实现了一个简单的链表管理系统,用于管理一组人员信息
代码如下:
#include "usart.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>struct person{char *name;int age;struct person *next;/*指向下一个人*/
};struct list{char *name;/*列表名称*/struct person *next;/*指向某个人*/
};void InitList(struct list *pList,char *name)
{pList ->name = name ;pList ->next = NULL ;
}void AddItemToList(struct list *pList,struct person *new_person)
{struct person *last;// 声明一个指向person结构体的指针变量last/*如果是空链表*/if (pList->next == NULL){pList->next =new_person ;new_person->next = NULL;return;}last = pList->next;while (last->next){last = last->next;}last->next = new_person;new_person->next = NULL;
}void PrintList(struct list *pList)
{int i = 0;struct person *p = pList->next ;while (p != NULL){printf("Person %d: %s is %d\r\n", i++, p->name ,p->age);/*后面还有人,移动到下一个*/p = p->next;}
}int main(int argc, char **arg)
{HAL_Init();MX_USART1_UART_Init();struct list a_list;int i;struct person p[]={{"p1",10,NULL},{"p2",20,NULL},{"p3",13,NULL},{"p4",41,NULL},{"p5",56,NULL},{"p6",12,NULL},{"p7",9,NULL},{"p8",21,NULL},{NULL ,0,NULL},};InitList(&a_list,"A_class");i = 0;while(p[i].name != NULL){AddItemToList(&a_list,&p[i]);i++;}PrintList(&a_list);while(1){}
}void Error_Handler(void)
{printf ("Error\r\n");while(1){}
代码结构
数据结构定义:
struct person
: 表示一个人,包含姓名、年龄和指向下一个人员的指针struct list
: 表示一个链表,包含链表名称和指向第一个人员的指针
功能函数:
InitList()
: 初始化链表AddItemToList()
: 向链表添加新人员PrintList()
: 打印链表中的所有人员信息
主程序:
初始化硬件和链表
创建一组人员数据
将人员添加到链表中
打印链表内容
代码运行结果如下:
在上面的代码中,向链表末尾添加新节点,在数据结构和链表中,节点(Node) 是一个基本但非常重要的概念。
注:在链表中,尾部(Tail) 指的是链表的最后一个节点。
它的特征是:
指针域为NULL(
next = NULL
)后面没有其他节点
是链表遍历的终点
节点的定义
节点是数据结构中的基本单位,它包含两部分:
数据域 - 存储实际的数据
指针域 - 存储指向其他节点的地址
在上面的链表中,有两种节点:
1. 人员节点(struct person)
struct person {char *name; // 数据域:存储姓名int age; // 数据域:存储年龄struct person *next; // 指针域:指向下一个人员节点
};
2. 链表头节点(struct list)
struct list {char *name; // 数据域:链表名称struct person *next; // 指针域:指向第一个人员节点
};
节点的可视化表示
单个节点:
+---------------+ | 数据域 | ← 存储具体信息 | (name, age) | +---------------+ | 指针域 | ← 指向下一个节点 | next | → NULL 或其他节点 +---------------+
链表中的多个节点:
头节点 节点1 节点2 节点3 +----------+ +----------+ +----------+ +----------+ | "A班" | | "张三",18| | "李四",20| | "王五",22| +----------+ +----------+ +----------+ +----------+ | ●-----|----→| ●-----|----→| ●-----|----→| NULL | +----------+ +----------+ +----------+ +----------+
节点的作用
1. 数据存储
每个节点就像一个"容器",存储特定的数据:
人员节点存储:姓名、年龄
头节点存储:链表名称
2. 连接关系
通过指针域,节点之间形成连接:
// 节点1指向节点2
node1.next = &node2;// 节点2指向节点3
node2.next = &node3;// 节点3指向NULL,表示链表结束
node3.next = NULL;