1.内核通用链表

一、什么是 list_head

list_head 是 Linux 内核中自己实现的一种 双向循环链表 的结构,定义在 <linux/list.h> 中。它设计得非常轻巧、灵活,广泛用于内核模块、驱动、进程调度、网络协议栈等。

它的关键思想是:

将链表结构嵌入到你的数据结构中,从而实现通用链表操作。


 二、结构定义
struct list_head {struct list_head *next, *prev;
};

每一个 list_head 实际就是两个指针:指向下一个节点和上一个节点。


 三、如何使用

假设你有一个自定义结构体:

struct student {char name[20];int age;struct list_head list;  // 嵌入 list_head
};

这样,每个 student 节点就能通过 list 成员串联成一个链表。


四、常用操作宏(定义在 list.h)
宏 / 函数作用
LIST_HEAD(name)定义并初始化链表头
INIT_LIST_HEAD(ptr)初始化某个节点(通常用于结构体内嵌)
list_add(new, head)添加到链表头部
list_add_tail(new, head)添加到链表尾部
list_del(entry)删除节点
list_empty(head)判断链表是否为空
list_for_each(pos, head)遍历链表(不获取结构体)
list_for_each_entry(ptr, head, member)遍历链表(获取结构体指针)
list_for_each_entry_safe(...)安全遍历(可删除)


五、链表是循环的

链表头的 next 指向第一个节点,prev 指向最后一个节点;尾节点的 next 又指向头部。这是一种 循环双向链表

七、完整例子

不依赖内核的用户态实现(适合你直接编译)

用户空间简化版 list.h

先把这段保存为 list.h

#ifndef _USER_LIST_H
#define _USER_LIST_H#include <stddef.h>struct list_head {struct list_head *next, *prev;
};#define LIST_HEAD_INIT(name) { &(name), &(name) }#define LIST_HEAD(name) \struct list_head name = LIST_HEAD_INIT(name)static inline void INIT_LIST_HEAD(struct list_head *list)
{list->next = list;list->prev = list;
}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;
}static inline void list_add(struct list_head *new, struct list_head *head)
{__list_add(new, head, head->next);
}static inline void list_add_tail(struct list_head *new, struct list_head *head)
{__list_add(new, head->prev, head);
}static inline void __list_del(struct list_head * prev, struct list_head * next)
{next->prev = prev;prev->next = next;
}static inline void list_del(struct list_head *entry)
{__list_del(entry->prev, entry->next);entry->next = entry->prev = NULL;
}static inline int list_empty(const struct list_head *head)
{return head->next == head;
}#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)#define container_of(ptr, type, member) ({                      \const typeof( ((type *)0)->member ) *__mptr = (ptr);        \(type *)( (char *)__mptr - offsetof(type, member) );})#define list_entry(ptr, type, member) \container_of(ptr, type, member)#define list_for_each(pos, head) \for (pos = (head)->next; pos != (head); pos = pos->next)#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))#define list_for_each_entry_safe(pos, n, head, member)                 \for (pos = list_entry((head)->next, typeof(*pos), member),         \n = list_entry(pos->member.next, typeof(*pos), member);       \&pos->member != (head);                                       \pos = n, n = list_entry(n->member.next, typeof(*n), member))#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "list.h"struct student {char name[20];int age;struct list_head list;
};LIST_HEAD(student_list);void add_student(const char *name, int age) {struct student *stu = malloc(sizeof(*stu));strcpy(stu->name, name);stu->age = age;INIT_LIST_HEAD(&stu->list);list_add_tail(&stu->list, &student_list);
}void show_students() {struct student *stu;list_for_each_entry(stu, &student_list, list) {printf("Name: %s, Age: %d\n", stu->name, stu->age);}
}void cleanup() {struct student *stu, *tmp;list_for_each_entry_safe(stu, tmp, &student_list, list) {list_del(&stu->list);free(stu);}
}int main() {add_student("Tom", 18);add_student("Jerry", 19);add_student("Alice", 20);printf("Student list:\n");show_students();cleanup();return 0;
}

分别介绍.h中的几个宏定义:

① offsetof宏

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

是 C 语言中非常经典的技巧,用来 计算结构体中某个成员变量相对于结构体起始地址的偏移量(以字节为单位)

