链表
普通链表的操作
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 指针,添加时传入容器地址,遍历时直接取出。节点可以与容器分离。
三种方法对比
| 节点位置 | 必须是容器第一个成员 | 容器任意位置 | 节点可独立,不要求内嵌 |
| 获取容器 | 直接强制转换 | 偏移计算(编译期常量) | 直接读取 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]]](https://www.171host.com/wp-content/uploads/2026/10/20261024081636-6adc6964a6376.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]]](https://www.171host.com/wp-content/uploads/2026/10/20261024081637-6adc696569cd1.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 前面
- 不是简单加到物理尾部
为什么要有这个函数
因为同优先级就绪任务并不需要按时间排序,只需要轮流执行。
所以内核的做法是:
这样就实现了时间片轮转。
删除节点
函数原型:
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; /* 返回当前游标对应节点的宿主对象 */ \\
}
它的工作流程是:
所以本质上是在做:
以TCB为实际载荷的环形链表的轮转遍历
![![[Pasted image 20261024000302.png]]](https://www.171host.com/wp-content/uploads/2026/10/20261024081637-6adc6965bcc90.png)


