使文件系统可导出¶
概述¶
所有文件系统操作都需要一个 dentry(或两个)作为起点。本地应用程序通过打开的文件描述符或 cwd/root 对合适的 dentries 持有引用计数。然而,通过诸如 NFS 等远程文件系统协议访问文件系统的远程应用程序可能无法持有这样的引用,因此需要另一种方式来引用特定的 dentry。由于作为替代的引用形式需要在重命名、截断和服务器重启(以及其他方面,尽管这些往往是最成问题的)中保持稳定,因此没有像“文件名”这样简单的答案。
这里讨论的机制允许每个文件系统实现指定如何为任何 dentry 生成一个不透明的(在文件系统外部)字节字符串,以及如何为任何给定的不透明字节字符串查找合适的 dentry。这个字节字符串将被称为“文件句柄片段”,因为它对应于 NFS 文件句柄的一部分。
支持文件句柄片段和 dentries 之间映射的文件系统将被称为“可导出的”。
Dcache 问题¶
dcache 通常包含任何给定文件系统树的适当前缀。这意味着如果任何文件系统对象在 dcache 中,则该文件系统对象的所有祖先也在 dcache 中。由于通常通过文件名访问,因此这个前缀是自然创建并易于维护的(通过每个对象维护其父对象的引用计数)。
但是,当通过解释文件句柄片段将对象包含到 dcache 中时,不会自动为该对象创建路径前缀。这导致了 dcache 的两个相关但不同的特性,这些特性不是正常文件系统访问所必需的。
dcache 有时必须包含不属于适当前缀的对象。即,未连接到根的对象。
dcache 必须为新发现的(通过 ->lookup)目录已经具有(未连接的)dentry 做好准备,并且必须能够将该 dentry 移动到位(基于 ->lookup 中的父对象和名称)。这对于目录尤其需要,因为目录只有一个 dentry 是 dcache 的不变性。
为了实现这些特性,dcache 具有:
一个 dentry 标志 DCACHE_DISCONNECTED,该标志设置在任何可能不属于适当前缀的 dentry 上。这在创建匿名 dentries 时设置,并在注意到 dentry 是适当前缀中的 dentry 的子级时清除。如果设置此标志的 dentry 的引用计数变为零,则立即丢弃该 dentry,而不是保留在 dcache 中。如果通过文件句柄反复访问尚未在 dcache 中的 dentry(如 NFSD 可能做的那样),则每次访问都会分配一个新的 dentry,并在访问结束时丢弃。
请注意,这样的 dentry 可以获取子级、名称、祖先等,而不会丢失 DCACHE_DISCONNECTED - 该标志仅在子树成功重新连接到根时清除。在此之前,子树中的 dentries 仅在有引用时才保留;引用计数达到零意味着立即驱逐,与未散列的 dentries 相同。这保证了我们不需要在卸载时查找它们。
一个用于创建辅助根的原始操作 - d_obtain_root(inode)。这些操作_不_带有 DCACHE_DISCONNECTED。它们被放置在每个超级块列表(->s_roots)上,因此可以在卸载时找到它们以便驱逐。
用于分配匿名 dentries 和帮助在查找时附加松散目录 dentries 的辅助例程。它们是:
- d_obtain_alias(inode) 将为给定的 inode 返回一个 dentry。
如果 inode 已经有一个 dentry,则返回其中一个。
如果它没有,则分配并附加一个新的匿名(IS_ROOT 和 DCACHE_DISCONNECTED)dentry。
在目录的情况下,会注意确保只能附加一个 dentry。
- d_splice_alias(inode, dentry) 将在树中引入一个新的 dentry;
如果合适,则传入的 dentry 或给定 inode 的预先存在的别名(例如由 d_obtain_alias 创建的匿名别名)。当使用传入的 dentry 时,它返回 NULL,遵循 ->lookup 的调用约定。
文件系统问题¶
要使文件系统可导出,它必须:
提供下面描述的文件句柄片段例程。
确保在 ->lookup 找到给定父级和名称的 inode 时使用 d_splice_alias 而不是 d_add。
如果 inode 为 NULL,则 d_splice_alias(inode, dentry) 等效于
d_add(dentry, inode), NULL类似地,d_splice_alias(ERR_PTR(err), dentry) = ERR_PTR(err)
通常,->lookup 例程将以一个简单结束
return d_splice_alias(inode, dentry); }
文件系统实现通过在 struct super_block 中设置 s_export_op 字段来声明该文件系统的实例是可导出的。此字段必须指向一个 “struct export_operations” 结构,该结构具有以下成员:
- encode_fh(强制性)
接受一个 dentry 并创建一个文件句柄片段,该片段稍后可用于查找或创建同一对象的 dentry。
- fh_to_dentry(强制性)
给定一个文件句柄片段,它应该找到隐含的对象并为其创建一个 dentry(可能使用 d_obtain_alias)。
- fh_to_parent(可选但强烈建议)
给定一个文件句柄片段,它应该找到隐含对象的父对象并为其创建一个 dentry(可能使用 d_obtain_alias)。如果文件句柄片段太小,则可能会失败。
- get_parent(可选但强烈建议)
当给定目录的 dentry 时,它应该返回父对象的 dentry。父 dentry 很可能是由 d_alloc_anon 分配的。默认的 get_parent 函数只返回一个错误,因此任何需要查找父级的文件句柄查找都将失败。->lookup(“..”) _不_用作默认值,因为它会在 dcache 中留下 “..” 条目,这些条目太混乱而无法使用。
- get_name(可选)
当给定父 dentry 和子 dentry 时,它应该在由父 dentry 标识的目录中找到一个名称,该名称指向由子 dentry 标识的对象。如果没有提供 get_name 函数,则会提供一个默认实现,该实现使用 vfs_readdir 来查找潜在的名称,并匹配 inode 号以找到正确的匹配。
- 标志
某些文件系统可能需要以不同于其他文件系统的方式进行处理。 export_operations 结构还包括一个标志字段,允许文件系统向 nfsd 传达它在处理它时可能希望执行不同的操作。有关更多解释,请参阅下面的“导出操作标志”部分。
文件句柄片段由 1 个或多个 4 字节字的数组以及一个字节的“类型”组成。 decode_fh 例程不应依赖于传递给它的声明大小。这个大小可能大于由 encode_fh 生成的原始文件句柄,在这种情况下,它将用 null 填充。相反,encode_fh 例程应选择一个“类型”,该“类型”指示 decode_fh 文件句柄有多少有效以及应如何解释它。
导出操作标志¶
除了操作向量指针之外,struct export_operations 还包含一个“flags”字段,该字段允许文件系统向 nfsd 传达它可能希望在处理它时执行不同的操作。定义了以下标志:
- EXPORT_OP_NOWCC - 在此文件系统上禁用 NFSv3 WCC 属性
RFC 1813 建议服务器始终在每次操作后向客户端发送弱缓存一致性 (WCC) 数据。服务器应原子地收集有关 inode 的属性,对其执行操作,然后收集之后的属性。这允许客户端在某些情况下跳过发出 GETATTR,但意味着服务器几乎为所有 RPC 调用 vfs_getattr。在某些文件系统(特别是那些集群或网络文件系统)上,这是昂贵的,并且原子性很难保证。此标志向 nfsd 指示,当在此文件系统上执行操作时,它应该跳过在 NFSv3 答复中向客户端提供 WCC 属性。请考虑在具有昂贵的 ->getattr inode 操作的文件系统上启用此功能,或者在无法保证操作前后属性收集之间的原子性时启用此功能。
- EXPORT_OP_NOSUBTREECHK - 禁止在此文件系统上进行子树检查
许多 NFS 操作都处理文件句柄,服务器必须对其进行验证,以确保它们存在于导出的树内。当导出由整个文件系统组成时,这是微不足道的。nfsd 可以只确保文件句柄存在于文件系统上。但是,如果只导出文件系统的一部分,那么 nfsd 必须遍历 inode 的祖先,以确保它在导出的子树内。这是一个昂贵的操作,并非所有文件系统都能正确支持它。此标志免除文件系统进行子树检查,并导致 exportfs 如果尝试在其上启用子树检查,则返回错误。
- EXPORT_OP_CLOSE_BEFORE_UNLINK - 始终在取消链接之前关闭缓存的文件
在某些可导出的文件系统(如 NFS)上,取消链接仍然打开的文件可能会导致相当多的额外工作。例如,NFS 客户端将执行“sillyrename”以确保该文件在仍然打开时保留。重新导出时,该打开的文件由 nfsd 持有,因此我们通常最终会执行 sillyrename,然后在链接计数实际变为零后立即删除 sillyrenamed 文件。有时,此删除可能会与其他操作竞争(例如,rmdir 父目录)。此标志会导致 nfsd _在_调用 vfs 执行取消链接或将替换现有文件的重命名操作之前,为此 inode 关闭所有打开的文件。
- EXPORT_OP_REMOTE_FS - 此文件系统的后备存储是远程的
PF_LOCAL_THROTTLE 存在于回环 NFSD 中,其中一个线程需要写入一个 bdi(最终 bdi),以便释放排队到另一个 bdi(客户端 bdi)的写入。此类线程会获得一个私有的脏页余额,以便客户端 bdi 的脏页不会影响写入最终 bdi 的守护进程。对于持久存储不是本地的文件系统(例如导出的 NFS 文件系统),此约束会产生负面影响。EXPORT_OP_REMOTE_FS 允许导出禁用写回节流。
- EXPORT_OP_NOATOMIC_ATTR - 文件系统不以原子方式更新属性
EXPORT_OP_NOATOMIC_ATTR 表示导出的文件系统无法提供 NFSv4 的 change_info4 中“atomic”布尔值所需的语义。此布尔值向客户端指示返回的前后更改属性是否与请求的元数据操作(UNLINK、OPEN/CREATE、MKDIR 等)原子地获取。
- EXPORT_OP_FLUSH_ON_CLOSE - 文件系统在 close(2) 上刷新文件数据
在大多数文件系统中,inode 可以在文件关闭后仍处于写回状态。NFSD 依赖于客户端活动或本地刷新器线程来处理写回。某些文件系统(例如 NFS)会在最后一次关闭时刷新 inode 的所有脏数据。以这种方式行为的导出应设置 EXPORT_OP_FLUSH_ON_CLOSE,以便 NFSD 知道在关闭此类文件时跳过等待写回。