下面我用内核源码级 + 图解思维 + 面试/实战导向,系统地给你讲清楚
👉 Linux 内核双向链表(list_head)。
这是内核、驱动、操作系统面试必考点之一。
一、为什么 Linux 内核要自己实现双向链表?
内核不能使用 C 标准库,必须:
- 🚫 不能
malloc/free - 🚫 不能用 STL / glib
- ✅ 高性能
- ✅ 强类型控制
- ✅ 通用、可复用
👉 所以内核设计了 侵入式双向链表(intrusive list)
二、核心数据结构:struct list_head
📁 源码位置:
include/linux/list.h
1️⃣ 定义
struct list_head {
struct list_head *next, *prev;
};
⚠️ 注意:
- 链表节点不保存数据
- 数据结构中嵌入 list_head
三、侵入式链表(重点理解)
1️⃣ 普通链表(用户态)
struct node {
int data;
struct node *next;
};
2️⃣ 内核侵入式链表
struct task_struct {
int pid;
struct list_head tasks;
};
📌 链表指针嵌入到业务结构中
四、链表初始化(非常重要)
1️⃣ 静态初始化
LIST_HEAD(my_list);
等价于:
struct list_head my_list = { &my_list, &my_list };
2️⃣ 动态初始化
struct list_head my_list;
INIT_LIST_HEAD(&my_list);
📌 空链表:head.next == head.prev == &head
五、节点插入原理(源码级)
1️⃣ 内部核心函数
static inline void __list_add(
struct list_head *new,
struct list_head *prev,
struct list_head *next)
{
next->prev = new;
new->next = next;
new->prev = prev;
prev->next = new;
}
2️⃣ 头插 & 尾插
list_add(new, head); // 头插
list_add_tail(new, head); // 尾插
🔍 本质:
list_add(new, head) => __list_add(new, head, head->next)
list_add_tail(new, head) => __list_add(new, head->prev, head)
六、节点删除(极易考)
static inline void __list_del(
struct list_head * prev,
struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
list_del(entry);
⚠️ 注意:
list_del不释放内存- 只是摘链
👉 驱动中必须配合 kfree()
七、container_of(灵魂级宏)
1️⃣ 宏定义
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})
📌 作用:
通过
list_head*找回外层结构体地址
2️⃣ 使用示例
struct task_struct *task;
task = list_entry(pos, struct task_struct, tasks);
本质:
container_of(pos, struct task_struct, tasks);
八、遍历链表(面试高频)
1️⃣ 普通遍历
struct list_head *pos;
list_for_each(pos, &my_list) {
struct node *n = list_entry(pos, struct node, list);
}
2️⃣ 安全遍历(删除时必须用)
struct list_head *pos, *n;
list_for_each_safe(pos, n, &my_list) {
list_del(pos);
}
📌 safe = 先保存 next
3️⃣ 直接遍历结构体(推荐)
struct node *p;
list_for_each_entry(p, &my_list, list) {
printk("%d\n", p->data);
}
九、完整示例(驱动 / 内核风格)
struct my_node {
int data;
struct list_head list;
};
LIST_HEAD(my_list);
struct my_node *n1 = kmalloc(sizeof(*n1), GFP_KERNEL);
n1->data = 10;
INIT_LIST_HEAD(&n1->list);
list_add(&n1->list, &my_list);
struct my_node *pos;
list_for_each_entry(pos, &my_list, list) {
printk("%d\n", pos->data);
}
十、Linux 链表的设计优势(必答)
| 优点 | 说明 |
|---|---|
| O(1) 插删 | 双向 |
| 零额外开销 | 无节点结构 |
| 高复用 | 任意结构 |
| cache 友好 | 嵌入式 |
十一、常见坑(面试会问)
❌ 忘记初始化
list_add(new, head); // 野指针
❌ 删除时普通遍历
list_for_each(pos, head) {
list_del(pos); // 崩
}
❌ 删除后继续使用
list_del(entry);
// entry->next 可能已失效
十二、一句话终极总结(背)
Linux 内核链表是侵入式双向循环链表,
用list_head组织结构,
用container_of找回数据,
用宏保证 O(1) 与高性能。