欢迎光临
我们一直在努力

FreeRTOS内核链表认识

链表

普通链表的操作

struct person {
char *name;
int age;
int sex;
struct person *next;//couple
};

struct list {
char *name;
struct person *next;
};

struct person w;
struct person h

int main(void)
{
w.name = 'w';
w.age = 30;
w.couple = &h;

h.name = 'h';
h.age = 35;
h.couple = &w;

printf("w.couple->name %s\\n", w.couple->name);

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("%s\\n",a_list.next->name)
}

普通链表的创建和添加操作

struct person {
char *name;
int age;
struct person *next;
};

struct List {
char *name;
struct person head;
}

void InitList(struct List *pList, char *name)
{
pList->name = name;
pList->head.next = NULL;
}

void AddItemToList(struct List *pList, struct person *new_person)
{
struct person *last = &pList->head;

/* 找到最后一个人 */
last = pList->head.next;
while (last->next != NULL) {
/*后面还有人 移动下一个*/
last = last->next;
}

/* 从循环退出,必定符合:last->next = NULL */

/* 加入新人*/
last->next = new_person;
new_person->next = NULL;
}

普通链表删除操作

void DelItemFromList(struct List *pList, struct person *person)
{
/* 指向表头 */
struct person *pre = &pList->head;
/* 找到person */
while (pre != NULL && pre->next != person) {
pre = pre->next
}
/* 如果找不到或者链表为空直接退出 */
if (pre == NULL)
return;
else
/* 找到的话,只有一个节点就将链表初始化一遍相当于删除*/
pre->next = person->next;
}

普通链表的排序

struct person {
char *name;
int age;
struct person *next;
};

struct List {
char *name;
struct person head;
}

void InitList(struct List *pList, char *name)
{
pList->name = name;
pList->head.next = NULL;
}

void AddItemToList(struct List *pList, struct person *new_person)
{
struct person *last = &pList->head;

/* 找到最后一个人 */
last = pList->head.next;
while (last->next != NULL) {
/*后面还有人 移动下一个*/
last = last->next;
}

/* 从循环退出,必定符合:last->next = NULL */

/* 加入新人*/
last->next = new_person;
new_person->next = NULL;
}
//在前节点后插入新节点
void AddItemAfter(struct person pre, struct person *new_person)
{
new_person->next = pre.next;
pre.next = new_person;
}

void DelItemFromList(struct List *pList, struct person *person)
{
/* 指向表头 */
struct person *pre = &pList->head;
/* 找到person */
while (pre != NULL && pre->next != person) {
pre = pre->next
}
/* 如果找不到或者链表为空直接退出 */
if (pre == NULL)
return;
else
/* 找到的话,只有一个节点就将链表初始化一遍相当于删除*/
pre->next = person->next;
}

void SortList(struct List *pList)
{
// int a[100];
// int i,j;
// int tmp;
// for (i = 0; i<100; i++)
// for (j = j+1; j<100; j++) {
// if (a[i] > a[j]) {
// tmp = a[i];
// a[i] = a[j];
// a[j] = tmp;
// }
// }
//简便冒泡排序
// struct person *cur, *next;
// int tmp;
// for (cur = pList->head.next; cur != NULL; cur = cur->next) {
// for (next = cur->next; next != NULL; next = next->next) {
// if (cur->age > next->age) {
// tmp = cur->age;
// cur->age = next->age;
// next->age = tmp;
// // 如有其他字段,一并交换
// }
// }
// }
struct person *pre1 = &pList->head;
struct person *pre2;
struct person *cur = pre1->next;
struct person *next;
struct person *tmp;

while (cur) {
pre2 = cur;
next = cur->next;
while (next) {
if (cur->age > next->age) {
/* 交换节点 */
/* 删除cur节点*/
DelItemFromList(pList, cur);
/* del next 节点 */
DelItemFromList(pList, next);
/* 在pre1之后插入next */
AddItemAfter(pre1, next);
/* 在pre2之后插入cur */
if (pre2 == cur)
AddItemAfter(next, cur);
AddItemAfter(pre2, cur)

/* cur 和 next 指针互换 */
tmp = cur;
cur = next;
next = tmp;
}
pre2 = next;
next = next->next;
}
pre1 = cur;
cur = cur->next;
}
}

