“匿名句柄” 是一切皆文件背后功臣……

90139c3e01321e7e25578016e1afda42.gif

作者 | 奇伢      

来源 | 奇伢云存储

c92b176cc589831dfe568af8e2a8635c.png


37570cbc6497a9657bcc6ce09c3d2898.png

匿名 fd 的样子?

288220463c7ee0584ddf3db09d6146c7.png

我们经常在 /proc/${pid}/fd/ 下面能看到 anon_inode : 前缀的句柄,如下:

root@ubuntu:~/temp# ll /proc/5398/fdlr-x- 1 x x 64 Aug 4 9:9 8 -> anon_inode:inotify
lrwx- 1 x x 64 Aug 4 9:9 4 -> anon_inode:[eventpoll]
lrwx- 1 x x 64 Aug 4 9:9 5 -> anon_inode:[signalfd]
lrwx- 1 x x 64 Aug 4 9:9 7 -> anon_inode:[timerfd]
lrwx- 1 x x 64 Aug 4 9:9 9 -> anon_inode:[eventpoll]

如果是正常的文件句柄,一般显式的是一个路径:

root@ubuntu:~/temp# ll /proc/5398/fdlr-x-- 1 x x 64 Aug 24 09:39 10 -> /proc/5398/mountinfo
lr-x-- 1 x x 64 Aug 24 09:39 12 -> /proc/swaps

当然 path 只是一个浅层次的感官,因为对于 socket 句柄来说也不算有人为理解上直观的 path ,但是它有完整的 inode,所以这个匿名其实匿的是 inode 。

fdf0413f9a86c8cceedc72c93158dec6.png

匿名 inode 的诞生?

ffc091e48646a2724e7cde56e8ca0e6e.png

重点提一下匿名 fd 的事情,为什么会有匿名 fd ? 什么是匿名?

在 Linux 里一切皆文件,你理解的常见“文件”有什么特性?是路径,也就是 path ,匿名的意思说的就是没有路径。匿名 fd 其实说的是匿名 inode 。

在 Linux 的文件体系中,一个文件句柄,对应一个 file 结构体,关联一个 inode 。file/dentry/inode  这三驾马车是一定要配齐的,就算是匿名的(无 path,无效 dentry ),对于 file 结构体来说,一定要绑定 inode 和 dentry ,哪怕是伪造的、不完整的 inode

anon_inodefs 就应运而生了,内核就帮你搞出来一个公共的 inode ,这就节省了所有有这样需求的内核模块,避免了内存的浪费,省了冗余重复的 inode 初始化代码。

匿名 fd 背后的是一个叫做 anon_inodefs 的内核文件系统( 位于 fs/anon_inodes.c ),这个文件系统极其简单,整个文件系统只有一个 inode ,这个 inode 是文件系统初始化的时候创建好的。之后,所有需要一个匿名 inode 的句柄都直接跟这个 inode 关联即可。

29ed020ef139b2bbc7ce8c11e86ad050.png

原理剖析

6d1b55355d653fd58d2443dd0ad2d6a0.png

 1   anon_inodefs 的初始化

上面提到了,匿名 inode 是一个公共需求,我们不需要一个完整功能的 inode,而只是需要一个 inode 而已,绑定到到 dentry ,file 等结构体。

anon_inodes.c 用来创建一个绑定匿名 inode 的 file 结构体。

整个 anon_inodefs 就只有一个文件,操作系统初始化的时候会调用初始化函数 fs_initcall(anon_inode_init) ,其中 anon_inode_init 只做两件事:

  1. 创建出一个 vfsmount 实例,创建出来之后赋值给全局变量 anon_inode_mnt ;

  2. 创建出一个 inode 实例,创建出来之后赋值给全局变量 anon_inode_inode ;

这两个变量就是 anon_inodefs 这个文件系统的全部家当了。

 2   anon_inodefs 的做了啥?

anon_inodefs 只提供了 2 个实用函数,一个获取到一个绑定匿名 inode 的 file 实例,另一个更多一些封装,返回的是 fd 句柄。如下:

anon_inode_getfile

这个函数非常简单,只做两件事:

  1. 获取一个 inode ( 获取全局的 inode 变量 anon_inode_inode ,当然也可以通过一个参数控制来创建新的 inode );

  2. 创建一个 file 结构体实例,并且把这个 inode 关联起来;

