LWN:为内核继续改善list遍历操作!

TI C2000 , .cmd文件,.TI.ramfunc,将程序分配为在RAM中运行/Flash运行的配置方法和原理讲解,(如28375S ,280049,28335等等芯片) 1 基础知识讲解 1.1 在RAM中运行还是在flash中运行指的是什么? 1.1.1 背景:程序编译完之后生成的.bin文件,.bin文件中包含代码段,代码段中全是汇编指令,而汇编指令中包含多个函数,各函数头的地址不同,如存在A函数和B函数,假设RAM地址区间是0x0000-0x8000,flash的地址区间是0xA000-0xF000,A函数要调用B函数,则A函数调用B函数的那段汇编指令中包含的是B函数头的存储地址,A函数和B函数都在flash中有存储 1.1.2 情况1:B函数是配置为在RAM中运行, 阅读详情

关注了就能看到更多这么棒的文章哦~

Toward a better list iterator for the kernel

By Jonathan Corbet
March 10, 2022
DeepL assisted translation
https://lwn.net/Articles/887097/

链表(linked list)概念很简单,它们往往在入门级数据结构课程的开始课程内就教过了。因此,要是大家知道内核社区对其长期都在用的链表实现感到不太满意,不仅在寻找解决一些问题的方法,而且一直在努力追求实现更好的解决方案。现在看来,近期就会有一些改动,也就是说:在 30 多年后,内核开发者可能已经找到了一种更好的方法来安全地迭代一个链表。

Kernel linked lists

当然,C语言下面创建链表相对容易。但是,它并没有定义和规范好如何创建包含任何类型结构的那种通用链表。C 语言的性质适合在每一种需要链表的情况下创建一个专用的链表,从而也就导致了更多的模板代码(boilerplate code)和重复定义。每一个链表的实现都必须经过 review 以保证其正确性。如果能有一个可以确认可用的唯一实现,那就最好了,这样内核开发者就可以更有效地利用他们自己的时间避免在具体领域代码之外引入 bug 了。

内核自然有一些关于链表的解决方案,但最常用的是 struct list_head:

struct list_head {
  struct list_head *next, *prev;
  };

这个结构可以显式创建双向链表,类似下面这个样子:

outside_default.png

struct list_head 可以很好地构造出一个链表,但有一个明显的缺点:它不能保存其他任何信息。通常,这种数据结构是需要用来把其他一些重要数据串联起来的,这个 list 结构本身并不是什么重点。C 语言无法很方便地创建一个可以支持任何形式的 payload 的链表,但可以很容易地将结构 list_head 嵌入到开发者希望用链表串起来的目标结构中:

outside_default.png

这就是在内核中构建链表的通常方式。使用 container_of() 这样的宏可以把指向 list_head 结构的指针变成指向包含它的结构的指针。使用链表的代码几乎总是使用这个宏(通常是间接调用)来获取对 payload 的访问指针。

最后还有一个值得注意的细节,list 的实际头部往往是一个 list_head 结构,并没有也嵌入到 payload 结构中:

105a54ae33340bbf6cd4b7563ebea1a6.png

关于这个基础结构如何使用,有一个现实世界的例子就是 inode 结构,一个 inode 结构就代表了文件系统中的一个文件。Inode 可以同时出现在很多 list 中,因此 inode 结构包含了至少五个各不相同的 list_head 结构,不幸的是,编者的制图水平并不能很好地展示出这个数据结构。其中的一个 list_head 结构名为 i_sb_list,是用来将 inode 与它所属的文件系统的 superblock 关联起来。指向这个列表的 list_head 结构就是 struct super_block 的 s_inodes 字段。这个例子就是一个没有嵌入到 inode 结构的的 list_head。