void PrintList(struct List *pList)
{
struct person *p = &pList->head.next;

/* 找到最后一个人 */
while (p != NULL) {
/*后面还有人 移动下一个*/
printf("%s %d \\n", p->name,p->age);
p = p->next;
}

printf(const char * _Nonnull, …)
}

通用链表操作

#include <stdio.h>
#include <stddef.h>

struct node_t {
struct node_t *next;
};

struct person {
char *name;
int age;
struct node_t node; // 内嵌节点
};

struct List {
char *name;
struct node_t head; // 哨兵节点
};

/* ========== 4. 地址反推宏 ========== */
#define container_of(ptr, type, member) \\
((type *)((char *)(ptr) – offsetof(type, member)))

// 通过 node 指针获取 person 指针
struct person *GetPerson(struct node_t *node) {
return (struct person *)((char *)node – offsetof(struct person, node));
}

// 比较年龄:p1 < p2 返回 -1,相等 0,大于 1
int CmpPersonAge(struct node_t *node1, struct node_t *node2) {
struct person *p1 = GetPerson(node1);
struct person *p2 = GetPerson(node2);
if (p1->age < p2->age) return -1;
if (p1->age > p2->age) return 1;
return 0;
}

void InitList(struct List *pList, char *name) {
pList->name = name;
pList->head.next = NULL;
}

void AddItemToList(struct List *pList, struct node_t *new_node) {
struct node_t *last = &pList->head;
while (last->next) {
last = last->next;
}
last->next = new_node;
new_node->next = NULL;
}

// 在 pre 节点之后插入 new_node
void AddItemAfter(struct node_t *pre, struct node_t *new_node) {
new_node->next = pre->next;
pre->next = new_node;
}

void DelItemFromList(struct List *pList, struct node_t *node) {
struct node_t *pre = &pList->head;
while (pre->next != NULL && pre->next != node) {
pre = pre->next;
}
if (pre->next == node) {
pre->next = node->next;
node->next = NULL;
}
}

// 按 age 升序排序,通过交换相邻节点指针实现
void SortList(struct List *pList) {
if (pList == NULL || pList->head.next == NULL || pList->head.next->next == NULL)
return;

int swapped;
struct node_t *pre, *cur, *next;

do {
swapped = 0;
pre = &pList->head;
cur = pre->next;

while (cur != NULL && cur->next != NULL) {
next = cur->next;
if (CmpPersonAge(cur, next) > 0) { // cur->age > next->age,交换
// 交换 cur 和 next
pre->next = next;
cur->next = next->next;
next->next = cur;

// 更新 pre,cur 保持在 next 之后
pre = next;
swapped = 1;
} else {
pre = cur;
cur = cur->next;
}
}
} while (swapped);
}

void PrintList(struct List *pList) {
struct node_t *node = pList->head.next;
while (node != NULL) {
struct person *p = GetPerson(node);
printf("%s %d\\n", p->name, p->age);
node = node->next;
}
}

int main(int argc, char **argv) {
struct List a_list;
int i = 0;
struct person p[] = {
{"p1", 10, {NULL}},
{"p2", 20, {NULL}},
{"p3", 30, {NULL}},
{"p4", 40, {NULL}},
{"p5", 50, {NULL}},
{"p6", 60, {NULL}},
{"p7", 70, {NULL}},
{"p8", 80, {NULL}},
{NULL, 0, {NULL}} // 结束标志
};

InitList(&a_list, "A_class");
while (p[i].name != NULL) {
AddItemToList(&a_list, &p[i].node);
i++;
}

printf("排序前:\\n");
PrintList(&a_list);

SortList(&a_list);

printf("排序后:\\n");
PrintList(&a_list);

return 0;
}

通用链表实现三种方式
方法1:节点放在容器最前面,直接强转

#include <stdio.h>