假设你有一个结构体:

struct student {int id;             // 偏移 0char name[20];      // 偏移 4double gpa;         // 偏移 24(视平台对齐规则)
};

现在你想知道 gpa 在结构体中的偏移是多少,可以这样写:

size_t offset = offsetof(struct student, gpa);

宏展开:

((size_t) &((struct student *)0)->gpa)

拆解:

  1. (struct student *)0:构造一个指向地址 0 的结构体指针(不会真的访问地址 0,只是为了类型推导)。

  2. ((struct student *)0)->gpa:假装在地址 0 上有一个结构体,访问其成员 gpa,结果是一个 地址偏移表达式(例如 0 + 24,实际上就是 24 字节偏移)。

  3. &((struct student *)0)->gpa:取这个成员的地址(是个偏移地址,不是真地址)。

  4. (size_t):强制转换为整数类型,得到偏移量。

例子

#include <stdio.h>
#include <stddef.h>struct my_struct {char a;         // offset 0int b;          // offset 4 (由于对齐)double c;       // offset 8
};int main() {printf("offset of a = %zu\n", offsetof(struct my_struct, a)); // 0printf("offset of b = %zu\n", offsetof(struct my_struct, b)); // 4printf("offset of c = %zu\n", offsetof(struct my_struct, c)); // 8
}

offsetof(TYPE, MEMBER) 返回成员 MEMBER 在结构体 TYPE 中的字节偏移量,用于高级结构体操作的基础工具之一。 

  • offsetof 是由 C 标准定义的宏(在 <stddef.h> 中)。

  • 它是完全 编译期计算的,不访问内存,也不会有运行时开销

  • 在结构体操作、容器设计、内核链表、序列化、手动内存布局等场景中非常有用。

② container_of 宏:

#define container_of(ptr, type, member) ({                      \const typeof( ((type *)0)->member ) *__mptr = (ptr);        \(type *)( (char *)__mptr - offsetof(type, member) );})

从结构体的某个成员指针(ptr),反推出这个成员所属的整个结构体的地址。

struct student {int id;struct list_head list;
};struct list_head *p = &stu->list;
struct student *stu_ptr = container_of(p, struct student, list);

这个宏干的事情就是:你只知道结构体中某个成员变量的地址 ptr,你想获得这个变量所在的整个结构体 type *,它会帮你算出来。

  • offsetof(type, member):得到成员在结构体中的偏移。

  • (char *)__mptr - offset:从成员地址减去偏移就得到结构体地址。

③ list_entry

#define list_entry(ptr, type, member) \container_of(ptr, type, member)

通过链表指针 ptr 得到包含它的结构体 type *

这个宏就是对 container_of 的封装,用在链表中,ptrstruct list_head * 类型。

④ list_for_each_entry

#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_head,而是包含它的那个结构体)。

  • pos:用于遍历的结构体指针。

  • head:链表头(struct list_head *)。

  • member:结构体中的链表成员名字。

⑤ list_for_each_entry_safe宏

#define list_for_each_entry_safe(pos, n, head, member)                 \for (pos = list_entry((head)->next, typeof(*pos), member),         \n = list_entry(pos->member.next, typeof(*pos), member);       \&pos->member != (head);                                       \pos = n, n = list_entry(n->member.next, typeof(*n), member))

是 Linux 内核链表中非常常用的一个 “安全遍历”宏,用于在 遍历过程中可能会删除当前节点 的情况。它是对 list_for_each_entry 的增强版本。

对比来看:

list_for_each_entry

#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 是当前元素指针。

但是 不能在遍历中删除当前元素,因为 pos = pos->next 是在遍历尾部执行的,一旦 pos 被删除了,就找不到下一个节点了,会导致访问野指针。

