【数据结构】链表(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 语言项目中借鉴实现高效的数据结构。

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

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

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

相关推荐
Gu_shiwww20 小时前
数据结构8——双向链表
c语言·数据结构·python·链表·小白初步
matlab的学徒20 小时前
Web与Nginx网站服务(改)
linux·运维·前端·nginx·tomcat
Insist75320 小时前
prometheus安装部署与alertmanager邮箱告警
linux·运维·grafana·prometheus
BAGAE21 小时前
MODBUS 通信协议详细介绍
linux·嵌入式硬件·物联网·硬件架构·iot·嵌入式实时数据库·rtdbs
灿烂阳光g21 小时前
SELinux 策略文件编写
android·linux
xqlily21 小时前
Linux操作系统之Ubuntu
linux·运维·ubuntu
阿部多瑞 ABU21 小时前
《基于国产Linux的机房终端安全重构方案》
linux·安全
苏小瀚1 天前
[数据结构] 排序
数据结构
倔强的石头1061 天前
【Linux指南】Makefile入门:从概念到基础语法
linux·运维·服务器
风_峰1 天前
Petalinux相关配置——ZYNQ通过eMMC启动
嵌入式硬件·ubuntu·fpga开发