anon_inode_getfd

这个函数非常简单,只做两件事情:

  1. 创建一个新的 fd 句柄,返回的是一个非负整数;

  2. 创建一个 file 实例( 调用的是 anon_inode_getfile 来获取 ),然后把这个 fd 和 file 关联起来;

这两个函数就是 anon_inodefs 提供的两个对外的函数接口。获取到一个 file 实例,这个实例绑定到 anon_inodefs 公共的 inode 实例。

关于 anon_inodefs 的功能,其实在函数的注释中也提到了,太直白了,如下:

// anon_inode_getfile 和 anon_inode_getfd 的注释明确提到了 anon_inodefs 的两个目的:
// - 节省内存
// - 封装公共的冗余代码* Creates a new file by hooking it on a single inode. This is* useful for files that do not need to have a full-fledged inode in* order to operate correctly.  All the files created with* anon_inode_getfd() will use the same singleton inode, reducing* memory use and avoiding code duplication for the file/inode/dentry* setup.  Returns a newly created file descriptor or an error code.


 3   为什么叫这个名字 "anon_inode:${dentry_name}" ?

为什么常见的匿名 fd 都有以 "anon_inode:" 这样开头?

其实这种看得到的字符串都是 path ,这个是和 dentry 对应起来的,对于这种匿名 inode 的 dentry ,有着统一的名字:

// dentry 的操作表
static const struct dentry_operations anon_inodefs_dentry_operations = {.d_dname    = anon_inodefs_dname,
};// 操作表 .d_dname 方法的定制实现
static char *anon_inodefs_dname(struct dentry *dentry, char *buffer, int buflen)
{return dynamic_dname(dentry, buffer, buflen, "anon_inode:%s", dentry->d_name.name);
}

那 dentry->d_name.name 又是怎么赋值的呢?来看一眼完整的调用栈,以 epoll fd 来举个例子:

epoll_create 函数入口

// epoll_create 函数入口 ( fs/eventpoll.c )
static int do_epoll_create(int flags)
{// 创建匿名句柄 ...file = anon_inode_getfile("[eventpoll]", &eventpoll_fops, ep, O_RDWR | (flags & O_CLOEXEC));
}

创建一个匿名句柄

// 创建一个匿名句柄( fs/anon_inodes.c )
static struct file *__anon_inode_getfile(const char *name, const struct file_operations *fops, void *priv, int flags, const struct inode *context_inode, bool secure)
{// name 被赋值了 "[eventpoll]"file = alloc_file_pseudo(inode, anon_inode_mnt, name, flags & (O_ACCMODE | O_NONBLOCK), fops);
}

创建出一个伪 file 实例

// 创建出一个伪 file 实例
struct file *alloc_file_pseudo(struct inode *inode, struct vfsmount *mnt, const char *name, int flags, const struct file_operations *fops)
{// 初始化字符串 "[eventpoll]"struct qstr this = QSTR_INIT(name, strlen(name));path.dentry = d_alloc_pseudo(mnt->mnt_sb, &this);}

创建一个伪 dentry 实例

// 创建一个伪 dentry 实例
struct dentry *d_alloc_pseudo(struct super_block *sb, const struct qstr *name)
{struct dentry *dentry = __d_alloc(sb, name);
}

创建并初始化 dentry 实例

// 创建并初始化 dentry 实例
static struct dentry *__d_alloc(struct super_block *sb, const struct qstr *name)
{// 最后:把 name 赋值给 dentry->d_name.name,也就是 "[eventpoll]"memcpy(dname, name->name, name->len);smp_store_release(&dentry->d_name.name, dname); /* ^^^ */
}

所以,epoll fd 的名字组合起来就是 "anon_inode:[eventpoll]" 喽。

问题来了,那这个一般用在哪些地方呢?

其实就是个人性化的名字而已,最常见的就是在 proc 文件系统中。

我们在 proc 文件系统中,ls 的时候,其实就像想看名字,这个名字其实就是 path ,就会出发调用到哪步的 d_path 函数,这个函数就是把 dentry 转换成人类可读的字符串 path 的名字。

char *d_path(const struct path *path, char *buf, int buflen)
{if (path->dentry->d_op && path->dentry->d_op->d_dname && (!IS_ROOT(path->dentry) || path->dentry != path->mnt->mnt_root))// 返回 dentry 定制的名称;return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
}