// 通用节点
struct node_t {
struct node_t *next;
};

// 容器:节点必须在最前面
struct person {
struct node_t node; // 第一个成员
char *name;
int age;
};

struct dog {
struct node_t node; // 第一个成员
char *name;
int age;
};

// 链表头(哨兵)
struct list_t {
struct node_t head;
};

void list_init(struct list_t *list) {
list->head.next = NULL;
}

void list_add(struct list_t *list, struct node_t *node) {
struct node_t *last = &list->head;
while (last->next) last = last->next;
last->next = node;
node->next = NULL;
}

// 遍历:直接强转
void list_print(struct list_t *list) {
struct node_t *node = list->head.next;
while (node) {
struct person *p = (struct person *)node; // 直接转换
printf("%s %d\\n", p->name, p->age);
node = node->next;
}
}

int main() {
struct list_t list;
list_init(&list);

struct person p1 = {{NULL}, "Alice", 30};
struct person p2 = {{NULL}, "Bob", 25};

list_add(&list, &p1.node);
list_add(&list, &p2.node);

list_print(&list);
return 0;
}

关键点:node 必须是容器的第一个成员,&p->node == p,直接 (struct person *)node 即可。

方法2:节点放任意位置,通过 container_of 反推

#include <stdio.h>
#include <stddef.h>

// 通用节点
struct node_t {
struct node_t *next;
};

// 容器:节点可以放在任意位置
struct person {
char *name;
struct node_t node; // 中间位置
int age;
};

struct dog {
int age;
struct node_t node; // 最后位置
char *name;
};

// 地址反推宏
#define container_of(ptr, type, member) \\
((type *)((char *)(ptr) – offsetof(type, member)))

// 链表头
struct list_t {
struct node_t head;
};

void list_init(struct list_t *list) {
list->head.next = NULL;
}

void list_add(struct list_t *list, struct node_t *node) {
struct node_t *last = &list->head;
while (last->next) last = last->next;
last->next = node;
node->next = NULL;
}

// 遍历:通过 container_of 反推
void list_print(struct list_t *list) {
struct node_t *node = list->head.next;
while (node) {
struct person *p = container_of(node, struct person, node);
printf("%s %d\\n", p->name, p->age);
node = node->next;
}
}

int main() {
struct list_t list;
list_init(&list);

struct person p1 = {"Alice", {NULL}, 30};
struct person p2 = {"Bob", {NULL}, 25};

list_add(&list, &p1.node);
list_add(&list, &p2.node);

list_print(&list);
return 0;
}

关键点:node 可以在容器任意位置,通过 container_of 计算偏移得到容器首地址。

方法3:节点中保存容器指针

#include <stdio.h>

// 通用节点:包含容器指针
struct node_t {
void *container; // 指向所属容器
struct node_t *next;
};

// 容器:不需要内嵌节点,或者节点独立
struct person {
char *name;
int age;
};

// 链表头
struct list_t {
struct node_t head;
};

void list_init(struct list_t *list) {
list->head.next = NULL;
}

// 添加节点时,需要传入容器指针
void list_add(struct list_t *list, struct node_t *node, void *container) {
node->container = container; // 保存容器地址
struct node_t *last = &list->head;
while (last->next) last = last->next;
last->next = node;
node->next = NULL;
}

// 遍历:直接取 node->container
void list_print(struct list_t *list) {
struct node_t *node = list->head.next;
while (node) {
struct person *p = (struct person *)node->container; // 直接取容器
printf("%s %d\\n", p->name, p->age);
node = node->next;
}
}

int main() {
struct list_t list;
list_init(&list);

struct person p1 = {"Alice", 30};
struct person p2 = {"Bob", 25};

// 节点可以独立定义,也可以内嵌,这里为了演示独立定义
struct node_t n1 = {0}, n2 = {0};

list_add(&list, &n1, &p1);
list_add(&list, &n2, &p2);

list_print(&list);
return 0;
}

关键点:节点中显式保存 container 指针,添加时传入容器地址,遍历时直接取出。节点可以与容器分离。

