steve kleiman 在 1986 年撰写了《vnodes: an architecture for multiple file system types in sun unix[3]》一文。这篇论文幅较短,大部分内容是数据结构的列举,以及 c 语言结构之间相互指向的图表。
steve kleiman是分布式文件系统领域的专家,在 sun microsystem 工作了多年,曾参与开发 sun network file system(nfs)等项目,为分布式文件系统领域做出了重要贡献。
kleiman 希望在 unix 中能够拥有多个文件系统,并希望这些文件系统能够共享接口和内存。具体而言,他希望设计一个能够提供以下功能的架构:
一个可以支持多个实现的通用接口;支持 bsd ffs,以及两个远程文件系统 nfs 和 rfs,还有特定的非 unix 文件系统,如 ms-dos;接口定义的操作需要是原子性的。
并且,能够在不影响性能的情况下动态地处理内存和数据结构,支持重入(reentrant) 和多核,并且具有一定面向对象进行编程的特性。
重入(reentrant) 是指程序或子程序在尚未完成上一次调用之前,可以再次被调用且不会出错或发生冲突。
两个抽象概念
steven 研究了文件系统的各种操作,决定将他们抽象为两个概念:
• vfs,虚拟文件系统,代表文件系统
• vnode,虚拟 inode,代表文件
vfs,虚拟文件系统,它提供统一的接口,使操作系统可以以一致的方式访问不同的文件系统,无论是本地文件系统还是网络文件系统。
vnode,虚拟 inode, 表示一个文件,每个文件都有一个相关联的索引节点,其中包含了文件的元数据(如文件权限、所有者、大小等)以及指向文件数据存储位置的指针。
采用了 c++风格(实际使用 c 语言),每一个类型会匹配一个虚函数表,通过虚函数表,系统在运行时根据对象的实际类型来调用适当的虚函数,实现动态绑定:
• 对于 vfs 类型,其虚函数表 struct vfsops,包含了一系列的函数指针,用来执行诸如 mount、unmount、sync 和 vget 等操作。在论文的后面,会解释这些函数的原型和功能;
• 对于 vnode 类型也是类似的,其虚函数表 struct vnodeops,包含 open、rdwr 和 close 等函数,还有create、unlink 和 rename 等函数。一些函数是针对特定的文件类型的,比如 readlink、mkdir、readdir 和 rmdir。
通过 vfs 对象来进行跟踪实际的挂载,其虚函数表 struct vfsops 指向适用于该特定子树的文件系统操作。
类似地,vnode 实例用来进行跟踪打开的文件。它包含 struct *vnodeops 指针,作为 vfs 的一部分,有指针 struct *vfs 指向文件系统实例。
vfs 和 vnode 这两个结构体都需要一些用于存储特定实现数据的字段(如“子类私有字段”)。他们都以 caddr_t ...data 指针结尾。这些私有数据并不是 vfs 和 vnode 的一部分,而是位于其他位置,并通过指针进行引用。
vnodes 实操
在论文中,有一整页的内容专门用于展示各种相互指向的结构。乍一看可能会感到困惑,但一旦追踪下来,就会发现它非常直观和优雅。
kleiman 详细解释了如何使用 lookuppn() 函数来解释事物的工作原理,该函数替代了传统 unix 中的 namei() 函数。类似于 namei() ,这个函数接受一个路径,并返回表示该路径所代表的 vnode 的 struct vnode 指针。
路径遍历始于根 vnode 或当前进程的当前目录 vnode,具体取决于路径的第一个字符是否为 /。
然后,这个函数会依次取出路径的每一个子项,并调用当前 vnode 的 lookup 函数,它接受一个路径子项和一个假设是目录的当前 vnode,并返回代表那个子项的 vnode。
当一个目录是个挂载点,它的 vfsmountedhere 会被设置为一个指向 struct vfs 的指针。lookuppn 函数会跟随这个指针,并调用 vfs 的根函数,以获取该文件系统的根 vnode,替换当前正在处理的 vnode。
反过来也是可能的:当解析父目录(.. )时,如果当前 vnode 的 flags 字段中设置了根标志,我们会跟随 vfsmountedhere 指针从当前 vnode 到 vfs。然后,我们可以使用该 vfs 中的 vnodecovered 字段来获取上层文件系统的 vnode。
无论如何,在成功完成后,会返回一个 struct vnode 指针,即所使用的路径。
新增的系统调用
为了使系统高效地运行,需要添加一些新的系统调用来完善接口。
在 unix 的历史中,我们看到引入了 statsfs 和 fstatsfs ,通过这两个函数可以获得与用户空间中的文件系统进行交互的接口。getdirentries 函数可以让用户一次性获取多个目录条目(取决于提供的缓冲区大小),这大大加快了远程文件系统的目录读取速度。
在 linux 系统中
通过查看 linux 内核源代码,我们可以找到 kleiman 设计的总体结构,尽管 linux 内核的复杂性和丰富性掩盖了其中大部分内容。linux 内核拥有丰富的文件系统类型,并且还添加了许多在 40 年前的 bsd 中不存在的功能。因此,我们可以找到更多的数据结构和系统调用,它们被用于实现命名空间、配额、属性、只读模式、目录名称缓存等功能。
文件
如果你仔细观察,原始的结构仍然可以找到:linux 内存中的文件相关结构分为两部分,一个是已打开的文件,它是一个带有当前位置的 inode;另一个是 inode,它代表整个文件。
我们可以在此处找到文件对象[4],struct file 的实例。在文件的所有其他内容中,最值得注意的是一个字段 loff_t f_pos,它表示文件当前位置距离文件起始位置的偏移量(以字节为单位)。
文件的类[5]是通过一个虚函数表来定义。我们可以找到一个指针 struct file_operations *f_op 。它展示了文件可以执行的所有操作,其中最常见的是打开(open)、关闭(close)、定位(lseek)、读取(read)和写入(write)。
文件还包含指向 inode 的指针,即 struct inode *f_inode。
索引节点
对于不需要偏移量的文件操作,它们是针对整个文件进行的,定义为 struct inode *。
查看此处[6]的定义。我们可以看到这里还有其他的定义,40 年前的 bsd 中没有类似的定义,比如 acl(访问控制列表)和属性(attributes)。
我们发现 inode 的类[7]通过虚函数表来定义,即 struct inode_operations *i_op。同样的,这其中很多函数涉及新特性,比如 acl(访问控制列表)和扩展属性,但我们也会找到我们期望的功能,比如链接(link)、删除(unlink)、重命名(rename)等。
inode 还包含一个指向文件系统的指针,即 struct super_block *i_sb。
超级块
挂载点用 struct super_block 来表示,在此处查看其定义。同样地,它有 struct super_operations *s_op 定义的各个操作,在此处[8]查看其定义。
支持的文件系统不再有限,可以通过内核模块动态地添加新的文件系统,通过数据结构 struct file_system_type 来表示,它只有一个用于创建 superblock 的工厂函数 mount。
小结
unix 发生了变化。它的运行时变得更加复杂,增加了许多新的功能,并增加了系统调用。系统变得更有结构。
但是,由 steve kleiman 和 bill joy(bsd 操作系统的共同创始人之一) 构思的原始设计和数据结构仍然存在,在当前的 linux 系统中仍然可以找到,虽然已经过去了 40 年。
Eurocom推出两款新的17.3英寸桌面替代性笔记本 最高可选配28TB SSD
IBM的区块链项目是真还是假?
摩托罗拉手机国外市场表现优异,在美国迎来复苏
红米7今日正式首发 AGM的全新AGM X3 Turbo亮相
ne5532电路图全集
如何支持多个文件系统
关于一加5:一个好消息和一个坏消息
高压精密运算放大器RS862X系列简述
三大芯片种类的详细介绍
华为助推中国计算产业加速发展
rtu和plc的区别是什么
斥资10亿欧元!博世首个智能物联网晶圆厂落成开业
电学感抗实验的心得体会
锐龙3 2300X上架更多市场 马来西亚售价约合484元人民币
华星光电大尺寸面板累计销量数据分析
阿里云安全系统,云原生安全定义下一代安全架构
5月VR/AR行业融资数据分析报告
小米6将黑科技都整全了? 陶瓷机身、虹膜识别、无线充电、防水
一加手机5通过认证:外观配置惊喜,或正面叫板小米6
分享五款可穿戴设备的性能分析和介绍