 4   inode 可以对应多个 dentry

在 Linux 中是一个倒挂树的设计,从根目录( / )开始,叶子结点为文件或者目录,从根节点到叶子结点这一段就称为 path 路径,在内存里面这颗倒挂的树就体现为 dentry 树,节点就是 dentry 结构体。

这里就有个重要的知识点:

划重点:一个 inode 上可以挂多个 dentry ,一个 dentry 只能属于一个 inode 。

还记得软链接和硬链接吗?

软链接就是创建了一个新的文件,链接文件里就是路径。inode,dentry 都创建了一个新的。

硬链接则没有创建新的 inode,而是只在目录文件中创建了一个 dirent ,在目录树中添加了一个 dentry 。硬链接的场景就是一个 inode 对应了多个 dentry 节点。

195cec69cd55a60c7bd56ebd4fc87607.png

换句话说,一个 inode 可以出现在目录树的多个位置。

每个文件或者目录都会在这棵树上有自己的位置,内存用 struct path 结构体来表示唯一的位置。

struct path {struct vfsmount *mnt;   // 标识在哪个具体的文件系统实例struct dentry *dentry;  // 内存目录树节点
};

这里顺便再说另一个重要知识点:为什么内核之中,需要用 struct path 这个复合结构体来标识唯一的一个目录树位置呢?

文件系统的挂载最关键的就是把一个文件系统的实例和目录树上的一个 dentry 关联起来,而一个 dentry 可以关联多个文件系统实例

换句话说:对于一个目录树路径其实是可以挂载多个文件系统实例。比如 /mnt/path 这么一个路径,其实是可以挂载多个文件系统的,不会报错,后面的挂载直接覆盖前面的。

fcd056130a0f43f654e858c8a3854d9f.png

 5   其实还有一类匿名

为了知识的完善,这里补充一个知识点。其实关于匿名 inode 还有一种方式,这种方式以 alloc_anon_inode 函数提供,该函数传入一个超级块作为参数用于创建一个匿名 inode 。这个函数创建一个新的内存 inode 实例,这个 inode 不具备完备的功能,也是用来做匿名之用。

struct inode *alloc_anon_inode(struct super_block *s)
{// ...// 根据这个 superblock 实例来创建一个伪 inodestruct inode *inode = new_inode_pseudo(s);// 初始化这个 inode 实例 // ...return inode;
}

这种匿名 inode 就不是 anon_inodefs 的那个了,而是具体文件系统实例上的匿名 inode 。

 6   谁用到了匿名 inode

随便列举一些 eventfd,eventpoll,timerfd,signalfd,inotifyfd,io_uring fd 等等,还有很多,但比较偏僻了,就不再举例了。童鞋们惊讶吗?

12a31c47d1e552fa2fb8d9e0aee9b036.png

总结

2d6e0bfed048877be18f3911fef8a08a.png

  1. anon_inodefs 是为了公共需求抽离出来的一个内核文件系统,只有一个 inode ,为了节省内存,抽象重复代码之用;

  2. 匿名句柄是因为 fd 对应的 file 实例背靠着的是匿名 inode ,anon_inodefs 提供了两个功能函数,都是用来获取匿名 fd 的;

  3. inode 上可以挂多个 dentry 节点,换句话说,一个 inode 可以出现在 Linux 目录树的多个位置

  4. dentry 对应目录树的一个节点位置,最直观的是对应 path 路径的一个位置;