三种方法对比
维度方法1:节点在最前方法2:container_of 反推方法3:节点保存容器指针
节点位置 必须是容器第一个成员 容器任意位置 节点可独立,不要求内嵌
获取容器 直接强制转换 偏移计算(编译期常量) 直接读取 node->container
内存开销 无额外开销 无额外开销 每个节点多一个指针(4/8字节)
一个容器挂多个链表 困难(节点都在最前会冲突) 容易(嵌入多个节点) 容易(多个节点分别保存容器指针)
代码复杂度 最低 需要 container_of 宏 添加时需传容器指针,节点管理稍复杂
类型安全 强转,类型需匹配 强转,但可通过宏指定类型 强转,类型需匹配
生命周期 节点与容器同生共死 节点与容器同生共死 节点可独立分配/释放,更灵活
适用场合 简单场景,容器只挂一个链表,追求极简 Linux 内核、FreeRTOS 等复杂系统,一个容器挂多个链表 节点需要动态创建,或容器与节点分离管理,如设备树、对象池
双向链表
值传递和指针传递

struct node_t n;
n.next = NULL; // 正确:n 是变量

struct node_t *p = &n;
p->next = NULL; // 正确:p 是指针

// 值传递:形参是变量,用 .
void func(struct node_t pre) {
pre.next = NULL; // 修改的是副本,不影响原结构体
}

// 指针传递:形参是指针,用 ->
void func(struct node_t *pre) {
pre->next = NULL; // 修改的是原结构体
}

应用

struct node_t {
struct node_t *pre;
struct node_t *next;
}

struct person {
char *name;
int age;
struct node_t node;
};

struct List {
char *name;
struct node_t head;
}

void InitList(struct List *pList, char *name)
{
pList->name = name;
pList->head.next = &pList->head;
pList->head.pre = &pList->head;
}

void AddItemToList(struct List *pList, struct node_t *new_node)
{
struct node_t *last = &pList->head.pre;
/* 加入新节点 */
new_node->next = pList->head;
last->next = new_node;
new_node->pre = last;
pList->head.pre = new_node;
}
//在前节点后插入新节点
void AddItemAfter(struct node_t *pre, struct node_t *new_node)
{
new_node->next = pre->next;
if (pre->nextnext != NULL) {
pre->nextnext->pre = new_node;
}
pre->next = new_node;
new_node->pre = pre;
}

/* 删除节点 */
void DelItemFromList(struct List *pList, struct node_t *node)
{
struct node_t *left = node->pre;
struct node_t *right = node->next;

left->next = right;
right->pre = left;
}

// 地址反推宏
#define container_of(ptr, type, member) \\
((type *)((char *)(ptr) – offsetof(type, member)))

int CmpPersonAge(struct node_t *pre, struct node_t *next)
{
struct person *p;
struct person *n;

p = container_of(pre, struct person, node);
n = container_of(next, struct person, node);
if (p->age < n->age) {
return -1;
}
else {
return 0;
}
}
void SortList(struct List *pList)
{
// int a[100];
// int i,j;
// int tmp;
// for (i = 0; i<100; i++)
// for (j = j+1; j<100; j++) {
// if (a[i] > a[j]) {
// tmp = a[i];
// a[i] = a[j];
// a[j] = tmp;
// }
// }
//简便
// struct person *cur, *next;
// int tmp;
// for (cur = pList->head.next; cur != NULL; cur = cur->next) {
// for (next = cur->next; next != NULL; next = next->next) {
// if (cur->age > next->age) {
// tmp = cur->age;
// cur->age = next->age;
// next->age = tmp;
// // 如有其他字段,一并交换
// }
// }
// }
struct node_t *pre1 = &pList->head;
struct node_t *pre2;
struct node_t *cur = pre1->next;
struct node_t *next;
struct node_t *tmp;

while (cur != pList->head) {
pre2 = cur;
next = cur->next;
while (next != pList->head) {
if (CmpPersonAge(cur, next) == 0) {
/* 交换节点 */
/* 删除cur节点*/
DelItemFromList(pList, cur);
/* del next 节点 */
DelItemFromList(pList, next);
/* 在pre1之后插入next */
AddItemAfter(pre1, next);
/* 在pre2之后插入cur */
if (pre2 == cur)
AddItemAfter(next, cur);
AddItemAfter(pre2, cur)

/* cur 和 next 指针互换 */
tmp = cur;
cur = next;
next = tmp;
}
pre2 = next;
next = next->next;
}
pre1 = cur;
cur = cur->next;
}
}

