Linux 内核链表是一种高效、灵活的双向循环链表实现,广泛用于内核开发中(如进程调度、设备驱动、文件系统等)。它基于 C 语言的宏和结构体嵌套实现,并提供了一组通用的接口函数,使得链表操作高效且安全。
内核链表
c
vim /usr/src/linux-headers-5.4.0-150-generic/include/linux/list.h
vim /usr/src/linux-headers-5.4.0-150-generic/include/linux/types.h
本质:就是双向循环链表
struct list_head{
struct list_head *next,*prev;
};
这是Linux 内核链表的核心设计,由一个大结构体嵌套着一个小的结构体。拆解:
- 核心数据结构:list_head
Linux 内核链表的基础是一个通用结构体 list_head
,用于表示节点之间的链接关系。
c
struct list_head {
struct list_head *next; // 指向下一个节点
struct list_head *prev; // 指向前一个节点
};
// 每个链表节点都有一个 list_head 类型的成员,用于链接前后节点。
// 通过这种方式实现了双向循环链表。
- 链表头
链表的头节点也是一个 list_head
结构体,但它不存储数据,仅用于标记链表的起点和终点。
- 如果链表为空,
head->next
和head->prev
都指向自己。
1> 内核链表的初始化操作
c
#define LIST_HEAD_INIT(name) { &(name), &(name) }
#define LIST_HEAD(name) \
struct list_head name = LIST_HEAD_INIT(name)
1>第一种初始化
struct list_head name = LIST_HEAD_INIT(name)
struct list_head name = { &(name), &(name) }
/*自己指向自己*/
struct list_head *next = &(name);
struct list_head *prev = &(name);
2>第二种初始化
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}
/*inline :内联函数,可以写在头文件,当调用时,直接将函数中的内容拷贝过去
简单特性,如果你的函数中有复杂的运算和循环结构,inline修饰的函数会退化成普通函数
*/
WRITE_ONCE : 本质与volatile一致
1>声明这个变量很重要,不要把它当成普通变量处理,做出错误的优化
2>保证cpu每次都从内存中读取数据,而不是直接向寄存器取值
2> 内核链表的插入操作
c
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;
}
解说:
汤姆(prev) 杰瑞(new) 白猫(next)
new会插在prev和next之间
static inline void list_add(struct list_head *new, struct list_head *head)
{
__list_add(new, head, head->next);
}
解说: new会插在head和head->next之间,实现头插
static inline void list_add_tail(struct list_head *new, struct list_head *head)
{
__list_add(new, head->prev, head);
}
解说:new会插在head->prev和head之间,实现尾插
3> 内核链表的删除操作
c
static inline void __list_del(struct list_head * prev, struct list_head * next)
{
next->prev = prev;
prev->next = next;
}
解说:使prev和next连接起来
static inline void __list_del_entry(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
}
解说:entry->prev和entry->next连接起来了
entry剪切出去了
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}
#define LIST_POISON1 ((void *) 0x00100100 + 0)
#define LIST_POISON2 ((void *) 0x00200200 + 0)
NULL ===》 0x00000000 ~ 0x08048000
解说:将entry剪切出去,为了安全使两个指针域指向NULL
static inline void list_del_init(struct list_head *entry)
{
__list_del_entry(entry);
INIT_LIST_HEAD(entry);
}
解说:将entry剪切出去,为了安全使两个指针域指向自己
4> 内核链表的替换操作
c
static inline void list_replace(struct list_head *old,
struct list_head *new)
{
new->next = old->next;
new->next->prev = new;
new->prev = old->prev;
new->prev->next = new;
}
解说:new结点取代old结点
static inline void list_replace_init(struct list_head *old,
struct list_head *new)
{
list_replace(old, new);
INIT_LIST_HEAD(old); //初始化,自己指向自己
}
解说:new结点取代old结点,让old结点的prev和next指向自己
5> 内核链表的移动操作
c
static inline void list_move(struct list_head *list, struct list_head *head)
{
__list_del_entry(list);
list_add(list, head);
}
解说:将list剪切出来
进行头插
static inline void list_move_tail(struct list_head *list,
struct list_head *head)
{
__list_del_entry(list);
list_add_tail(list, head);
}
解说:将list剪切出来
进行尾插
6> 内核链表的遍历操作
c
/**
* list_entry - get the struct for this entry
* @ptr: the &struct list_head pointer.
* @type: the type of the struct this is embedded in.
* @member: the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)
分析:
功能:通过小结构体的地址得到大结构体的地址
ptr : 小结构体的地址
type : 大结构体的类型
member: 大结构体中小结构体的名字
container_of(ptr, type, member)
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr - offsetof(type,member) );})
container_of做以下两个操作:
操作一://(保存小结构体的地址)
1> ((type *)0)->member ==》list(指向大结构体的小结构体成员)
2>typeof() --> 取类型
例子: typeof(10) --> int
3>typeof( ((type *)0)->member ) ==> typeof( list )
===>struct list_head
const struct list_head *__mptr = (ptr);
操作二://(求出大结构体的地址)
(type *)( (char *)__mptr - offsetof(type,member) );
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
( (char *)__mptr - ((size_t) &((TYPE *)0)->MEMBER );
(char *)__mptr : 对小结构体的地址进行char *转 ,让其地址偏移量为1字节
((size_t) &((TYPE *)0)->MEMBER ://计算小结构体到大结构体的偏移量
(size_t) == int
&((TYPE *)0)->MEMBER :
(TYPE *)0 :指向大结构体的地址0x00000000
((TYPE *)0)->MEMBER : list
&list //偏移量的体现
c
#define list_for_each(pos, head) \
for (pos = (head)->next; pos != (head); pos = pos->next)
解说:
pos:用于循环的中间变量,类似与for循环中i(小结构指针)
head :头结点的小结构指针类型
正序遍历
#define list_for_each_prev(pos, head) \
for (pos = (head)->prev; pos != (head); pos = pos->prev)
解说:
pos:用于循环的中间变量,类似与for循环中i(小结构指针)
head :头结点的小结构指针类型
逆序遍历
#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))
解说:
pos:用于循环的中间变量,类似于for的i(大结构指针)
head:头结点的小结构体指针类型
member:大结构体中小结构体的名字
正序遍历
#define list_for_each_entry_reverse(pos, head, member) \
for (pos = list_entry((head)->prev, typeof(*pos), member); \
&pos->member != (head); \
pos = list_entry(pos->member.prev, typeof(*pos), member))
解说:
pos:用于循环的中间变量,类似于for的i(大结构指针)
head:头结点的小结构体指针类型
member:大结构体中小结构体的名字
逆序遍历
7> 内核链表的删除函数
c
void link_del(link_t *p,datatype data)
{
link_t *pos = NULL; //类似for循环的i
link_t *node = NULL;
//1>遍历链表
/*#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))*/
list_for_each_entry(pos, &p->list, list)
{
//数据对比
if(pos->data == data)
{
//中间变量
node = pos;
//超秀操作 获得要删除结点前一个结点的地址
pos = list_entry(pos->list.prev, typeof(*pos), list);
//删除与释放
list_del_init(&node->list);
free(node);
}
}
}
8> 内核链表的替换操作
c
void link_replace(link_t *p,datatype old,datatype new)
{
link_t *pos = NULL; //类似for循环的i
link_t *new_node = NULL; //类似for循环的i
//1>遍历
list_for_each_entry(pos, &p->list, list)
{
//数据对比
if(pos->data == old)
{
//新创建一个结点
new_node = create_node(new);
if(NULL == new_node)
{
perror("create_node");
return;
}
//替换结点
list_replace_init(&pos->list,&new_node->list);
//释放旧结点
free(pos);
//将pos指向新的替换结点的地址
pos = new_node;
}
}
}
Linux 内核链表的使用方法
- 定义链表和节点:
定义链表头:
c
LIST_HEAD(my_list);
定义节点结构体:
c
struct my_struct {
int data;
struct list_head list; // 嵌入链表节点
};
- 添加节点:
c
#include <stdio.h>
#include <stdlib.h>
#include "list.h" // 假设链表宏定义和函数在list.h中
struct my_struct {
int data;
struct list_head list;
};
int main() {
LIST_HEAD(my_list); // 初始化链表头
// 创建并添加节点
for (int i = 0; i < 5; i++) {
struct my_struct *new_node = (struct my_struct *)malloc(sizeof(struct my_struct));
new_node->data = i;
list_add_tail(&new_node->list, &my_list); // 添加到链表尾部
}
// 遍历链表
struct list_head *pos;
struct my_struct *entry;
list_for_each(pos, &my_list) {
entry = list_entry(pos, struct my_struct, list);
printf("Data: %d\n", entry->data);
}
return 0;
}
- 删除节点:
c
// 删除所有节点
struct list_head *pos, *n;
list_for_each_safe(pos, n, &my_list) {
struct my_struct *entry = list_entry(pos, struct my_struct, list);
list_del(pos); // 从链表中删除
free(entry); // 释放内存
}
- 适配更多场景:
通过 list_for_each_entry 宏直接遍历包含链表节点的结构体:
c
#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))
Linux 内核链表的优点
内核链表具有通用性:list_head
和相关宏定义可以适配任何结构体,无需额外定义链表节点结构。高效性:插入、删除操作时间复杂度为 O(1)。灵活性:支持双向、循环、动态扩展等多种操作。内存安全:提供了防止悬空指针和错误访问的机制。
我们以一个模拟的任务调度系统为例,演示如何利用Linux 内核链表管理多个任务。这个例子演示了链表更为直观的实际应用场景:
任务调度场景描述
假设我们需要管理多个任务,每个任务有以下信息:
- 任务 ID:唯一标识任务。
- 任务优先级:用于决定任务的执行顺序(高优先级任务优先执行)。
- 任务状态:表示任务是运行中、等待中还是完成。
通过内核链表,我们可以实现如下功能:
- 动态插入新任务。
- 根据条件删除任务(例如任务完成)。
- 按优先级遍历任务列表。
- 模拟任务的调度行为。
1> 数据结构定义
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 内核链表头文件(假设 `list.h` 中定义了 Linux 链表的相关宏和函数)
#include "list.h"
// 定义任务结构体
struct task {
int task_id; // 任务 ID
int priority; // 任务优先级
char status[20]; // 任务状态(running, waiting, completed)
struct list_head list; // 链表节点,用于将任务加入链表
};
2> 初始化链表
c
// 定义链表头,用于管理任务列表
LIST_HEAD(task_list);
3> 添加任务
我们将任务按优先级插入链表中,确保链表始终按优先级从高到低排列。
c
// 添加任务到链表,按优先级插入
void add_task(int task_id, int priority, const char *status) {
// 创建新任务节点
struct task *new_task = (struct task *)malloc(sizeof(struct task));
new_task->task_id = task_id;
new_task->priority = priority;
strcpy(new_task->status, status);
// 遍历链表,找到插入位置
struct list_head *pos;
struct task *entry;
list_for_each(pos, &task_list) {
entry = list_entry(pos, struct task, list);
if (priority > entry->priority) {
// 在优先级较低的任务之前插入
list_add_tail(&new_task->list, pos->prev);
return;
}
}
// 如果链表为空或优先级最低,插入到链表末尾
list_add_tail(&new_task->list, &task_list);
}
4> 删除任务
我们可以根据任务 ID 或任务状态删除任务,例如删除所有状态为 "completed" 的任务。
c
// 根据任务 ID 删除任务
void delete_task_by_id(int task_id) {
struct list_head *pos, *n;
struct task *entry;
// 遍历链表找到目标任务
list_for_each_safe(pos, n, &task_list) {
entry = list_entry(pos, struct task, list);
if (entry->task_id == task_id) {
list_del(pos); // 从链表中删除节点
free(entry); // 释放内存
printf("Task %d deleted.\n", task_id);
return;
}
}
printf("Task %d not found.\n", task_id);
}
// 删除所有完成的任务
void delete_completed_tasks() {
struct list_head *pos, *n;
struct task *entry;
list_for_each_safe(pos, n, &task_list) {
entry = list_entry(pos, struct task, list);
if (strcmp(entry->status, "completed") == 0) {
list_del(pos);
free(entry);
printf("Completed task deleted.\n");
}
}
}
5> 遍历任务
我们可以按链表顺序(即优先级顺序)遍历所有任务,并打印任务信息。
c
// 打印任务列表
void print_tasks() {
struct list_head *pos;
struct task *entry;
printf("Task List:\n");
printf("ID Priority Status\n");
printf("=============================\n");
list_for_each(pos, &task_list) {
entry = list_entry(pos, struct task, list);
printf("%-5d %-10d %-10s\n", entry->task_id, entry->priority, entry->status);
}
printf("\n");
}
6> 模拟任务调度
我们可以通过修改任务状态来模拟任务的调度行为。
c
// 模拟任务调度
void simulate_task_execution() {
struct list_head *pos;
struct task *entry;
list_for_each(pos, &task_list) {
entry = list_entry(pos, struct task, list);
if (strcmp(entry->status, "waiting") == 0) {
// 将第一个等待中的任务标记为运行中
strcpy(entry->status, "running");
printf("Task %d is now running.\n", entry->task_id);
break;
}
}
}
7> 主函数(main.c)
c
int main() {
// 初始化任务列表
add_task(1, 10, "waiting");
add_task(2, 20, "waiting");
add_task(3, 15, "waiting");
add_task(4, 5, "waiting");
print_tasks(); // 打印初始任务列表
// 模拟任务调度
printf("Simulating task execution:\n");
simulate_task_execution();
print_tasks();
// 删除完成的任务(目前没有完成任务)
delete_completed_tasks();
// 删除任务 ID 为 3 的任务
printf("Deleting task with ID 3:\n");
delete_task_by_id(3);
print_tasks();
// 添加新任务
printf("Adding new task:\n");
add_task(5, 25, "waiting");
print_tasks();
return 0;
}
运行结果示例
plaintext
Task List:
ID Priority Status
=============================
2 20 waiting
3 15 waiting
1 10 waiting
4 5 waiting
Simulating task execution:
Task 2 is now running.
Task List:
ID Priority Status
=============================
2 20 running
3 15 waiting
1 10 waiting
4 5 waiting
Deleting task with ID 3:
Task 3 deleted.
Task List:
ID Priority Status
=============================
2 20 running
1 10 waiting
4 5 waiting
Adding new task:
Task List:
ID Priority Status
=============================
5 25 waiting
2 20 running
1 10 waiting
4 5 waiting
代码拆解分析:
- 动态优先级插入:任务根据优先级动态插入到链表的适当位置,确保链表始终按优先级从高到低排序。
- 链表操作灵活性 :
- 使用
list_add_tail()
和list_del()
等操作,轻松实现插入、删除等功能。 list_for_each_safe()
确保在遍历过程中安全删除节点。
- 使用
- 模拟调度 :通过修改任务状态(如将
waiting
改为running
),模拟任务的执行。 - 代码扩展性:可以轻松扩展其他功能,如暂停任务、重新调整优先级等。
这里先为大家介绍了Linux中内核链表的完整写法,然后用例子展示了 Linux 内核链表在动态任务管理中的应用,在实际开发中,Linux 内核链表广泛用于管理类似的动态数据结构,例如网络数据包队列、设备驱动请求队列等场景。内核链表具有很多好处:
- 动态优先级插入和排序。
- 高效的插入、删除、遍历操作。
- 扩展性强,适合复杂任务调度场景。
综上。Linux 内核链表利用 C 语言的灵活性和宏定义实现了简洁、高效的双向循环链表。它的核心设计特点包括:
- 基于
list_head
实现通用双向循环链表。 - 提供了一组操作宏和辅助函数,简化链表操作。
- 通过结构体嵌套和
container_of
实现灵活的节点管理。
掌握 Linux 内核链表的原理和使用方法,不仅有助于理解内核开发,还能在其他 C 语言项目中借鉴实现高效的数据结构。
综上。希望该内容能对你有帮助,感谢!
以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。
我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!