Linux内核常用接口和宏(一)LIST_HEAD
2026/7/22 16:24:57 网站建设 项目流程

一、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的链表头,它的nextprev指针都指向自己(空链表状态)。


二、内核链表的定义

内核链表不是将链表指针嵌入数据中,而是将链表节点嵌入数据结构中——这是一种"侵入式"链表设计。

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和遍历宏,构成了内核中最核心、最优雅的数据结构之一

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询