void PrintList(struct List *pList)
{
struct node_t *node = pList->head.next;
struct person *p;

/* 找到最后一个人 */
while (node != pList->head) {
p = (struct person*)((char *)node – (unsigned int)&((struct person *)0)->node);
/*后面还有人 移动下一个*/
printf("%s %d \\n", p->name,p->age);
node = node->next;
}

printf(const char * _Nonnull, …)
}

FreeRTOS的链表代码
三个核心结构体
链表

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;

  • uxNumberOfItems:链表中有多少个item
  • pxIndex:指向当前正在使用的item,这个pxIndex用来遍历链表。不遍历到哨兵
迷你节点结构体

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;/* 前一个节点 */
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

节点结构体

struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE /* 完整性检查(调试用,通常为空) */
configLIST_VOLATILE TickType_t xItemValue; /* 排序值:延时链表存到期Tick,就绪链表存优先级 */
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 /* 完整性检查(调试用,通常为空) */
};
typedef struct xLIST_ITEM ListItem_t;

  • pvOwner:包含ITEM的结构体
  • pxContainer:节点所属链表
add item

在pxIndex之前插入:在pxIndex之前的元素之后插入

按序添加

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue;

/* 情况1:插入值等于 portMAX_DELAY(最大值),直接插到哨兵前面 */
if( xValueOfInsertion == portMAX_DELAY )
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
/* 情况2:遍历链表,找到第一个 xItemValue > 新值 的节点 */
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd );
pxIterator->pxNext->xItemValue <= xValueOfInsertion;
pxIterator = pxIterator->pxNext )
{
/* 空循环,只移动 pxIterator */
}
}

/* 在 pxIterator 之后插入新节点 */
pxNewListItem->pxNext = pxIterator->pxNext;
pxNewListItem->pxNext->pxPrevious = pxNewListItem;
pxNewListItem->pxPrevious = pxIterator;
pxIterator->pxNext = pxNewListItem;

/* 更新归属链表与计数 */
pxNewListItem->pxContainer = pxList;
( pxList->uxNumberOfItems )++;
}

删除节点

UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
{
/* 通过节点的 pxContainer 获取它所在的链表 */
List_t * const pxList = ( List_t * ) pxItemToRemove->pxContainer;

/* 1. 从双向环形链表中摘除节点:让前驱和后继互相指向 */
pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

/* 2. 如果被删除的节点正好是 pxIndex 指向的节点,调整 pxIndex */
if( pxList->pxIndex == pxItemToRemove )
{
pxList->pxIndex = pxItemToRemove->pxPrevious; // 指向前一个节点
}

/* 3. 清除归属标记,计数减一 */
pxItemToRemove->pxContainer = NULL;
( pxList->uxNumberOfItems )–;

return pxList->uxNumberOfItems; // 返回剩余节点数
}

内核链表核心操作函数

内核链表对外主要提供 4 个函数:

函数作用
vListInitialise() 初始化链表
vListInitialiseItem() 初始化节点
vListInsert() 按 xItemValue 有序插入
vListInsertEnd() 插到当前遍历位置前面
uxListRemove() 从链表中删除节点
初始化链表头vListInitialise()

void vListInitialise( List_t * const pxList )
{
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );/* 遍历游标先指向哨兵 */

pxList->xListEnd.xItemValue = portMAX_DELAY /* 哨兵值设为最大,保证永远排在最后 */

pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); /* 空链表:哨兵 next 指自己 */
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/* 空链表:哨兵 previous指向自己 */

pxList->uxNumberOfItems = ( UBaseType_t ) 0U; /* 当前没有真实节点 */
}

