Linux 内核 list 是内核开发中广泛使用的一种双向链表实现,它为内核中的数据结构管理提供了高效、灵活的解决方案,与标准 C 库中的链表不同,Linux 内核 list 通过嵌入式的节点设计,允许任意结构体无缝接入链表操作,这种设计充分体现了内核代码的简洁性与高效性。

Linux 内核 list 的核心设计理念
Linux 内核 list 的核心在于其“侵入式”设计理念,传统的链表节点通常包含数据指针和前后指针,而内核 list 将链表节点直接嵌入到数据结构体中。
struct my_data {
int id;
char name[32];
struct list_head list; // 嵌入的链表节点
};
这种设计的优势在于:
- 内存效率:无需为链表节点单独分配内存,减少了内存碎片。
- 操作便捷:通过
container_of宏,可以从链表节点指针反向回溯到父结构体指针。 - 通用性:同一数据结构可以同时加入多个不同类型的链表,只需嵌入多个
struct list_head成员。
关键数据结构与宏定义
内核链表的核心数据结构是 struct list_head,定义在 <linux/list.h> 中:
struct list_head {
struct list_head *next, *prev;
};
该结构仅包含两个指针,分别指向下一个和上一个节点,形成双向循环链表,内核提供了一系列宏和函数来简化链表操作:
| 宏/函数 | 功能描述 |
|---|---|
LIST_HEAD(name) |
静态初始化一个链表头 |
INIT_LIST_HEAD(list) |
动态初始化链表头 |
list_add(new, head) |
在链表头后添加新节点 |
list_add_tail(new, head) |
在链表尾前添加新节点 |
list_del(entry) |
删除指定节点 |
list_for_each(pos, head) |
遍历链表 |
list_for_each_entry(pos, head, member) |
遍历链表并获取父结构体指针 |
链表的基本操作流程
初始化链表
静态初始化:

static LIST_HEAD(my_list);
动态初始化:
struct list_head my_list; INIT_LIST_HEAD(&my_list);
添加节点
struct my_data *data1 = kmalloc(sizeof(*data1), GFP_KERNEL); INIT_LIST_HEAD(&data1->list); list_add(&data1->list, &my_list); // 添加到链表头部 struct my_data *data2 = kmalloc(sizeof(*data2), GFP_KERNEL); INIT_LIST_HEAD(&data2->list); list_add_tail(&data2->list, &my_list); // 添加到链表尾部
遍历链表
使用 list_for_each_entry 宏是最常见的遍历方式:
struct my_data *pos;
list_for_each_entry(pos, &my_list, list) {
printk("ID: %d, Name: %s\n", pos->id, pos->name);
}
删除节点
struct my_data *pos;
list_for_each_entry(pos, &my_list, list) {
if (pos->id == target_id) {
list_del(&pos->list); // 从链表中删除
kfree(pos); // 释放内存
break;
}
}
高级特性与注意事项
安全遍历与修改
在遍历链表时修改链表(如删除节点)可能导致遍历失效,内核提供了 list_for_each_entry_safe 宏,通过保存下一个节点的指针确保遍历安全:
struct my_data *pos, *n;
list_for_each_entry_safe(pos, n, &my_list, list) {
if (pos->id == target_id) {
list_del(&pos->list);
kfree(pos);
}
}
链表排序
内核提供了 list_sort 函数(需包含 <linux/list_sort.h>),可对链表进行自定义排序:
int compare_func(void *priv, struct list_head *a, struct list_head *b) {
struct my_data *da = container_of(a, struct my_data, list);
struct my_data *db = container_of(b, struct my_data, list);
return da->id - db->id;
}
list_sort(NULL, &my_list, compare_func);
性能优化
- 批量操作:
list_splice可将一个链表合并到另一个链表,适用于高效数据迁移。 - RCU 保护:在并发场景下,可通过
list_for_each_entry_rcu结合 RCU 机制实现无锁遍历。
实际应用场景
Linux 内核 list 被广泛应用于子系统管理,

- 进程管理:
task_struct中的tasks链表维护所有进程。 - 设备驱动:
pci_dev通过链表管理 PCI 设备。 - 文件系统:VFS 的 inode 链表管理文件节点。
- 网络子系统:
sk_buff结构通过链表管理网络数据包。
以设备驱动为例,PCI 子系统使用链表管理所有 PCI 设备:
struct pci_dev {
struct list_head global_list; // 全局设备链表
struct list_head bus_list; // 总线设备链表
// 其他成员...
};
与其他链表实现的对比
| 特性 | Linux 内核 list | 标准链表(如 glib) |
|---|---|---|
| 设计模式 | 侵入式 | 非侵入式 |
| 内存开销 | 低(无额外节点) | 高(需独立节点) |
| 遍历效率 | 高(直接访问结构体) | 中(需间接访问) |
| 并发安全 | 需手动加锁 | 部分实现内置锁 |
| 适用场景 | 内核高性能场景 | 用户态通用场景 |
最佳实践与常见陷阱
- 初始化一致性:确保链表头正确初始化,未初始化的链表头会导致遍历越界。
- 内存管理:删除节点后及时释放内存,避免内存泄漏。
- 并发保护:在多线程/中断上下文中操作链表时,必须使用自旋锁或互斥锁保护。
DEFINE_SPINLOCK(list_lock); spin_lock(&list_lock); list_add(&new_node->list, &my_list); spin_unlock(&list_lock);
- 宏的使用:避免直接操作
next和prev指针,始终使用内核提供的宏,确保代码可维护性。
Linux 内核 list 通过其精巧的设计和高效的实现,成为内核开发中不可或缺的工具,理解其工作原理和最佳实践,不仅能提升内核代码的质量,还能为系统级编程打下坚实基础,无论是设备驱动开发还是子系统设计,掌握内核链表都是迈向高级内核开发的关键一步。




















