Linux内核链表(list.h)详细分析
一、内核链表核心数据结构
1. 双向循环链表(struct list_head)
struct list_head {
struct list_head *prev;
struct list_head *next;
};
-
特点:双向循环链表,头节点也指向自己
-
使用方式:嵌入到自定义结构体中
2. 单向链表(struct hlist_head/hlist_node)
struct hlist_head {
struct hlist_node *first;
};
struct hlist_node {
struct hlist_node *next, **pprev;
};
-
特点:单向链表,适用于哈希表,节省空间
-
缺点:无法O(1)访问尾节点
二、核心宏定义
1. container_of 宏
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({ \
const typeof(((type *)0)->member) *__mptr = (ptr); \
(type *)((char *)__mptr - offsetof(type, member)); \
})
-
作用:通过成员指针找到包含它的结构体
-
原理:通过成员偏移量计算结构体首地址
2. list_entry 宏
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
- 作用:简化container_of的使用
三、链表初始化
1. 静态初始化
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
使用:
LIST_HEAD(student_list); // 静态定义并初始化
2. 动态初始化
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
使用:
struct list_head head;
INIT_LIST_HEAD(&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;
}
// 头插
static inline void list_add(struct list_head *new,
struct list_head *head)
{
__list_add(new, head, head->next);
}
// 尾插
static inline void list_add_tail(struct list_head *new,
struct list_head *head)
{
__list_add(new, head->prev, head);
}
2. 删除节点
static inline void __list_del(struct list_head *prev,
struct list_head *next)
{
next->prev = prev;
prev->next = next;
}
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1; // 设置毒药指针
entry->prev = LIST_POISON2;
}
3. 判断链表是否为空
static inline int list_empty(const struct list_head *head)
{
return head->next == head;
}
五、遍历宏
1. 基本遍历(不安全)
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
2. 安全遍历(支持删除)
#define list_for_each_safe(pos, n, head) \
for (pos = (head)->next, n = pos->next; \
pos != (head); \
pos = n, n = pos->next)
3. 遍历结构体(常用)
#define list_for_each_entry(pos, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.next, typeof(*pos), member))
4. 安全遍历结构体
#define list_for_each_entry_safe(pos, n, head, member) \
for (pos = list_entry((head)->next, typeof(*pos), member), \
n = list_entry(pos->member.next, typeof(*pos), member); \
&pos->member != (head); \
pos = n, n = list_entry(n->member.next, typeof(*n), member))
六、哈希链表(hlist)操作
1. 初始化
#define HLIST_HEAD(name) struct hlist_head name = { .first = NULL }
static inline void INIT_HLIST_HEAD(struct hlist_head *h)
{
h->first = NULL;
}
2. 插入节点
static inline void hlist_add_head(struct hlist_node *n,
struct hlist_head *h)
{
struct hlist_node *first = h->first;
n->next = first;
if (first)
first->pprev = &n->next;
h->first = n;
n->pprev = &h->first;
}
3. 遍历宏
#define hlist_for_each_entry(tpos, pos, head, member) \
for (pos = (head)->first; \
pos && ({ prefetch(pos->next); 1; }) && \
({ tpos = hlist_entry(pos, typeof(*tpos), member); 1; }); \
pos = pos->next)
七、实际应用示例
1. 自定义结构体
struct Student {
int id;
char name[50];
struct list_head list; // 嵌入链表节点
};
2. 完整操作流程
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"
LIST_HEAD(student_list); // 全局链表头
// 添加学生
void add_student(int id, const char *name)
{
struct Student *new_stu = malloc(sizeof(struct Student));
new_stu->id = id;
strcpy(new_stu->name, name);
INIT_LIST_HEAD(&new_stu->list);
list_add_tail(&new_stu->list, &student_list);
}
// 显示所有学生
void show_students()
{
struct Student *pos;
list_for_each_entry(pos, &student_list, list) {
printf("ID: %d, Name: %s\n", pos->id, pos->name);
}
}
// 删除学生(安全方式)
void delete_student(int id)
{
struct Student *pos, *n;
list_for_each_entry_safe(pos, n, &student_list, list) {
if (pos->id == id) {
list_del(&pos->list);
free(pos);
return;
}
}
}
// 更新学生信息
void update_student(int id, const char *new_name)
{
struct Student *pos;
list_for_each_entry(pos, &student_list, list) {
if (pos->id == id) {
strcpy(pos->name, new_name);
return;
}
}
}
// 查找学生
struct Student *find_student(int id)
{
struct Student *pos;
list_for_each_entry(pos, &student_list, list) {
if (pos->id == id) {
return pos;
}
}
return NULL;
}
3. 主函数示例
int main()
{
// 初始化链表
INIT_LIST_HEAD(&student_list);
// 添加学生
add_student(1, "Alice");
add_student(2, "Bob");
add_student(3, "Charlie");
// 显示
show_students();
// 更新
update_student(2, "Bobby");
// 查找
struct Student *stu = find_student(2);
if (stu) {
printf("Found: %s\n", stu->name);
}
// 删除
delete_student(1);
// 再次显示
show_students();
return 0;
}
八、自实现内核链表(klist)
1. 核心宏定义
// 计算成员偏移
#define offset(type, mem) ((size_t) &((type*)0)->mem)
// 通过成员指针找到结构体
#define containerof(p, type, mem) ({ \
const typeof(((type*)0)->mem) *_mptr = (p); \
(type*)((char*)_mptr - offset(type, mem)); \
})
// 遍历宏
#define klist_for_each(p, n, head, node) \
for(p = containerof((head)->next, typeof(*p), node), \
n = containerof(p->node.next, typeof(*p), node); \
&p->node != (head); \
p = n, n = containerof(n->node.next, typeof(*n), node))
2. 主要函数
// 初始化
void klist_init(KLIST *l)
{
l->next = l;
l->prev = l;
}
// 添加节点(通用)
static void klist_add(KLIST *new, KLIST *prev, KLIST *next)
{
new->prev = prev;
new->next = next;
prev->next = new;
next->prev = new;
}
// 头插
void klist_add_head(KLIST *new, KLIST *head)
{
klist_add(new, head, head->next);
}
// 尾插
void klist_add_tail(KLIST *new, KLIST *head)
{
klist_add(new, head->prev, head);
}
// 删除
void klist_del(KLIST *node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
node->next = NULL;
node->prev = NULL;
}
九、关键知识点总结
1. 内核链表特点
-
嵌入方式:list_head嵌入到结构体中
-
类型无关:操作与数据类型无关
-
双向循环:头节点也参与循环
-
高性能:使用内联函数减少函数调用开销
2. 容器宏原理
// 假设结构体:
struct Person {
int age;
struct list_head list;
char name[20];
};
// 计算list成员偏移:
// &((struct Person*)0)->list = 4(age占4字节)
// 通过list指针找到Person:
// Person* = list指针 - 4
3. 安全遍历的重要性
-
list_for_each_entry_safe:允许在遍历时删除节点
-
保存下一个节点:防止访问已释放内存
-
应用场景:需要删除匹配节点的遍历
4. 使用技巧
// 1. 定义链表头
static LIST_HEAD(my_list);
// 2. 定义结构体
struct MyData {
int value;
struct list_head list;
};
// 3. 添加节点
struct MyData *data = kmalloc(sizeof(*data), GFP_KERNEL);
data->value = 100;
INIT_LIST_HEAD(&data->list);
list_add_tail(&data->list, &my_list);
// 4. 遍历节点
struct MyData *pos;
list_for_each_entry(pos, &my_list, list) {
printk("Value: %d\n", pos->value);
}
// 5. 删除所有节点
struct MyData *pos, *n;
list_for_each_entry_safe(pos, n, &my_list, list) {
list_del(&pos->list);
kfree(pos);
}
十、常见问题解答
Q1:为什么使用双向循环链表?
-
可以从任意节点开始遍历
-
删除节点时不需要知道前驱节点
-
头节点也参与循环,简化边界条件判断
Q2:hlist和list_head的区别?
-
list_head:双向循环,每个节点有两个指针
-
hlist:单向,头节点只有一个指针,节点有next和**pprev(二级指针),节省空间
Q3:为什么要有list_for_each_safe?
-
普通遍历在删除当前节点后,next指针会失效
-
safe版本保存下一个节点,避免访问无效内存
Q4:LIST_POISON1/2的作用?
-
标识已删除的节点
-
帮助调试,访问已删除节点时会产生段错误