步骤:

1. 设置遍历游标

pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd );/* 遍历游标先指向哨兵 */

2. 设置哨兵值

pxList->xListEnd.xItemValue = portMAX_DELAY /* 哨兵值设为最大,保证永远排在最后 */

3. 让哨兵自己成环

pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); /* 空链表:哨兵 next 指自己 */
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd );/* 空链表:哨兵 previous指向自己 */

4. 节点数清零

pxList->uxNumberOfItems = ( UBaseType_t ) 0U; /* 当前没有真实节点 */

初始化完成后,这个链表就可以使用了,但里面还没有任何真实节点

![[Pasted image 20261023222928.png]]

初始化节点:vListInitialiseItem()

void vListInitialiseItem( ListItem_t * const pxItem )
{
pxItem->pxContainer = NULL;
}

这行代码很关键

pxItem->pxContainer = NULL;

表示:
当前节点不在任何链表里。
以后只要看到pxContainer == NULL,说明这节点是“游离状态”。

使用前的固定流程

任何节点在插入链表前,都必须先执行。

vListInitialiseItem(&某个ListItem)

任何链表在使用前,都必须先执行。

vListInitialiseItem(&某个List)

如果跳过这两步,后面插入,删除时容易把内存链表结构弄坏。

按值插入 vListInsert()

作用:
把一个新节点按 xItemValue 从小到大插入链表

void vListInsert( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t *pxIterator;
const TickType_t xValueOfInsertion = pxNewListItem->xItemValue; /* 取出待插入节点的排序值 */

if( xValueOfInsertion == portMAX_DELAY ) /* 最大值插到哨兵前面(逻辑尾部) */
{
pxIterator = pxList->xListEnd.pxPrevious;
}
else
{
for( pxIterator = ( ListItem_t * ) &( pxList->xListEnd ); /* 从哨兵出发后查找 */
pxIterator->pxNext->xItemValue <= xValueOfInsertion; /* 找第一个比新值大的位置,相同值插到后面 */
pxIterator = pxIterator->pxNext )
{
/* 空循环,只移动 pxIterator */
}
}

pxNewListItem->pxNext = pxIterator->pxNext; /* 新节点 next 指向后继 */
pxNewListItem->pxNext->pxPrevious = pxNewListItem; /* 后继 previous 指向新节点 */
pxNewListItem->pxPrevious = pxIterator; /* 新节点 previous 指向前驱 */
pxIterator->pxNext = pxNewListItem; /* 前驱 next 指向新节点 */

pxNewListItem->pxContainer = pxList; /* 记录新节点所属链表 */
( pxList->uxNumberOfItems )++; /* 节点数加 1 */
}

插入规则
  • 规则1:按值升序排列
    即:
    值越小,越靠前
    值越大,越靠后
  • 规则2:相同值插在后面
    判断条件是:
    pxIterator->pxNext->xItemValue <= xValueOfInsertion
    意味着:

    • 如果新节点和已有节点的排序值相同
    • 新节点后插到相同排序值节点的后面
  • 规则3:哨兵永远在最后
    • 因为哨兵的排序值是 portMAX_DELAY,普通节点不可能比它大,永远在最后。

![[Pasted image 20261023222807.png]]

尾部插入:vListInsertEnd()

函数原型:

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

和 vListInsert() 最大区别是:
不按值排序,而是插到 pxIndex 前面。

源码

void vListInsertEnd( List_t * const pxList, ListItem_t * const pxNewListItem )
{
ListItem_t * const pxIndex = pxList->pxIndex; /* 取出当前遍历游标 */

pxNewListItem->pxNext = pxIndex; /* 新节点next指向游标 */
pxNewListItem->pxPrevious = pxIndex->pxPrevious;/* 新节点 previous 指向游标前驱 */

pxIndex->pxPrevious->pxNext = pxNewListItem;/* 游标的 previous 的next指向新节点 */
pxIndex->pxPrevious = pxNewListItem;/* 游标的 previous 指向新节点 */

pxNewListItem->pvContainer = ( void * ) pxList; /* 记录新节点所属链表 */

( pxList->uxNumberOfItems )++; /* 节点数++ */
}

