【数据结构】链表(3):Linux 的内核链表

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 内核链表的核心设计,由一个大结构体嵌套着一个小的结构体。拆解:

  1. 核心数据结构:list_head

Linux 内核链表的基础是一个通用结构体 list_head,用于表示节点之间的链接关系。

c 复制代码
struct list_head {
    struct list_head *next; // 指向下一个节点
    struct list_head *prev; // 指向前一个节点
};
// 每个链表节点都有一个 list_head 类型的成员,用于链接前后节点。
// 通过这种方式实现了双向循环链表。
  1. 链表头

链表的头节点也是一个 list_head 结构体,但它不存储数据,仅用于标记链表的起点和终点。

  • 如果链表为空,head->nexthead->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 内核链表的使用方法
  1. 定义链表和节点:

定义链表头:

c 复制代码
LIST_HEAD(my_list);

定义节点结构体:

c 复制代码
struct my_struct {
    int data;
    struct list_head list; // 嵌入链表节点
};
  1. 添加节点:
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;
}
  1. 删除节点:
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);   // 释放内存
}
  1. 适配更多场景:

通过 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 内核链表管理多个任务。这个例子演示了链表更为直观的实际应用场景:

任务调度场景描述

假设我们需要管理多个任务,每个任务有以下信息:

  1. 任务 ID:唯一标识任务。
  2. 任务优先级:用于决定任务的执行顺序(高优先级任务优先执行)。
  3. 任务状态:表示任务是运行中、等待中还是完成。

通过内核链表,我们可以实现如下功能:

  1. 动态插入新任务。
  2. 根据条件删除任务(例如任务完成)。
  3. 按优先级遍历任务列表。
  4. 模拟任务的调度行为。
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 内核链表广泛用于管理类似的动态数据结构,例如网络数据包队列、设备驱动请求队列等场景。内核链表具有很多好处:

  1. 动态优先级插入和排序。
  2. 高效的插入、删除、遍历操作。
  3. 扩展性强,适合复杂任务调度场景。

综上。Linux 内核链表利用 C 语言的灵活性和宏定义实现了简洁、高效的双向循环链表。它的核心设计特点包括:

  1. 基于 list_head 实现通用双向循环链表。
  2. 提供了一组操作宏和辅助函数,简化链表操作。
  3. 通过结构体嵌套和 container_of 实现灵活的节点管理。

掌握 Linux 内核链表的原理和使用方法,不仅有助于理解内核开发,还能在其他 C 语言项目中借鉴实现高效的数据结构。

综上。希望该内容能对你有帮助,感谢!

以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!

相关推荐
叶 落几秒前
Ubuntu 下载安装 Consul1.17.1
java·服务器·ubuntu·中间件·consul·配置中心
sjsjs112 分钟前
【数据结构-堆】力扣2530. 执行 K 次操作后的最大分数
数据结构·算法·leetcode
Bruce_Liuxiaowei8 分钟前
结合 nc 工具利用笑脸漏洞(Smile Bug)攻击 Metasploitable2 Linux
linux·运维·nc·笑脸漏洞
星迹日21 分钟前
数据结构:ArrayList与顺序表
java·数据结构·经验分享·笔记·顺序表
終不似少年遊*29 分钟前
数据结构之线性表
数据结构·笔记·python·算法·线性表
高兴蛋炒饭1 小时前
Nginx的介绍以及配置使用
linux·服务器·nginx
qiu_shi_1 小时前
1.2.1-2部分数据结构的说明02_链表
数据结构
Dusk_橙子2 小时前
在Linux中,如何配置负载均衡器以分配网络流量?
linux·运维·负载均衡
Lang_xi_2 小时前
Linux中的tty和pts概念和区别
linux·运维·服务器
huaweichenai3 小时前
Linux下实现磁盘挂载
linux·运维·服务器