对一个链表的遍历通常就是从这个 list_head 开始,然后跟随下一个指针跳转,直到再次找到这个 list_head。当然,我们可以自己写代码来实现遍历,但是内核也为这个目的提供了许多函数和宏。其中一个就是 list_for_each_entry(),它将遍历整个列表,在每个节点都提供一个指向外包结构的指针。使用这个宏的典型代码看起来像这样:

struct inode *inode;

/* ... */
list_for_each_entry(inode, &sb->s_inodes, i_sb_list) {
  /* Process each inode here */
}
/* Should not use the iterator here */

在循环中,该宏使用 container_of()将 inode 指向每个列表条目的外包 inode 结构。问题是从循环中退出时 inode 的值是多少?如果代码以 break 语句退出循环,inode 将指向当时正在处理的那一项元素。然而,如果完成遍历整个列表的话,inode 就会是指向那个独立的列表头部再使用 container_of() 的结果,但是问题是这个节点并没有一个外包的 inode 结构。因此内核就可能会出现不确定的行为,导致许多问题。

出于这个原因,像 list_for_each_entry() 这样的宏的规则就是,这个迭代变量不应该在循环之外使用。如果一个值需要在循环之后被访问,它应该被保存在一个单独的变量中,从而才可以在循环之后访问。但这是一条隐含的规则,没有人觉得有必要在宏本身的文档中明确指出这个限制。不足为奇的是,这条规则充其量只是一个指导原则,内核中充满了事实上在循环后仍在使用迭代变量的代码。

The search for a safer iterator

当我们上次讨论这个问题时,Jakob Koschel 已经发布了 patch 来修复其中一部分犯错的位置,后来他也在继续这个工作。然而,Linus Torvalds 认为这种改动方法还是不够的,因为它无法防止未来出现问题:

因此,如果我们的基本规则是 "不要在循环结束后使用循环迭代变量,因为这种用法可能导致各种微妙的问题",那么除了修复现有的包含这个问题的代码外,我真的希望:a)为未来新加入的这种代码要给出一个编译器警告;b)这种写法在未来需要确保完全无法正常工作。

因为不这样的话这个问题今后还会再次发生。

一路走来,开发者们意识到,转到一个较新版本的 C 标准可能会有帮助,因为它允许在循环体内声明迭代变量(从而使它在循环之外不可见)。Torvalds 初步尝试了一个解决方案,看起来像这样。

#define list_for_each_entry(pos, head, member)                          \
  for (typeof(pos) __iter = list_first_entry(head, typeof(*pos), member); \
       !list_entry_is_head(__iter, head, member) && (((pos)=__iter),1); \
       __iter = list_next_entry(__iter, member))

这版的宏仍然接受迭代器变量作为参数,保持与之前相同的原型。这一点很重要,因为在内核代码中有成千上万使用这个宏的位置。但是它声明了一个新的变量来进行实际的迭代使用,并且只在循环里对传入的迭代变量进行设置。由于循环本身可能永远不会被执行(比如如果是个空列表),存在着循环里从未设置过这个外面传入的迭代变量,所以它可能在循环之后保持未初始化的状态。

这个版本很快被第二版所取代,都被称为了 "a work of art (艺术作品)":

#define list_for_each_entry(pos, head, member)                          \
  for (typeof(pos) pos = list_first_entry(head, typeof(*pos), member);  \
       !list_entry_is_head(pos, head, member);                          \
       pos = list_next_entry(pos, member))

现在,循环范围内的迭代变量被声明为与外层变量同名,从而将外部变量屏蔽掉(shadowing it)。在这个版本中,外部声明的迭代变量根本就不会在循环中使用。

Torvalds 尝试这两种方案时希望,如果在循环之外使用(外部)迭代变量,这将会导致编译器产生警告,因为它不会再允许循环体内部初始化。但这并未起到效果;现在代码中有些地方明确初始化了迭代变量,而且,无论如何,内核里面早已经禁用了 "使用未初始化的变量" 这种编译器警告,因为内核里有太多关于这个的误报了。

