跳到主要内容
C 算法
Linux 侵入式链表详解 Linux 内核中的侵入式双向链表。对比了传统链表与自包含链表,阐述了侵入式链表内存紧凑、性能高、支持多链表管理的优势。介绍了核心数据结构 list_head、初始化宏及增删遍历操作函数。重点解析了 container_of 宏原理,通过偏移量计算获取结构体指针。提供了基本使用、多链表管理及队列栈实现的示例代码,适用于系统编程、内核开发及高性能场景。
樱花落尽 发布于 2026/3/27 更新于 2026/4/16 2 浏览什么是侵入式链表
**侵入式链表(Intrusive Linked List)**是一种特殊的链表实现方式,它的特点是:链表节点直接嵌入到数据结构内部 ,而不是通过指针指向独立的数据节点。
在侵入式链表中,链表节点(list_head)是数据结构的一个成员,而不是独立存在的。这种设计使得链表操作更加高效,并且不需要额外的内存分配。
核心思想
数据结构包含 list_head 成员,list_head 嵌入在数据中,通过 container_of 获取完整数据。
与传统链表的对比
传统链表(非侵入式)
传统链表通常采用以下结构:
* data;
};
struct list_node {
void
struct list_node * next ;
struct list_node * prev ;
链表节点和数据是分离的
需要额外的指针来存储数据
每次访问数据都需要解引用
内存布局不连续
说明: list_node中的 data字段是一个指针,指向实际的数据对象,数据对象和链表节点在内存中是分离的。
自包含链表(专用链表节点) 还有一种常见的链表实现方式,数据直接嵌入在链表节点中:
struct list_node {
int a;
struct list_node * next ;
struct list_node * prev ;
};
数据直接存储在链表节点中,不是指针
链表节点就是数据结构本身
不需要额外的指针来访问数据
内存布局连续,但只适用于单一数据类型
简单直接,适合特定场景
说明: 数据字段(如 int a)直接嵌入在链表节点结构体中,链表节点本身就是数据容器。
数据类型简单且固定
不需要存储复杂数据结构
适合教学示例和简单应用
侵入式链表
struct list_head {
struct list_head * next ;
struct list_head * prev ;
};
struct my_data {
int value;
char name[32 ];
struct list_head list ;
};
链表节点直接嵌入在数据结构中
不需要额外的指针存储数据
通过 container_of宏从节点指针获取完整数据结构
内存布局更紧凑
说明: list_head直接嵌入在 my_data结构体中,数据对象和链表节点在内存中是连续的,通过 list成员连接。
三种链表类型对比总结 特性 传统链表 自包含链表 侵入式链表 数据存储 外部对象(通过指针) 节点内部 数据结构内部 灵活性 高(可存储任意类型) 低(固定类型) 高(任意类型) 内存开销 中等(需要指针) 低 最低 适用场景 通用场景 简单固定类型 系统编程、高性能场景
侵入式链表的优势
1. 内存效率高
无额外指针开销 :不需要额外的指针来存储数据
内存布局紧凑 :数据连续存储,缓存友好
2. 性能优势
减少内存分配 :不需要为链表节点单独分配内存
减少指针解引用 :数据访问路径更短
更好的缓存局部性 :数据连续存储,提高缓存命中率
3. 灵活性
一个对象可以同时属于多个链表 :只需在结构中包含多个 list_head成员
类型无关 :链表操作不关心数据类型,通用性强
4. 适合系统编程
零开销抽象 :编译后几乎没有额外开销
适合内核开发 :Linux 内核广泛使用
Linux 内核中的实现 Linux 内核在 include/linux/list.h中实现了完整的侵入式双向链表。这是经过多年优化的工业级实现。
文件位置 linux-4.14.7/include/linux/list.h
核心数据结构
list_head 结构体 struct list_head {
struct list_head * next ;
struct list_head * prev ;
};
这是侵入式链表的核心数据结构,只包含两个指针,非常简洁。
初始化宏
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
static inline void INIT_LIST_HEAD (struct list_head *list ) {
WRITE_ONCE(list ->next, list );
list ->prev = list ;
}
next和 prev都指向自身
形成一个空的双向循环链表
核心操作函数
1. 添加节点
list_add - 在头部添加
static inline void list_add (struct list_head *new, struct list_head *head) {
__list_add(new, head, head->next);
}
list_add_tail - 在尾部添加
static inline void list_add_tail (struct list_head *new, struct list_head *head) {
__list_add(new, head->prev, head);
}
__list_add - 内部插入函数
static inline void __list_add(struct list_head *new, struct list_head *prev, struct list_head *next) {
if (!__list_add_valid(new, prev, next)) return ;
next->prev = new;
new->next = next;
new->prev = prev;
WRITE_ONCE(prev->next, new);
}
2. 删除节点
list_del - 删除节点
static inline void list_del (struct list_head *entry) {
__list_del_entry(entry);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
static inline void __list_del_entry(struct list_head *entry) {
if (!__list_del_entry_valid(entry)) return ;
__list_del(entry->prev, entry->next);
}
static inline void __list_del(struct list_head *prev, struct list_head *next) {
next->prev = prev;
WRITE_ONCE(prev->next, next);
}
list_del_init - 删除并重新初始化
static inline void list_del_init (struct list_head *entry) {
__list_del_entry(entry);
INIT_LIST_HEAD(entry);
}
3. 遍历链表
list_for_each - 遍历链表节点
#define list_for_each(pos, head) \
for(pos = (head)->next; pos != (head); pos = pos->next)
list_for_each_entry - 遍历链表中的数据
#define list_for_each_entry(pos, head, member) \
for(pos = list_first_entry(head, typeof(*pos), member); \
&pos->member != (head); \
pos = list_next_entry(pos, member))
list_for_each_entry_safe - 安全遍历(允许删除)
#define list_for_each_entry_safe(pos, n, head, member) \
for(pos = list_first_entry(head, typeof(*pos), member), \
n = list_next_entry(pos, member); \
&pos->member != (head); \
pos = n, n = list_next_entry(n, member))
4. 其他常用操作
list_empty - 判断链表是否为空
static inline int list_empty (const struct list_head *head) {
return READ_ONCE(head->next) == head;
}
list_move - 移动节点
static inline void list_move (struct list_head *list , struct list_head *head) {
__list_del_entry(list );
list_add(list , head);
}
container_of 宏详解 container_of宏是侵入式链表的灵魂,它能够从结构体成员的指针获取整个结构体的指针。
宏定义
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char*)__mptr - offsetof(type, member)); \
})
工作原理
1. offsetof 宏 #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)
将 0强制转换为 TYPE*类型
访问 MEMBER成员,得到成员相对于结构体起始地址的偏移量
由于基址是 0,所以 &((TYPE *)0)->MEMBER就是偏移量
struct my_data {
int value;
char name[32 ];
struct list_head list ;
};
2. container_of 计算过程 struct my_data {
int value;
struct list_head list ;
};
struct my_data * data ;
struct list_head * list_ptr = &data->list ;
const typeof (((struct my_data*)0 )->list ) *__mptr = list_ptr;
char *__mptr_char = (char *)__mptr;
struct my_data * result = (struct my_data*)(__mptr_char - offsetof(struct my_data, list ));
地址:0x1000 0x1024 [my_data] [list]
^ ^
| |
data list_ptr
offsetof = 0 x1024 - 0 x1000 = 0 x24
从 list_ptr 获取 data: data = list_ptr - off setof = 0 x1024 - 0 x24 = 0 x1000 ✓
为什么需要 typeof typeof用于类型检查,确保传入的 ptr确实是 member类型的指针,提高代码安全性。
使用示例
示例 1:基本使用 #include <stdio.h>
#include <stdlib.h>
#include "list.h"
struct student {
int id;
char name[32 ];
int age;
struct list_head list ;
};
int main (void ) {
LIST_HEAD(student_list);
struct student * s1 = malloc (sizeof (struct student));
s1->id = 1 ;
strcpy (s1->name, "Alice" );
s1->age = 20 ;
INIT_LIST_HEAD(&s1->list );
struct student * s2 = malloc (sizeof (struct student));
s2->id = 2 ;
strcpy (s2->name, "Bob" );
s2->age = 21 ;
INIT_LIST_HEAD(&s2->list );
list_add(&s1->list , &student_list);
list_add(&s2->list , &student_list);
struct student * pos ;
list_for_each_entry(pos, &student_list, list ) {
printf ("ID: %d, Name: %s, Age: %d\n" , pos->id, pos->name, pos->age);
}
struct student * n ;
list_for_each_entry_safe(pos, n, &student_list, list ) {
list_del(&pos->list );
free (pos);
}
return 0 ;
}
示例 2:多个链表
struct task {
int pid;
char name[32 ];
struct list_head run_list ;
struct list_head wait_list ;
struct list_head all_tasks ;
};
struct task * t = malloc (sizeof (struct task));
INIT_LIST_HEAD(&t->run_list);
INIT_LIST_HEAD(&t->wait_list);
INIT_LIST_HEAD(&t->all_tasks);
list_add(&t->run_list, &run_queue);
list_add(&t->all_tasks, &task_list);
示例 3:实现队列
LIST_HEAD(queue );
void enqueue (struct list_head* item) {
list_add_tail(item, &queue );
}
struct list_head* dequeue (void ) {
if (list_empty(&queue )) return NULL ;
struct list_head * item = queue .next;
list_del(item);
return item;
}
示例 4:实现栈
LIST_HEAD(stack );
void push (struct list_head* item) {
list_add(item, &stack );
}
struct list_head* pop (void ) {
if (list_empty(&stack )) return NULL ;
struct list_head * item = stack .next;
list_del(item);
return item;
}
应用场景
1. Linux 内核
进程管理 :进程链表、运行队列
内存管理 :页框链表、伙伴系统
文件系统 :inode 链表、dentry 缓存
设备驱动 :设备链表
网络协议栈 :sk_buff 链表
2. 高性能系统编程
嵌入式系统 :资源受限环境
实时系统 :低延迟要求
游戏引擎 :性能敏感场景
3. 需要多链表管理的场景 当一个对象需要同时属于多个链表时,侵入式链表特别有用:
struct process {
int pid;
struct list_head runq ;
struct list_head waitq ;
struct list_head children ;
struct list_head siblings ;
};
总结
侵入式链表的优点
内存效率高 :无额外指针开销
性能优秀 :缓存友好,访问速度快
灵活性强 :一个对象可属于多个链表
类型无关 :通用性强,代码复用性好
侵入式链表的缺点
侵入性 :需要修改数据结构,添加 list_head成员
学习曲线 :container_of宏的理解需要一定时间
调试困难 :指针操作较多,调试相对复杂
适用场景
✅ 系统编程(内核、驱动)
✅ 性能敏感的应用
✅ 需要多链表管理的场景
✅ 内存受限的环境
❌ 简单的用户态应用(可能过度设计)
关键要点
理解 container_of宏 :这是侵入式链表的精髓
正确使用遍历宏 :区分普通遍历和安全遍历
注意内存管理 :删除节点后要释放内存
理解循环链表 :链表头指向自身表示空链表
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown转HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
HTML转Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online