一、简介
Linux 内核使用了一种 双向循环链表 作为基础的数据结构,主要用于管理各种内核对象,如进程、任务、设备等。它的实现位于 include/linux/list.h
头文件中。
二、链表结构定义
struct list_head {struct list_head *next, *prev;
};
next
指向下一个节点prev
指向上一个节点- 由于是 双向循环链表,最后一个节点的
next
指向头节点,头节点的prev
指向最后一个节点
三、初始化链表
#define LIST_HEAD_INIT(name) { &(name), &(name) }static inline void INIT_LIST_HEAD(struct list_head *list)
{WRITE_ONCE(list->next, list);list->prev = list;
}
前者直接传入链表变量,然后返回给变量。后者要先定义链表变量,然后再传入。
示例:
//第一种方式
struct list_head my_list = LIST_HEAD_INIT(my_list);//第二种方式
struct list_head my_list;
INIT_LIST_HEAD(&my_list);
四、链表操作
假设我们有一个包含 list_head
结构的自定义结构:
struct my_struct {int data;struct list_head list;
};
4.1. 插入节点
/*
** new: 要插入的节点
** head: 插入的节点位于该节点后面
*/
void list_add(struct list_head *new, struct list_head *head)
/*
** new: 要插入的节点
** head: 插入的节点位于该节点前面
** 注意: 千万别被 list_add_tail中的 tail 误导了,它只有 head 是链表头时,才是在链表尾部插入,否则它是插入 head 的前面。
** 但是当你用双向循环链表的方式去理解的,其实它就是在head前面插入。
*/
void list_add_tail(struct list_head *new, struct list_head *head)
头部插入
struct list_head my_list = LIST_HEAD_INIT(my_list);
list_add(&new_node->list, &my_list);
list_add()
在my_list
之后 插入新节点my_list
仍然是头节点,链表顺序:my_list → new_node → …
尾部插入
list_add_tail(&new_node->list, &my_list);
list_add_tail()
在my_list
之前 插入新节点my_list
仍然是头节点,链表顺序:my_list → … → new_node
4.2. 删除节点
list_del(&node->list);
- 只从链表中移除节点,但不释放内存
- 需要
kfree()
释放节点内存(如果是动态分配的)
4.3. 遍历链表
/*
** pos: 用于遍历的临时指针,指向包含list_head的自定义结构体
** head: 链表头指针,类型为struct list_head *
** member: 自定义结构体中list_head的成员名
*/
#define list_for_each_entry(pos, head, member)/*
** pos: 用于遍历的临时指针,指向包含list_head的自定义结构体
** n: 临时保存下一个节点,用于遍历时避免因删除而出错
** head: 链表头指针
** member: 自定义结构体中list_head的成员名
*/
#define list_for_each_entry_safe(pos, n, head, member)
普通遍历:
struct my_struct {int data;struct list_head list;
};struct list_head my_list;
INIT_LIST_HEAD(&my_list);// 遍历
struct my_struct *pos;
list_for_each_entry(pos, &my_list, list) {printk(KERN_INFO "Data = %d\n", pos->data);
}
安全遍历(删除时使用)
struct my_struct *pos, *n;
list_for_each_entry_safe(pos, n, &my_list, list) {if (pos->data == 123) {list_del(&pos->list); // 安全删除当前节点kfree(pos);}
}
如果经常操作内核链表,建议默认使用 *_safe
版本,防止一些野指针和崩溃问题。
4.4. 判断链表是否为空
if (list_empty(&my_list)) {printk(KERN_INFO "List is empty\n");
}
4.5. 获取结构体指针
struct my_struct *entry = list_entry(ptr, struct my_struct, list);
ptr
是list_head
指针struct my_struct
是目标结构体list
是my_struct
中的list_head
成员名称
五、总结
- 通常我们要自定义一个结构体,然后结构体中包含
struct list_head
。list_head
本质上就是链表中的一个节点,而且是一个 通用节点结构,用来把自己的结构体组织成链表。可以把struct list_head
理解为“钩子”,用来将节点一个个挂到链表上。 - 链表的头(head) 是一个
struct list_head
,不代表任何实际数据。
&my_list --> item1->list <--> item2->list <--> item3->list▲ ▲ ▲│ │ │item1 item2 item3(struct my_struct) (struct my_struct) (struct my_struct)
六、链表示例
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/list.h>struct my_node {int data;struct list_head list; // 链表节点
};static LIST_HEAD(my_list); // 定义并初始化链表头// 模块加载时执行
static int __init list_demo_init(void)
{int i;struct my_node *node;printk(KERN_INFO "list Demo module init!\n");// 添加 5 个节点for (i = 0; i < 5; i++) {node = kmalloc(sizeof(*node), GFP_KERNEL);if (!node)return -ENOMEM;node->data = i;list_add_tail(&node->list, &my_list); // 添加到链表尾部printk(KERN_INFO "add node:data = %d\n", node->data);}// 遍历链表printk(KERN_INFO "iterator list:\n");list_for_each_entry(node, &my_list, list) {printk(KERN_INFO "node data = %d\n", node->data);}return 0;
}// 模块卸载时执行
static void __exit list_demo_exit(void)
{struct my_node *node, *tmp;printk(KERN_INFO "list Demo module exit...\n");list_for_each_entry_safe(node, tmp, &my_list, list) {printk(KERN_INFO "delete node:data = %d\n", node->data);list_del(&node->list); // 从链表中删除kfree(node); // 释放内存}
}module_init(list_demo_init);
module_exit(list_demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yang");
MODULE_DESCRIPTION("a list demo");