James Bottomley 提出了一个不同的方法:

#define list_for_each_entry(pos, head, member)                          \
  for (pos = list_first_entry(head, typeof(*pos), member);              \
  !list_entry_is_head(pos, head, member) && ((pos = NULL) == NULL;      \
                                             pos = list_next_entry(pos, member))

这一版会在退出循环时明确地将迭代变量设置为 NULL,导致任何会使用它的代码应该都会 fail。Torvalds 指出了这个做法有个明显问题:它改变了一个在整个内核中广泛使用的宏的语义,并可能会引入 bug。这也会使开发者在向没有新语义支持的 stable kernel 内核进行 patch backport 时碰到更多困难。

还有一种方法是由 Xiaomeng Tong 提出的:

#define list_for_each_entry_inside(pos, type, head, member) \
  for (type * pos = list_first_entry(head, type, member);   \
       !list_entry_is_head(pos, head, member);              \
       pos = list_next_entry(pos, member))

Tong 的 patch 创建了一套新的宏,全新的名字,对现有的代码可以逐个慢慢切换到新的用法上。完全不使用外部声明的迭代变量,相反,迭代变量的名称和类型被作为参数传递进来,并且迭代器被声明在循环本身的范围内。然而,Torvalds 也不喜欢这种方法。他说,几乎每一处使用它的地方都会导致冗长的、难以阅读的代码,而且在错误的地方导致了痛苦。"他说:"我们应该争取在 糟糕 的情况下必须做额外的工作,而且哪怕在这些地方我们也应该认真追求可读性。"

A solution at last?

在拒绝了各种解决方案之后,Torvalds 开始思考一个好的解决方案可能是什么样子的。他总结说,这里的一部分问题是,外包结构的类型与 list_head 结构是分开的,这使得编写迭代遍历宏更加困难。如果这两种类型能够以某种方式连接起来,事情就会变得简单。此后不久,他想出了一个解决方案,实现了这个想法。它首先提供了一个新的宏:

#define list_traversal_head(type,name,target_member)            \
  union { struct list_head name; type *name##_traversal_type; }

这个宏会被用来声明 list 的真正 head,而不是包含在其他结构中的 list_head 项。具体来说,它声明了一个新的 union 类型的变量,包含一个名为 name 的 list_head 结构,以及一个指向了外包结构类型的指针,名为 name_traversal_type。这个指针从未被使用过,它只是将包含结构的类型与 list_head 变量联系起来的一种方式。

然后,新增了一个迭代 API:

#define list_traverse(pos, head, member)                                \
  for (typeof(*head##_traversal_type) pos = list_first_entry(head, typeof(*pos), member); \
       !list_entry_is_head(pos, head, member);                          \
       pos = list_next_entry(pos, member))

代码可以通过使用 list_traverse()而不再使用 list_for_each_entry() 来遍历一个列表。迭代变量将是 pos,它只存在于循环体内部。列表的第一项作为 head 传递进来,而 member 是包含结构中 list_head 结构的名称。该 patch 包括几个原有使用位置的修改替换,来告诉人们该如何使用。

Torvalds 认为这就是 "未来的方向"。完成这个改动可能是一个长达数年的项目。在内核中,有超过 15000 个使用了 list_for_each_entry()(包括它的变种)的位置。每一个最终都需要被修改,而且列表头的声明位置也必须同时改变。所以这不是一个快速的解决方案,但从长远来看,它可以使内核中的链表实现更加安全。

有人可能会说,所有这些都是由于在内核中继续使用 C 语言而造成的自找的痛苦。这可能是事实,但目前还是缺乏更好的替代方案。例如,尽管 Rust 语言有很多优点,但对想实现链表的人来说用 Rust 也都不是容易的事情,所以转用这种语言也不会能自动解决这个问题。因此,内核开发者似乎不得不在一段时间内继续容忍这种要小心谨慎使用的基础设施。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

a875ff17dc50fe351c5c140b61cb9cd4.png

值得苦练的100道Python经典练手题(全网最全,附详细答案,建议收藏) 这100道Python练习题涵盖了从基础语法到高级算法、从数据库操作到Web开发的各个方面,是备考数据库系统工程师的绝佳素材。建议按照章节顺序逐一练习,遇到难点可反复钻研,真正掌握每一道题背后的知识点。通过这些练习,不仅能提升Python编程能力,还能加深对数据库系统原理的理解,为考试和实际工作打下坚实基础。 阅读详情

相关推荐

AWS-解析mysql binlog同步数据方案

虽然是公有云的鼻祖,AWS在某些产品的实现却太不给力;可能是习惯了阿里云喂到嘴边的感觉,AWS很多方案需要自己折腾,蛋疼!比如这里要讲的mysql数据同步方案。 阿里云产品DTS,点几下就OK了,AWS,只能通过插件安装、配置、调试、蹚坑……,具体就是用msk的debezium插件连接器。没见识过的一脸懵逼,下面说下配置步骤和一些注意的坑。

sdmei 1406

Linux中内核操作链表的函数大全

Linux中内核操作链表的函数大全 struct list_head : 链表结构体的指针的数据结构 LIST_HEAD_INIT: 用于初始化这个头部编成独立结点 INIT_LIST_HEAD(ptr) : 将这个指针初始化为孤立的结点 LIST_HEAD(name) : 初始化一个变量为name, 并且初始化 list_add(struct list_head *new, struct lis...

cxy504的博客 386

功率开关管驱动串扰(Crosstalk):深度解析与优化方案

介绍了功率开关器件驱动串扰产生机理,并整理了常见的工程应用优化措施

qq_38326420的博客 553

linux kernel list详解

linux kernel list,linux内核的链表结构,它使用了最简洁的方式实现了一个几乎是万能的链表。 说起链表,一共有3种:单向链表、双向链表、循环链表。 单向链表是链表中最简单的一种形式,它只有一个后继指针(一般代码中是next)来维护结构。所以,单向链表只能沿着后继指针做向后操作,不能向前操作。单向链表也不能从中间插入或者删除一个元素,因为没有前向指针,所以无法获取前一个元素,...

weixin_39094034的博客 1550

DSP复习笔记(5)——外部接口模块XINTF

【注意】 1. 芯片型号:TMS320F28335 2. 适用于NJUSTEO李彧晟老师课程 3. 参考资料:《TMS320F28335 DSP原理、开发及应用》 4. 对应PPT第二章 5. 本章内容较多而且较为重要,请结合PPT一起复习

BerenCamlost的博客 1万+

基于 MATLAB 的综合能源系统建模:全面解析与案例应用 

本文详细介绍了基于 MATLAB 的综合能源系统建模相关知识,包括 MATLAB 在综合能源建模中的优势、综合能源系统各组件和网络的建模方法,并通过园区综合能源系统的案例展示了从建模到优化调度求解以及结果分析的全过程。在当今全球能源格局深刻变革的背景下,综合能源系统(Integrated Energy System,IES)作为一种创新的能源供应体系,整合了多种能源形式,如电力、热力、燃气等,以实现能源的高效生产、传输、分配和利用。需要考虑燃气的压力、流量特性以及管道的阻力特性等。

qq_57128262的博客 1334

linux内核中list的基本用法

linux内核list基本用法

qq_45528033的博客 718

UOS系统手动添加1920x1080分辨率:xrandr命令详解与持久化配置

本文详细介绍了在UOS系统中使用xrandr命令手动添加1920x1080分辨率的方法。针对系统设置中缺失该选项的常见问题,文章提供了从识别显示设备、使用cvt生成模式、添加并切换分辨率,到通过修改.profile文件实现持久化配置的完整步骤。同时涵盖了虚拟机环境、设备名识别及排错等进阶内容,帮助用户彻底解决UOS分辨率配置难题。

weixin_29300931的博客 498

Linux等待队列

一开始只是想看看select源码,但似乎需要先看看等待队列,发现了一些好的博客。

qq_45721740的博客 995

PS射击游戏射线检测优化:原理、工程与全链路高性能实现

射线池:减少GC,提升批量检测效率。分层检测:先粗后细,远距离射击高效判定。批量检测:并行处理,适合大量射线场景。空间结构:进一步加速大场景检测。下面继续深入射线检测优化,补充更多实用细节和进阶技巧,包括空间分区加速、射线检测多线程/异步化、射线合并与分帧、射线池通用模板、常见性能陷阱与调优建议等。对于极端性能需求,可自定义轻量级物理系统,仅检测关键对象(如玩家、子弹),跳过复杂物理引擎。结合AABB、OBB、球体等简单碰撞体,射线-包围体检测极快。空间分区+分层检测是大场景射线优化核心。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 434

Qt代码-软件商店

ui->statusBar->showMessage(QString("%1 已卸载").arg(app["name"].toString()));ui->statusBar->showMessage(QString("开始下载 %1...").arg(app["name"].toString()));QString("%1 已下载完成,是否立即安装?ui->statusBar->showMessage(QString("%1 下载完成").arg(app["name"].toString()));

寒寒豆豆软件公司的博客 111

【linux内核中的双向链表-01】list_head、list_entry

【linux进程管理-02】内核中的双向链表list_head(x86)

rentong123的博客 776

【Flink】Flink 消费一段时间1万多条数据就卡死

我们有个现场环境,发现了奇怪的现象,就是每次启动任务后,消费1万多条数据后就会卡死,然后整个流不消费,也不往下输出,看起来卡死了一样。缺图这里面出现的网闸原因导致的,但是经过我们写了一个简单的kafka消费程序,然后在出问题的节点上消费数据,实际排查,发现不是这个问题。这个问题,发现也不是,这段时间没有进行过kafka数据清理。里面的问题。因为这个问题,我们后面的代码刻意规范了,不这样操作。后来是在没办法了,然后我们dump了线程,发现有如下代码。

九师兄 350

深入理解Linux网络技术内幕 P1-P2

细品

西门吹雪 539

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

linux内核链表以及list_entry--linux内核数据结构(一)

链表是一种复杂的数据结构,其数据之间的相互关系使链表分成三种:单链表、循环链表、双向链表,而内核中的链表又是如何实现的呢? linux内核主要是有C和汇编写成的,C是面向过程的语言,但是linux内核却用C实现了一套面向对象的设计模式,linux内核中处处体现着面向对象的思想 传统的链表实现之前我们前面提到的链表都是在我们原数据结构的基础上增加指针域next(或者prev),从而使各个节点

OSKernelLAB(gatieme) 5017

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

LIST_ENTRY链表学习

链表是驱动开发中经常遇到的一个数据结构,主要是双向循环链表;要使用链表,需要用到一个LIST_ENTRY的结构,其定义如下: typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; // 指向下一个节点 struct _LIST_ENTRY *Blink; // 指向前一个节点 } LIST_ENTR

ShadowAssassin的专栏 6548

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

list_entry的宏定义

list_entry的宏定义:#define list_entry(ptr, type, member) / ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member))) 这个倒是不难理解:从一个结构的成员指针找到其容器的指针。但是正因为如此,我的第一感觉是,这个宏的名字应该更加抽象,名字似乎应该改称叫“寻找容器”一类的,查

自由海,含着风! 2972

Linux内核之双向链表 - 通过list_head指针找到wrapper结构指针

转载自:Linux内核之双向链表 - list_head(3) (mudongliang.github.io)https://mudongliang.github.io/2015/10/05/linux-list_head3.html#:~:text=list_first_entry,%E5%AE%8F%EF%BC%9A%E8%8E%B7%E5%BE%97%E9%93%BE%E8%A1%A8%E7%AC%AC%E4%B8%80%E4%B8%AA%E5%85%83%E7%B4%A0%E6%89%80%E5%9

637

Linux container_of 以及list_for_each_entry 的拆分理解

在阅读Linux 内核以及驱动的时候,经常会见到这么一个宏"list_for_each_entry()",或者"list_for_each_entry_xxx()",如果不理解就验证影响代码的理解。拖了很久,这次下决心分析明白,一分析发现实际上很容易理解。 list_for_each_entry() 宏,主要有两个宏,list_first_entry, list_next_entry,加一个fo

zuihaobushi的专栏 786

Linux内核链表深度分析

链表简介: 链表是哟中常用的数据结构,它通过指针将一系列数据节点连接成一条数据链。相对于数组,链表具有更好的动态性,建立链表时无需预先知道数据总量,可以随机分配空间,可以高效地在链表中的任意位置实时插入或者删除数据。链表的开销主要是访问的顺序性和组织链的空间损失。 1. 链表对比 传统链表和内核链表 传统链表:一般指的是单向链表 struct List { struct li

coding__madman的博客 1万+

Linux淘金记(二):list_head——链表?会它就够了

Linux内核源码是一个巨大的宝库,总会发现属于你的宝物。阅读源码不是目的,为己所用才是答案。

baidu_23858427的博客 2144

内核链表分析

依此类推,每次插入的新节点都是紧挨着 header_task 表尾,而插入的第一个节点 my_first_task 排在了第一位, my_second_task 排在了第二位,可得出:先插入的节点排在前面,后插入的节点排在后面, “先进先出,后进后出” ,这不正是队列的特点吗(First in First out)!获取下一个宿主结构的地址。我们真正更关心的是包含 list_head 这个结构体字段的宿主结构体,因为只有定位到了宿主结构体的起始地址,我们才能对对宿主结构体中的其它有意义的字段进行操作。

weixin_52849254的博客 1061

深入理解 Linux 内核---虚拟文件系统

虚拟文件系统所隐含的思想是把表示很多不同种类文件系统的共同信息放入内核; 其中有一个字段或函数来支持 Linux 所支持的所有实际文件系统所提供的任何操作。 虚拟文件系统(VFS)的作用 虚拟文件系统也可称为虚拟文件转换,是一个内核软件层,用来处理与 Unix 标志文件系统相关的所有系统调用。 其健壮性表现在能为各种文件系统提供一个通用的接口。 VFS 支持的文件系统可以划分为三种主要类型” ...

新博客:https://aping-dev.com/ 1417

linux kernel链表相关结构及操作

相关内容都定义在 include/linux/list.h文件中 LIST_HEAD #define LIST_HEAD(name) \ struct list_head name = LIST_HEAD_INIT(name) #define LIST_HEAD_INIT(name) { &(name), &(name) } list_add_tail static ...

copbint 264

Linux社区关于链表的bug讨论我们要看一下

最近在Linux社区看到一个关于内核链表的讨论原文讨论链接:https://lwn.net/SubscriberLink/885941/01fdc39df2ecc25f/先用例子说明怎么使...

嵌入式Linux 584

Linux内核链表及list_entry解析

链表是一些包含数据的独立数据结构的集合,链表中的每一个节点通过链或者指针连接在一起。程序通过指针访问链表中的节点。链表一般分为单链表和双链表。 1.单链表 单链表中,每个节点包含指向下一个节点的指针。链表最有一个节点的指针字段值为NULL,表明链表后面不再有其它节点。下面是一张单链表的图: 对应的数据结构为: C代码 typedefstructNODE...

zz460833359的博客 1103
上一篇: LWN: Benenios - 用于不公开投票的系统!
下一篇: LWN:随机数以及VM-fork!
LinuxNews搬运工
博客等级 码龄7年 438粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值