目录
一 链表头概述
list_head
是Linux内核中用于实现链表数据结构的基础结构体。在Linux内核编程中,双链表是一种常见的数据结构,用于连接多个数据单元(节点),每个节点都有一个指向其前一个和后一个节点的指针。list_head
结构体定义如下:
struct list_head {
struct list_head *next, *prev;
};
next
和 prev
分别是指向链表中下一个和上一个节点的指针。通过这种方式,可以快速地在链表中插入、删除节点,以及向前或向后遍历链表。
在实际使用时,通常会在其他数据结构中嵌入一个list_head
成员,这样就可以将该数据结构作为一个节点加入到链表中。例如
struct my_struct {
int data;
struct list_head list;
};
struct list_head my_list = LIST_HEAD_INIT(my_list); // 初始化链表头
// 插入节点
struct my_struct node;
node.data = 10;
list_add(&node.list, &my_list);
// 遍历链表
struct my_struct *entry;
list_for_each_entry(entry, &my_list, list) {
printk(KERN_INFO "Data: %d\n", entry->data);
}
在上述代码中,my_struct
结构体包含了 list_head
成员 list
,这样每个 my_struct
实例就能作为一个节点加入到链表 my_list
中。通过 list_for_each_entry
宏,可以遍历链表中的每个节点,并访问节点的 data
成员。
二 链表头使用场景
Linux内核中广泛使用链表头(struct list_head
)作为基础数据结构来组织和管理多种资源和数据。以下是一些常见的使用场景:
-
设备驱动管理:
- 驱动程序经常使用链表来维护设备列表,例如,系统中所有的USB设备、网络接口卡(NIC)等都被组织成链表。
-
文件系统:
- 在VFS(虚拟文件系统)中,inode、dentry(目录项)和其他文件系统元数据结构通常通过链表相互连接,形成文件系统层次结构。
-
进程调度:
- Linux内核使用链表来维护进程调度队列,如运行队列、等待队列等。每个进程结构体
task_struct
中包含一个或多个链表头,用于将进程连接到相应的调度队列。
- Linux内核使用链表来维护进程调度队列,如运行队列、等待队列等。每个进程结构体
-
内存管理:
- 内核的内存管理系统使用链表来管理空闲页框列表、活动页面列表、slab缓存系统中的对象列表等。
-
定时器管理:
- Linux内核的定时器系统中,定时器被组织成链表,以便按超时时间排序和处理。
-
网络协议栈:
- 网络协议栈中,socket、skb(socket缓冲区)以及其他网络相关的数据结构通过链表相连,如TCP连接列表、网络设备的缓冲队列等。
-
模块管理:
- 已加载的内核模块列表通过链表进行管理。
总之,在Linux内核中,几乎在任何需要动态组织和管理一组相似或相关数据的地方,都可以见到链表头结构体的身影。通过灵活的链表操作宏,内核能够高效地处理这些数据结构,无论是添加、删除、遍历还是排序操作。
三 链表头函数/宏概述
Linux内核中关于链表头(struct list_head
)的操作,提供了丰富的宏和函数来创建、初始化、遍历和管理链表。以下是一些常见的宏和函数,以及它们的使用实例:
-
定义和初始化链表头:
#define LIST_HEAD(name) \ struct name { \ struct list_head head; \ } // 创建一个链表头 LIST_HEAD(my_list); // 初始化链表头 INIT_LIST_HEAD(&my_list.head);
-
链表节点的插入和删除:
struct my_struct { int data; struct list_head node; }; struct my_struct element; // 插入节点到链表头 list_add(&element.node, &my_list.head); // 在链表中任意位置插入节点 list_add_tail(&element.node, &another_element.node); // 从链表中删除节点 list_del(&element.node);
-
遍历链表:
struct my_struct *entry; // 遍历整个链表 list_for_each_entry(entry, &my_list.head, node) { printk(KERN_INFO "Data: %d\n", entry->data); } // 反向遍历链表 list_for_each_entry_reverse(entry, &my_list.head, node) { printk(KERN_INFO "Data: %d\n", entry->data); }
-
链表是否为空:
if (list_empty(&my_list.head)) { printk(KERN_INFO "List is empty.\n"); }
-
链表合并:
list_splice_tail(&source_list.head, &my_list.head);
-
链表长度获取:
int list_length = list_length$link(struct list_head *, &my_list.head);
-
链表排序:
list_sort(NULL, &my_list.head, compare_func);
注意:上述函数和宏的具体语法可能根据内核版本有所不同,请查阅对应内核版本的文档或源码以获取准确信息。同时,有些操作如list_sort
需要额外的支持函数(如compare_func
用于排序比较)。
四 链表头使用场景--组织和管理设备实例
在Linux内核设备驱动管理中,链表(尤其是双向循环链表)是非常常见的一种数据结构,用于组织和管理各种设备。以下是一个简化的设备驱动管理中使用链表头的场景举例:
假设有一个简单的设备结构体,它包含设备信息和一个链表头:
struct my_device {
unsigned int id;
char name[32];
struct list_head dev_list;
// 其他设备相关成员...
};
// 初始化链表头
LIST_HEAD(my_devices);
// 定义一个设备结构体实例
struct my_device dev1 = {
.id = 1,
.name = "Device 1",
.dev_list = LIST_HEAD_INIT(dev1.dev_list),
};
// 将设备添加到链表
list_add_tail(&dev1.dev_list, &my_devices);
// 假设有更多的设备...
struct my_device dev2 = {...}, dev3 = {...};
// 将dev2和dev3也添加到链表
list_add_tail(&dev2.dev_list, &my_devices);
list_add_tail(&dev3.dev_list, &my_devices);
// 遍历链表,对每个设备执行操作
struct my_device *curr_dev;
list_for_each_entry(curr_dev, &my_devices, dev_list) {
printk(KERN_INFO "Device ID: %u, Name: %s\n", curr_dev->id, curr_dev->name);
// 对设备执行其他操作...
}
// 在适当时候,可以从链表中删除设备
list_del(&dev1.dev_list);
在这个示例中,my_devices
是一个全局的链表头,每个设备(如dev1
、dev2
和dev3
)都有一个嵌入的struct list_head
类型的成员dev_list
。通过list_add_tail
函数将各个设备添加到链表中,并通过list_for_each_entry
宏遍历链表以访问每个设备。当需要删除设备时,使用list_del
函数从链表中移除相应设备节点。这样,设备驱动程序就可以通过链表有效地管理一组设备实例了。