“匿名句柄” 是一切皆文件背后功臣……
作者 | 奇伢
来源 | 奇伢云存储
匿名 fd 的样子?
我们经常在 /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 。
匿名 inode 的诞生?
重点提一下匿名 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 关联即可。
原理剖析
1 anon_inodefs 的初始化
上面提到了,匿名 inode 是一个公共需求,我们不需要一个完整功能的 inode,而只是需要一个 inode 而已,绑定到到 dentry ,file 等结构体。
anon_inodes.c 用来创建一个绑定匿名 inode 的 file 结构体。
整个 anon_inodefs 就只有一个文件,操作系统初始化的时候会调用初始化函数 fs_initcall(anon_inode_init) ,其中 anon_inode_init 只做两件事:
创建出一个 vfsmount 实例,创建出来之后赋值给全局变量 anon_inode_mnt ;
创建出一个 inode 实例,创建出来之后赋值给全局变量 anon_inode_inode ;
这两个变量就是 anon_inodefs 这个文件系统的全部家当了。
2 anon_inodefs 的做了啥?
anon_inodefs 只提供了 2 个实用函数,一个获取到一个绑定匿名 inode 的 file 实例,另一个更多一些封装,返回的是 fd 句柄。如下:
anon_inode_getfile
这个函数非常简单,只做两件事:
获取一个 inode ( 获取全局的 inode 变量 anon_inode_inode ,当然也可以通过一个参数控制来创建新的 inode );
创建一个 file 结构体实例,并且把这个 inode 关联起来;
anon_inode_getfd
这个函数非常简单,只做两件事情:
创建一个新的 fd 句柄,返回的是一个非负整数;
创建一个 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 节点。
换句话说,一个 inode 可以出现在目录树的多个位置。
每个文件或者目录都会在这棵树上有自己的位置,内存用 struct path 结构体来表示唯一的位置。
struct path {struct vfsmount *mnt; // 标识在哪个具体的文件系统实例struct dentry *dentry; // 内存目录树节点
};
这里顺便再说另一个重要知识点:为什么内核之中,需要用 struct path 这个复合结构体来标识唯一的一个目录树位置呢?
文件系统的挂载最关键的就是把一个文件系统的实例和目录树上的一个 dentry 关联起来,而一个 dentry 可以关联多个文件系统实例。
换句话说:对于一个目录树路径其实是可以挂载多个文件系统实例。比如 /mnt/path 这么一个路径,其实是可以挂载多个文件系统的,不会报错,后面的挂载直接覆盖前面的。
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 等等,还有很多,但比较偏僻了,就不再举例了。童鞋们惊讶吗?
总结
anon_inodefs 是为了公共需求抽离出来的一个内核文件系统,只有一个 inode ,为了节省内存,抽象重复代码之用;
匿名句柄是因为 fd 对应的 file 实例背靠着的是匿名 inode ,anon_inodefs 提供了两个功能函数,都是用来获取匿名 fd 的;
inode 上可以挂多个 dentry 节点,换句话说,一个 inode 可以出现在 Linux 目录树的多个位置;
dentry 对应目录树的一个节点位置,最直观的是对应 path 路径的一个位置;
一个挂载路径可以挂多个文件系统实例,后面的覆盖前面的,所以光靠 dentry 无法唯一定位一个“文件”,Linux 内核才用两元组 < vfsmount, dentry > 来唯一定位一个“文件”;
往期推荐
“5G+AI”到底有啥用?
云原生时代,底层性能如何调优?
普通大学生的Java什么水平可以进大厂
只因“薪水过高”!被欠薪三个月后遭解雇
点分享
点收藏
点点赞
点在看
“匿名句柄” 是一切皆文件背后功臣……相关推荐
- 一切皆文件的编程思想
一切皆文件的编程思想和通信的思想类似,都是建立联系然后进行操作. 在常规的文件操作中,这个联系表现为文件的句柄: 在网络通信中,这个联系表现为socket连接: 在变量操作中,这个联系表现为变量和指针 ...
- 实例演绎Unix/Linux的一切皆文件思想
大家习惯了使用socket来编写网络程序,socket是网络编程事实上的标准. 我们知道,在Unix/Linux系统中"一切皆文件",socket也被认为是一种文件,socket被 ...
- Linux socket文件系统体现“一切皆文件”
来自<实例演绎Unix/Linux的"一切皆文件"思想> 大家习惯了使用socket来编写网络程序,socket是网络编程事实上的标准. 我们知道,在Unix/Linu ...
- Linux系统下一切皆文件,socket编程浅析
"一切皆Socket!" 话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket. --有感于实际编程和开源项目研究. 我们深谙信息交流的价值,那网络中进程之间如何通信 ...
- Linux中一切皆文件
Linux中一切皆文件 1. Linux中所有内容都是以文件的形式保存和管理,即:一切皆文件. 普通文件是文件. 目录(在win下称为文件夹)是文件. 硬件设备(键盘.硬盘.打印机)是文件. 套接字( ...
- linux一切对象皆文件,为什么说Linux下“一切皆文件”?
前言:接触Linux的同志们都听过一句话:"Linux下一切皆文件"."一切皆是文件"是 Unix/Linux 的基本哲学之一,那么为什么Linux在一切皆文件 ...
- linux一切皆是文件_Linux 的虚拟文件系统(真正理解“一切皆文件”)
1,引言 Linux 中允许众多不同的文件系统共存,如 ext2, ext3, vfat 等.通过使用同一套文件 I/O 系统 调用即可对 Linux 中的任意文件进行操作而无需考虑其所在的具体文件系 ...
- Linux 一切皆文件认知
Linux的一切皆文件 一切都可看作是文件,其最显著的好处是对于上面所列出的输入/输出资源,只需要相同的一套 Linux 工具.实用程序和 API.你可以使用同一套api(read, write)和工 ...
- lsof 一切皆文件
lsof 一切皆文件 Docs » 工具参考篇 » 3. lsof 一切皆文件 Docs » 工具参考篇 » 3. lsof 一切皆文件 Edit on GitHub 3. lsof 一切皆文件¶ l ...
最新文章
- TensorFlow基础10-(误差反向传播算法以及实现多层神经网络)
- 4.3 偏差与方差-机器学习笔记-斯坦福吴恩达教授
- php博客添加live2d,在博客中增加自己的live2d纸片人模型方法
- JAVA中File转MultiparFile
- 安装配置 TensorFlow on Android
- 第三周 分队列 mooc 翁恺 c语言
- 重读模式与架构(2)——层次划分的依据和角色职责
- 【软件测试】单元测试不属于动态测试
- 来到深圳奋斗的这些年(不断更新!)
- Opencv之获取边缘和画轮廓
- HBase MapReduce实例分析
- 论文编写的9个实用软件
- python数据科学手册pdf中文版百度云,Python数据科学手册 英文pdf源码
- protues仿真51单片机驱动继电器
- 语音计算机怎么切换音乐模式,如何把微信里收藏的语音音乐转换成mp3格式?
- 微信公众号开发模式没有域名怎么办?申请免费域名
- 基于深度学习的图像压缩
- 简单差分放大器和套筒式共源共栅放大器Cadence仿真
- 如何快速提升文章阅读量?
- 问题解决-----如何从windows10向ubuntu传输大数据的文件(大于等于50M)
热门文章
- dumpbin发现没有入口函数_JavaScript基础之入口函数-2020版
- linux lvm lv扩充--虚拟机,虚拟机新增磁盘后lvm下的lv扩容
- java代码ftp重命名未生效_java使用apache commons连接ftp修改ftp文件名失败原因
- xxljob 配置文件_最详细的xxl-job java配置方式spring-boot
- scripts文件夹_常用Scripts整理
- linux 命令 语法,linux常用命令及语法
- 10个趣味的物理与化学动图欣赏,看过直称神奇!
- centos普通用户修改文件权限_Linux实战014:Centos创建用户并添加root授权
- hbase/thrift/go连接失败
- 如何做科研20171206