一、LIST_HEAD是什么?
LIST_HEAD是 Linux 内核中定义并初始化链表头的宏。它用于创建一个双向循环链表的头部节点。
c
复制
下载
#include <linux/list.h> LIST_HEAD(my_list);
这个宏展开后等价于:
c
复制
下载
struct list_head my_list = { &my_list, &my_list };即创建一个名为my_list的链表头,它的next和prev指针都指向自己(空链表状态)。
二、内核链表的定义
内核链表不是将链表指针嵌入数据中,而是将链表节点嵌入数据结构中——这是一种"侵入式"链表设计。
c
复制
下载
// 链表节点结构(内核定义) struct list_head { struct list_head *next; struct list_head *prev; }; // 使用时,将 list_head 嵌入到你的数据结构中 struct my_data { int id; char name[20]; struct list_head list; // 链表节点 };三、LIST_HEAD的使用方式
方式1:LIST_HEAD定义链表头
c
复制
下载
#include <linux/list.h> #include <linux/slab.h> #include <linux/module.h> // 定义链表头(静态初始化) LIST_HEAD(my_list); struct my_data { int id; char name[20]; struct list_head list; }; // 添加节点 void add_node(int id, const char *name) { struct my_data *node; node = kmalloc(sizeof(*node), GFP_KERNEL); if (!node) return; node->id = id; strncpy(node->name, name, sizeof(node->name) - 1); node->name[sizeof(node->name) - 1] = '\0'; // 插入到链表头部 list_add(&node->list, &my_list); } // 遍历链表 void print_list(void) { struct my_data *entry; struct list_head *pos; list_for_each(pos, &my_list) { entry = list_entry(pos, struct my_data, list); printk(KERN_INFO "ID: %d, Name: %s\n", entry->id, entry->name); } }方式2:LIST_HEAD_INIT动态初始化
如果链表头是动态分配的,用LIST_HEAD_INIT初始化:
c
复制
下载
struct list_head *my_list; my_list = kmalloc(sizeof(*my_list), GFP_KERNEL); LIST_HEAD_INIT(my_list); // 或者 INIT_LIST_HEAD(my_list)
四、内核链表的核心操作
| 函数/宏 | 作用 |
|---|---|
LIST_HEAD(name) | 定义并初始化链表头 |
INIT_LIST_HEAD(head) | 初始化已存在的链表头 |
list_add(new, head) | 在 head 后面插入新节点(头插) |
list_add_tail(new, head) | 在 head 前面插入新节点(尾插) |
list_del(entry) | 从链表中删除节点 |
list_empty(head) | 判断链表是否为空 |
list_for_each(pos, head) | 遍历链表(pos 是 list_head*) |
list_for_each_safe(pos, n, head) | 安全遍历(支持删除) |
list_entry(ptr, type, member) | 从 list_head 获取包含它的结构体 |
五、list_entry详解
list_entry是内核链表最精妙的部分,它通过成员指针反推结构体首地址。
c
复制
下载
#define list_entry(ptr, type, member) \ container_of(ptr, type, member) #define container_of(ptr, type, member) ({ \ const typeof(((type *)0)->member) *__mptr = (ptr); \ (type *)((char *)__mptr - offsetof(type, member)); \ })原理:用offsetof计算member在结构体中的偏移量,然后用ptr减去这个偏移量,得到结构体的首地址。
c
复制
下载
struct my_data { int id; char name[20]; struct list_head list; // 假设偏移量是 24 字节 }; // 已知 &node->list,反推 node 的地址 struct my_data *node = list_entry(&node->list, struct my_data, list); // 等价于: (struct my_data*)((char*)&node->list - 24)六、完整示例(内核模块)
c
复制
下载
#include <linux/module.h> #include <linux/kernel.h> #include <linux/list.h> #include <linux/slab.h> MODULE_LICENSE("GPL"); // 定义链表头 LIST_HEAD(my_list); struct student { int id; char name[20]; struct list_head list; }; // 添加学生 static void add_student(int id, const char *name) { struct student *s = kmalloc(sizeof(*s), GFP_KERNEL); if (!s) return; s->id = id; snprintf(s->name, sizeof(s->name), "%s", name); list_add_tail(&s->list, &my_list); // 尾插 } // 遍历打印 static void print_students(void) { struct student *s; struct list_head *pos; printk(KERN_INFO "=== Student List ===\n"); list_for_each(pos, &my_list) { s = list_entry(pos, struct student, list); printk(KERN_INFO "ID: %d, Name: %s\n", s->id, s->name); } } // 删除指定 ID 的学生 static void delete_student(int id) { struct student *s; struct list_head *pos, *n; list_for_each_safe(pos, n, &my_list) { s = list_entry(pos, struct student, list); if (s->id == id) { list_del(pos); kfree(s); printk(KERN_INFO "Deleted student ID: %d\n", id); return; } } printk(KERN_INFO "Student ID %d not found\n", id); } // 清空链表 static void clear_list(void) { struct student *s; struct list_head *pos, *n; list_for_each_safe(pos, n, &my_list) { s = list_entry(pos, struct student, list); list_del(pos); kfree(s); } } // 模块初始化 static int __init my_init(void) { printk(KERN_INFO "Module loaded\n"); add_student(1, "Alice"); add_student(2, "Bob"); add_student(3, "Charlie"); print_students(); delete_student(2); print_students(); return 0; } // 模块卸载 static void __exit my_exit(void) { clear_list(); printk(KERN_INFO "Module unloaded\n"); } module_init(my_init); module_exit(my_exit);七、LIST_HEAD与其他初始化方式对比
| 方式 | 代码 | 适用场景 |
|---|---|---|
LIST_HEAD(name) | LIST_HEAD(my_list); | 静态定义链表头(全局/局部变量) |
INIT_LIST_HEAD(head) | INIT_LIST_HEAD(&my_list); | 动态初始化已存在的链表头 |
LIST_HEAD_INIT(name) | struct list_head my_list = LIST_HEAD_INIT(my_list); | 定义时初始化(与LIST_HEAD等价) |
c
复制
下载
// 三种方式的等价关系 LIST_HEAD(my_list); // 等价于 struct list_head my_list = LIST_HEAD_INIT(my_list); // 等价于 struct list_head my_list; INIT_LIST_HEAD(&my_list);
八、为什么选择"侵入式"链表?
| 特性 | 传统链表(非侵入式) | Linux 侵入式链表 |
|---|---|---|
| 节点包含数据 | 节点内包含data指针 | 数据包含list_head |
| 类型安全 | 需要void*转换 | 通过list_entry保证类型安全 |
| 代码复用 | 每个类型需要独立实现 | 一套 API 适用于所有类型 |
| 内存分配 | 节点和数据分开分配 | 一次分配,缓存友好 |
| 灵活性 | 较低 | 高(同一数据可在多个链表中) |
九、常见用法模式
模式1:结构体包含多个链表节点
c
复制
下载
struct task_struct { // ... struct list_head tasks; // 在全局任务链表中 struct list_head ptrace; // 在父进程的跟踪链表中 struct list_head run_list; // 在调度器运行队列中 // ... };模式2:遍历并修改
c
复制
下载
// 安全遍历(允许删除) struct list_head *pos, *n; list_for_each_safe(pos, n, &my_list) { struct my_data *entry = list_entry(pos, struct my_data, list); if (entry->id == target_id) { list_del(pos); kfree(entry); } }十、总结
| 问题 | 答案 |
|---|---|
LIST_HEAD是什么? | 定义并初始化链表头的宏 |
| 展开后是什么? | struct list_head name = { &name, &name } |
| 链表头的作用? | 作为双向循环链表的入口 |
| 如何获取包含的结构体? | list_entry(pos, type, member) |
| 遍历用什么? | list_for_each()或list_for_each_safe() |
| 为什么用侵入式? | 通用性强,缓存友好,一套 API 通吃 |
一句话总结:LIST_HEAD是 Linux 内核链表的"起点",它创建一个空的双向循环链表头,配合list_entry和遍历宏,构成了内核中最核心、最优雅的数据结构之一