struct my_struct *pos;
list_for_each_entry(pos, &head, list) {if (should_delete(pos)) {list_del(&pos->list);  // ❌危险:pos下一次访问就无效了kfree(pos);}
}

安全版本 list_for_each_entry_safe

#define list_for_each_entry_safe(pos, n, head, member)                 \for (pos = list_entry((head)->next, typeof(*pos), member),         \n = list_entry(pos->member.next, typeof(*pos), member);       \&pos->member != (head);                                       \pos = n, n = list_entry(n->member.next, typeof(*n), member))

多了一个变量 n(next):

  • 在遍历当前节点时,预先保存好下一个节点 n

  • 即使你在中间 free(pos)list_del(&pos->member)也不会影响后续访问 n,避免野指针。

struct my_struct *pos, *n;
list_for_each_entry_safe(pos, n, &head, list) {if (should_delete(pos)) {list_del(&pos->list);  // ✅可以安全删除kfree(pos);}
}

宏名作用
container_of从成员指针推导出结构体指针
list_entrylist_head* 拿到结构体指针
list_for_each_entry遍历链表中包含 list_head 的结构体元素
list_for_each_entry_safe安全遍历链表中包含 list_head 的结构体元素

为什么要用用户空间简化版不依赖内核的用户态实现?

1. 用户态无需加载内核模块,更安全方便

  • 在用户态编写和运行程序不需要 root 权限,也不涉及内核模块的编译、加载、卸载。

  • 避免由于错误操作导致内核崩溃(例如 oops、panic)。

2. 无需依赖 Linux 内核头文件

  • 内核中的 list_head 定义在 <linux/list.h>,不能直接在用户空间中使用,因为它依赖于很多内核结构(如 container_ofprefetch、内存屏障等)。

  • 简化版中只保留核心逻辑,移除内核复杂依赖,更适合教学和用户态程序调试。

3. 易于学习和调试

  • 用户态程序可以用 gdbprintf 等方式方便地调试,学习 list_head 的插入、删除、遍历操作。

  • 可以专注理解链表逻辑,而不必关心底层硬件资源或内核调度等复杂因素。

4. 跨平台兼容性更好

  • 简化版不依赖内核,在不同平台和系统(如 macOS、Windows 下的 WSL)中也能运行和调试。

5. 利于单元测试或快速原型

  • 在开发内核模块之前,可以在用户空间用简化版 list_head 提前验证算法逻辑。

用户空间简化版特点:

  • 和内核的一样,提供双向循环链表的基本结构。

  • list_addlist_dellist_for_each 等函数模拟内核链表操作。

  • 通常会配套实现一个 container_of 宏,简化结构体成员查找。

八、内核链表与普通链表的区别

从编写代码的角度看:

内核链表:

嵌入在结构体内部,相当于在结构体内部的链表

struct student {char name[20];int age;struct list_head list;
};

普通链表:

创建一个专门的链表节点,在链表内部的结构体

typedef struct ListNode {int data;               // 数据域(可根据需求修改类型)struct ListNode *next;  // 指针域,指向下一个节点
} ListNode;

特性优势
通用性可嵌入任何结构体,实现任意链表类型。
内核标准几乎所有内核链表都是用它做的
特性list_head(内核链表)普通链表(如学生手写的)
 支持双向链表默认支持需自己实现
节点结构通用通用结构体嵌入每种链表都得重新设计
插入/删除效率高O(1),不需要额外判断需特判头/尾/空链表等情况
安全性高通过宏屏蔽指针错误易出现野指针、空指针
支持遍历宏list_for_each 等宏方便遍历手写 while (node) 不易维护
支持从节点找回宿主结构体container_of() 宏可找回宿主结构体需要自己手动维护映射关系
易于模块化封装一套链表机制通用于所有模块每个模块都得造轮子

1. 通用性:

  • 你可以把 list_head 内嵌到任何结构体中,只要结构体里有 struct list_head 成员,就能挂进链表。

  • 所以它特别适合模块化/插件化开发,广泛用于内核、驱动、队列系统等。


2. 性能:

  • 插入和删除操作 不需要遍历链表,也不需要特判头尾节点,始终是 O(1)

  • 它用的是双向循环链表结构(不是 NULL 结尾,而是指向自身),这让很多操作逻辑更简洁。


3. 安全性:

  • 它会自动维护前后指针,避免你手动操作 next/prev 出错。

  • Linux 内核中还引入了调试辅助机制,能检测非法访问、双重删除等行为。


4. 反向查找能力:

  • 通过 list_entry()container_of() 宏,可以轻松从 list_head 节点获取其宿主结构体的地址,实现灵活的对象管理。


 普通链表存在的问题

  • 每次写都要重新定义结构体。

  • 插入/删除时容易出现各种 bug。

  • 遍历复杂,容易出现内存泄漏或悬挂指针。

  • 不支持从节点反查宿主结构体,缺乏灵活性。

  • 没法通用于多个模块,需要复制粘贴逻辑。

list_head 是一个“通用、可嵌入、性能高、安全”的链表实现,它解决的不是“如何实现链表”,而是“如何设计一套所有模块都能复用、零成本维护的链表管理机制”。

你可以把它理解为 链表机制中的“操作系统级标准库”,比自己写一个链表强得多。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/90063.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/90063.shtml
英文地址,请注明出处:http://en.pswp.cn/web/90063.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Spring Boot+Redis Zset:三步构建高可靠延迟队列系统

系统设计架构图---------------- ----------------- ---------------- | | | | | | | 生产者 |------>| Redis ZSet |------>| 定时任务消费者 | | (添加延迟任务) | | (延…

MCP vs 传统集成方案:REST API、GraphQL、gRPC的终极对比

MCP vs 传统集成方案&#xff1a;REST API、GraphQL、gRPC的终极对比 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特…

SQL语句中锁的使用与优化

一、锁机制简介1.定义在数据库中&#xff0c;除了传统的计算资源&#xff08;如CPU、RAM、I/O等&#xff09;的争用以外&#xff0c;数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题&#xff0c;锁冲突也是影响数据库并…

Linux笔记1——简介安装

操作系统给用户一个操作界面&#xff0c;用户通过操作界面使用系统资源Linux内核管理控制硬件&#xff0c;和硬件打交道SCSI&#xff08;盘&#xff09;sd**;第一个*表示磁盘顺序&#xff0c;第二个*表示分区。例如&#xff1a;sda\sdb\sdc,sda1,sda2NVMe&#xff08;盘&#x…

GoLand 部署第一个项目

前言&#xff1a;Go环境部署分为两种模式&#xff0c;一种是基于GOPATH部署&#xff08;老版本&#xff09;&#xff0c;另一种是基于Module部署&#xff08;新版本v1.11开始&#xff09;。GOPATH&#xff1a;需要配置GOPATH路径&#xff0c;将GOPATH目录视为工作目录&#xff…

Mosaic数据增强介绍

1. 核心概念与目标Mosaic 是一种在计算机视觉&#xff08;尤其是目标检测任务&#xff09;中非常流行且强大的数据增强技术。它最早由 Ultralytics 的 Alexey Bochkovskiy 在 YOLOv4 中提出并推广&#xff0c;后来被广泛应用于 YOLOv5, YOLOv7, YOLOv8 等模型以及其他目标检测框…

LINUX 722 逻辑卷快照

逻辑卷快照 lvcreate -L 128M -s -n lv1-snap /dev/vg1/lv1 lvs lvscan mount -o ro /dev/vg1/lv1 /mmt/lv1-snap dmsetup ls --tree 测试 lvs /dev/vg1/lv1-snap dd if/dev/zero of/uc1/test bs1M count40 lvs /dev/vg1/lv1-snap 问题 [rootweb ~]# cd /mnt [rootweb mnt]# m…

Springboot+vue个人健康管理系统的设计与实现

文章目录前言详细视频演示具体实现截图后端框架SpringBoot前端框架Vue持久层框架MyBaits成功系统案例&#xff1a;代码参考数据库源码获取前言 博主介绍:CSDN特邀作者、985高校计算机专业毕业、现任某互联网大厂高级全栈开发工程师、Gitee/掘金/华为云/阿里云/GitHub等平台持续…

数据结构 --栈和队链

一.栈的概念一种特殊的线性表&#xff0c;只能从固定的一端插入和删除元素。栈中元素遵循先进后出的原则。二.模拟实现public class MyStack {public int size;public int[] array;public MyStack(){array new int[10];}private void grow(){array Arrays.copyOf(array,array…

文档处理控件TX Text Control系列教程:使用 C# .NET 将二维码添加到 PDF 文档

PDF 文档通常是合同、发票、证书和报告的最终格式。尽管它们在设计上是静态的&#xff0c;但用户现在希望能够与它们交互、验证信息并直接从这些文件访问数字服务。这时&#xff0c;二维码就变得至关重要。 PDF 文档中的二维码将印刷或数字内容与动态在线体验连接起来。用户只需…

Google Chrome 谷歌浏览器全部版本集合

Google Chrome 谷歌浏览器全部版本集合 Collection of all software versions of Google Chrome. 项目介绍 本项目为Google Chrome谷歌浏览器的全部版本集合&#xff0c;方便大家下载旧版本使用。 因为Gitee项目限制仓库1G大小&#xff0c;所以许多谷歌浏览器版本无法上传。…

论文略读:Towards Safer Large Language Models through Machine Unlearning

ACL 2024大型语言模型&#xff08;LLMs&#xff09;的迅猛发展展现了其在多个领域的巨大潜力&#xff0c;这主要得益于其广泛的预训练知识和出色的泛化能力。然而&#xff0c;当面对问题性提示&#xff08;problematic prompts&#xff09;时&#xff0c;LLMs 仍然容易生成有害…

深度学习 ---参数初始化以及损失函数

深度学习 —参数初始化以及损失函数 文章目录深度学习 ---参数初始化以及损失函数一&#xff0c;参数初始化1.1 固定值初始化1.1.1 全0初始化1.1.2 全1初始化1.3 任意常数初始化1.2 随机初始化一&#xff0c;参数初始化 神经网络的参数初始化是训练深度学习模型的关键步骤之一…

JS--M端事件

移动端&#xff08;Mobile 端&#xff0c;简称 M 端&#xff09;开发中&#xff0c;由于设备特性&#xff08;触摸屏、手势操作等&#xff09;&#xff0c;需要处理一些与桌面端不同的事件。这些事件主要针对触摸交互、手势识别等场景 一、触摸事件&#xff08;Touch Events&am…

Linux网络编程-tcp

tcp、udp对比&#xff1a;UDP1. 特点无连接&#xff1a;无需建立连接即可发送数据。不可靠&#xff1a;不保证数据顺序或完整性。低延迟&#xff1a;适合实时性要求高的场景。2. 应用场景视频/音频流传输&#xff08;如直播&#xff09;。DNS 查询、在线游戏。TCP1. 特点面向连…

记一次flink资源使用优化

一.现状分析 现有任务的资源配置如下&#xff0c;根据ui监控中Garbage Collection可以发现&#xff0c;此任务频繁的发生GC&#xff0c;且老年代GC时间较久二.整体memory使用分析如下Framework Heap&#xff08;框架堆内存&#xff09;用于Flink框架自身的堆内存&#xff08;如…

Vue底层换成啥了?如何更新DOM的?

摘要&#xff1a;之前的vue是使用虚拟 DOM的&#xff0c;但是Vue 3.6 带来了一个意义重大的更新&#xff1a; Vapor Mode 渲染模式。Vue 渲染策略的演进&#xff1a; Vue 1.x&#xff1a; 基于模板渲染策略&#xff0c;直接将模板转换为DOM元素&#xff0c;并为每个DOM元素创建…

0722 数据结构顺序表

Part 1.顺序表的代码一.顺序表的内存申请head.h: typedef int datatype;typedef struct sqlist {//数据元素datatype data[MAXSIZE];//顺序表长度int len;}*sqlist; //*sqlist的作用: //sqlist:struct Sqlist * sqlist create();head.c: sqlist create() {sqlist list (sqlist)…

为何在 Vue 的 v-model 指令中不能使用可选链(Optional Chaining)?

Vue 的 v-model 是实现组件与数据双向绑定的核心指令之一&#xff0c;它本质上是一个语法糖&#xff0c;用于简化对表单元素和组件 props 的同步更新。然而&#xff0c;在 Vue 3&#xff08;以及 Vue 2 的某些模式下&#xff09;&#xff0c;开发者尝试在 v-model 中使用 JavaS…

基于单片机智能药盒/智能药箱/定时吃药系统

传送门 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目速选一览表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目功能速览 概述 本设计实现了一种基于单片机的智能药盒&#xff0c;系统以微控制器&#xff08;如STM32&#xff…