它到底插哪里

准确来说:
插到当前 pxIndex的前面
如果当前链表是:

pxIndex
|
▼
[TaskA] <–> [TaskB] <–> [哨兵]

执行:

vListInsertEnd(pxList, &TaskC);

结果是:

pxIndex
|
▼
[TaskC] <–> [TaskA] <–> [TaskB] <–> [哨兵]

注意:

  • TaskC 插在 pxIndex 前面
  • 不是简单加到物理尾部
    为什么要有这个函数
    因为同优先级就绪任务并不需要按时间排序,只需要轮流执行。
    所以内核的做法是:
  • 新就绪任务用 vListInsertEnd() 挂进就绪链表
  • 调度时用 listGET_OWNER_OF_NEXT_ENTRY() 依次取下一个任务
    这样就实现了时间片轮转。
  • 删除节点

    函数原型:

    UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove );

    它最特别地方:
    删除时只需要节点指针,不需要链表头。
    原因:节点保存了所属链表 pvContainer。

    UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )
    {
    List_t * const pxList = ( List_t * ) pxItemToRemove->pvContainer;/* 通过节点找到所在链表,无需传链表头 */

    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;/* 后继节点的previous跳过被删节点 */
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;/* 前驱节点的next跳过被删节点 */

    if( pxList->pxIndex == pxItemToRemove ) /* 若删的正是当前游标 */
    {
    pxList->pxIndex = pxItemToRemove->pxPrevious; /* 游标回退到前一节点 */
    }

    pxItemToRemove->pvContainer = NULL; /* 标志节点已脱离链表 */
    ( pxList->uxNumberOfItems )–; /* 链表节点数– */

    return pxList->uxNumberOfItems; /* 返回删除后剩余节点数 */
    }

    删除步骤
    假设要删除 NodeB:

    1 [NodeA] <–> [NodeB] <–> [NodeC] <–> [哨兵]

    第1步:通过pvContainer找到链表

    pxList = ( List_t * ) pxItemToRemove->pvContainer;

    第2步:该前后指针

    pxItemToRemove->pxNext->pxPrevious = pxItemToRemove->pxPrevious;
    pxItemToRemove->pxPrevious->pxNext = pxItemToRemove->pxNext;

    第3步:如果删的是当前游标,就回退游标

    if( pxList->pxIndex == pxItemToRemove )
    {
    pxList->pxIndex = pxItemToRemove->pxPrevious; /* 游标回退到前一节点 */
    }

    第4步:解绑链表标志

    pxItemToRemove->pvContainer = NULL;

    第5步:节点数减一

    ( pxList->uxNumberOfItems )–;

    返回值:
    函数返回链表还剩多少个节点。
    这个返回值在内核常用来判断链表是否为空

    内核链表常用关键宏

    #define listGET_OWNER_OF_NEXT_ENTRY( pxTCB, pxList ) \\
    { \\
    List_t * const pxConstList = ( pxList ); /* 取链表指针,防止宏内被修改 */ \\
    ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;/* 游标后移一个节点*/ \\
    if( ( void * ) ( pxConstList )->pxIndex == ( void * ) &( ( pxConstList )->xListEnd ) )/*若移到哨兵*/ \\
    { \\
    ( pxConstList )->pxIndex = ( pxConstList )->pxIndex->pxNext;/* 跳过哨兵,再后移一个 */ \\
    } \\
    ( pxTCB ) = ( pxConstList )->pxIndex->pvOwner; /* 返回当前游标对应节点的宿主对象 */ \\
    }

    它的工作流程是:

  • pxIndex先往后移一个
  • 判断是否移到了哨兵,如果移到,再后移一个
  • 返回当前 pxIndex 对应节点的pvOwner。
    所以本质上是在做:
    以TCB为实际载荷的环形链表的轮转遍历
  • ![[Pasted image 20261024000302.png]]

    赞(0)
    未经允许不得转载:171主机测评 » FreeRTOS内核链表认识
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址