  5. 一个挂载路径可以挂多个文件系统实例,后面的覆盖前面的,所以光靠 dentry 无法唯一定位一个“文件”,Linux 内核才用两元组 < vfsmount, dentry > 来唯一定位一个“文件”;

c043d4bbf019331be567c3f2f11679aa.gif

2a752337538e54c609ed0b5edf19f78e.png

往期推荐

“5G+AI”到底有啥用?

云原生时代,底层性能如何调优?

普通大学生的Java什么水平可以进大厂

只因“薪水过高”!被欠薪三个月后遭解雇

3ab7e60a6746d6c3f7796abafd10c3be.gif

点分享

3f2cccbfae6facd1404a3fc6a0cec69f.gif

点收藏

2d7450b789073d5252494c2b0e85c326.gif

点点赞

d9665b3fccb1380c8eb679f4f931d1b6.gif

点在看

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

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

相关文章

Flink集成Iceberg在同程艺龙的实践

简介&#xff1a; 本文由同城艺龙大数据开发工程师张军分享&#xff0c;主要介绍同城艺龙 Flink 集成 Iceberg 的生产实践。 本文由同城艺龙大数据开发工程师张军分享&#xff0c;主要介绍同城艺龙 Flink 集成 Iiceberg 的生产实践。内容包括&#xff1a; 背景及痛点Flink Ice…

阿里巴巴开源容器镜像加速技术

简介&#xff1a; 近日阿里巴巴开源了其云原生容器镜像加速技术&#xff0c;其推出的overlaybd镜像格式&#xff0c;相比于传统的分层tar包文件格式&#xff0c;实现了基于网络的按需读取&#xff0c;从而使得容器可以快速启动。 近日阿里巴巴开源了其云原生容器镜像加速技术&…

Unity重写Inspector简化分组配置文件

Unity重写Inspector简化分组配置文件 重写Inspector创建分组管理配置文件创建修改参数参数对应类工程在我的资源中名为CreateConfig&#xff0c;免费下载 重写Inspector创建分组管理配置文件 创建 修改参数 参数对应类 using UnityEngine;public class GameConfig : Scriptab…

985大学的高材生只会写代码片段,丢人吗?

很多同学在学习编程的时候都会遇到各种各样的难题&#xff0c;比如&#xff1a;没有合适的资料、学习过于碎片化、资料的质量层次不齐、看了很多视频自己动手时却还是不会、接触不到完整项目、无法检测自己的编程水平是不是企业所认可的……最近&#xff0c;小郭和小解同学也遇…

快手基于RocketMQ的在线消息系统建设实践

简介&#xff1a; 快手需要建设一个主要面向在线业务的消息系统作为 Kafka 的补充&#xff0c;低延迟、高并发、高可用、高可靠的分布式消息中间件 RocketMQ 正是我们所需的。 作者&#xff1a;黄理 黄理&#xff0c;10多年软件开发和架构经验&#xff0c;热衷于代码和性能优…

基于 RocketMQ Prometheus Exporter 打造定制化 DevOps 平台

简介&#xff1a; 本文将对 RocketMQ-Exporter 的设计实现做一个简单的介绍&#xff0c;读者可通过本文了解到 RocketMQ-Exporter 的实现过程&#xff0c;以及通过 RocketMQ-Exporter 来搭建自己的 RocketMQ 监控系统。RocketMQ 在线可交互教程现已登录知行动手实验室&#xff…

c语言结构体函数平面向量加法公式,插值 拟合 符号变量与符号表达式 微积分 解方程 向量运算...

7.1.1 分段线性插值所谓分段线性插值就是通过插值点用折线段连接起来逼近原曲线&#xff0c;这也是计算机绘制图形的基本原理。实现分段线性插值不需编制函数程序&#xff0c;MATLAB自身提供了内部函数interp1其主要用法如下&#xff1a;interp1(x,y,xi) 一维插值◆ yiinterp1(…

Redis 很屌,不懂使用规范就糟蹋了

作者 | 码哥 来源 | 码哥字节❝这可能是最中肯的 Redis 使用规范了一网友昨天和我说&#xff0c;公司凌晨 12 点之后&#xff0c;网站用户量暴增&#xff0c;出现了一个技术故障&#xff0c;用户无法下单&#xff0c;当时老大火冒三丈&#xff01;经过查找发现 Redis 报 C…

python统计字符在文件中出现的次数_一文搞定统计字符串中某字符出现的频次

下面是统计字符串中某字符出现的次数的方法 方法1&#xff1a; 这个方法相当简单&#xff0c;零基础自学编程&#xff0c;代码写成这样能满足需求&#xff0c;但它逐个逐个计数&#xff0c;比较笨拙。rlt {} for i in content: if i in rlt.keys(): rlt[i] 1 else: rlt[i] 1…

深度 | 数据仓库分层存储技术揭秘

简介&#xff1a; 作者&#xff1a; 沄浩、士远 一 、背景 据IDC发布的《数据时代2025》报告显示&#xff0c;全球每年产生的数据将从2018年的33ZB增长到2025年的175ZB&#xff0c;平均每天约产生491EB数据。随着数据量的不断增长&#xff0c;数据存储成本成为企业IT预算的重…

android tab 切换动画,Android之ViewPager+TabLayout组合实现导航条切换效果(微信和QQ底部多标签切换)...

前言之前在另外一篇中用Fragment和button实现了点击切换Fragment的效果&#xff0c;比较简陋。这次改用ViewPagerTabLayout 实现联动的效果。实现效果ViewPager 多个页面滑动TabLayout 和 ViewPager绑定&#xff0c;实现Fragment和标签绑定TabLayout的自定义标签以及选中颜色改…

5G 和云原生时代的技术下半场,视频化是最大最新的确定性

简介&#xff1a; 随着 5G/ 芯片 / 区块链等等新技术的不断成熟、云计算的普及和云原生时代带来的诸多便捷&#xff0c;开发者和架构师们眼前的挑战也不再只是 0-1 的建设问题&#xff0c;技术如何更多地带来业务价值成为了一个值得讨论的话题。阿里巴巴集团研究员&#xff0c;…

linux unzip命令不存在_15个常用基础命令Linux(很多人不知道!)

Linux 是码农最常用的的OS&#xff0c;很多操作都是命令行&#xff0c;所以很有必要熟练和理解其中一些重要的命令。这里会介绍一些。这里讲的所有都基于bash,mac也可以使用。!!这件事发生了几次&#xff1f; 输入并运行一条长命令后&#xff0c;您发现您忘记在开头添加sudo。 …

云安全的新战场上,要靠什么来抵御威胁

当谈及安全产业&#xff0c;你脑海里能够想到哪些事情&#xff1f;是红黑大战的攻防演练&#xff0c;还是PC上的各种安全软件&#xff1f;事实上&#xff0c;安全的范围远超我们的想象&#xff0c;安全产业也一直在背后&#xff0c;默默的保护在互联网生活的周围。 互联网的发…

函数计算助力高德地图平稳支撑亿级流量高峰

简介&#xff1a; 2020 年的“十一出行节”期间&#xff0c;高德地图创造了记录 ——截止 2020 年 10 月 1 日 13 时 27 分 27 秒&#xff0c;高德地图当日活跃用户突破 1 亿&#xff0c;比 2019 年 10 月 1 日提前 3 时 41 分达成此记录。 期间&#xff0c;Serverless 作为其中…

阿里云李克:阿里云边缘云计算的技术和实践

简介&#xff1a; 李克&#xff1a;边缘计算的核心目标是推动人、事、物的快速决策。 在4月7日下午举办的边缘计算论坛上&#xff0c;阿里云资深技术专家李克为我们带来了《阿里云边缘云计算的技术和实践》为题的精彩演讲。 备受关注的2021全球分布式云大会北京站于4月7日隆重…

数学在左,人生在右

在人们印象中&#xff0c;数学作为一门基础学科&#xff0c;由简单的数字和符号组成或简单或复杂的算式&#xff0c;融入我们的生活、学习、工作的方方面面&#xff0c;是理性、严谨的。 然而笔者在 2021 阿里巴巴全球数学竞赛颁奖典礼上看到数学的另一面&#xff1a;在数学的…

函数计算助力语雀构建稳定且安全的业务架构

简介&#xff1a; 语雀是一个专业的云端知识库&#xff0c;用于团队的文档协作。现在已是阿里员工进行文档编写和知识沉淀的标配&#xff0c;并于 2018 年开始对外提供服务。 客户介绍 语雀是一个专业的云端知识库&#xff0c;用于团队的文档协作。现在已是阿里员工进行文档编…

android menu自定义,Android提高之自定义Menu(TabMenu)实现方法

一般使用过UCWEB-Android版的人都应该对其特殊的menu有一定的印象&#xff0c;把menu做成Tab-Menu(支持分页的Menu)&#xff0c;可以容纳比Android传统的menu更丰富的内容(Android的menu超过6项则缩略在[更多]里)&#xff0c;本文参考网上的例子的基础上对例子进行简化以及封装…

一行指令造成 60 亿美元蒸发,更让 Facebook 遭遇史诗级故障!

作者 | 马超 责编 | 张红月出品 | CSDN弱小从来不是生存的障碍&#xff0c;傲慢才是。10月4日 FaceBook 发生了一次史诗级中断事故&#xff0c;故障期间 FaceBook 所有旗下APP全面对外服务中断&#xff0c;而且故障的时间长达7个小时之久。根据 Facebook 最新的声明来看&…