LWN: Btrfs inode号问题的解决方案!
共 5106字,需浏览 11分钟
·
2021-09-12 11:41
关注了就能看到更多这么棒的文章哦~
The Btrfs inode-number epic (part 2: solutions)
By Jonathan Corbet
August 23, 2021
DeepL assisted translation
https://lwn.net/Articles/866709/
本系列文章的上一篇探讨了当包含 subvolume 的 Btrfs 文件系统通过 NFS export 使用时出现的困境。Btrfs 有几个很特别的行为,使得这种情况下的处理变得更复杂:为 subvolume 使用了单独的设备号,以及整个文件系统并没有进行唯一 inode 编号。最近,Neil Brown 开始致力于解决这些问题,结果发现情况比预料中的还要困难,需要进行很多尝试。
Take 1: internal mounts
Brown 的第一组 patch set 打算创造一个 "internal mounts" 的概念来解决这些问题。Btrfs 会为用户可见的每个 subvolume 自动创建所谓的 internal mount。这个自动 mount 机制就可以让这些 mount 节点正常出现。进行一些调整之后,内核的 NFS 服务器守护程序(nfsd)就可以识别出这些特殊的 mount,不需要 NFS 远端显式进行 mount 操作,就可以正常使用了。完成这个操作之后,系统显示的设备号就和预期的保持了一致,并且 inode 号在 mount 中就是唯一确定的了。
乍一看,这套 patch set 似乎是解决问题的好办法。早在 7 月份,当有人向他介绍这种方法时,文件系统开发者 Christoph Hellwig 回应说。"这是我多年来一直想要的方案"。进行了这些改动之后,Btrfs 看起来就不那么奇怪了,一些长期存在的问题也终于得到了解决。
不过,这套 patch set 很快就遇到了麻烦。Al Viro 指出,用来查询设备号的这个机制中,可能会在持有一个不允许进行 I/O 的锁时触发 I/O 操作,从而使得系统死锁。而如果没有这个查询机制,那么我们就无法从文件系统中获得设备号。这里有个潜在的替代方案,那就是为了每个 internal mount 都单独提供一个包含了所需信息 superblock,不过这个方案更糟糕。内核的虚拟文件系统层中的许多操作都需要去遍历所有 mount 上来的 superblock 的列表。为 Btrfs subvolume 增加数以千计的 superblock 会导致许多性能问题出现,需要大量的改动才能解决。
此外,Amir Goldstein 指出,这种新增的 mount structure 可能会给 overlayfs 带来麻烦。同时这也会导致他开发的一些用户空间工具无法正常工作了。还有一个小问题,那就是所有这些 internal mount 将如何在/proc/mounts 中展示。在有大量 subvolume 的系统中,这会导致 /proc/mounts 变成了一个非常混乱的东西,同时也可能暴露出其他那些 private subvolume 的名称。
Take 2: file handles
Brown 总结说:"在目前的框架下这个问题是无法解决的"。具体来说,问题在于为 inode 号预留的 64 位格式,对现在的 Btrfs 来说也是完全不够的。这个问题在 overlayfs 中就变得更糟了,由于需要整合多个文件系统的 inode 号,那么最终产物的编号数量必然会大于任何一个文件系统中的数量。Brown 将目前 overlayfs 的解决方案简述了一下:"它重用了这些高位 bit,寄希望于文件系统不会使用到",这个想法不太现实。但是,只要 inode 编号被限制在某个固定大小之内的话,就没有办法解决这个问题,这是他的看法。
他继续说,最好是使用许多文件系统中都提供的 file handle 文件句柄,这主要用于 NFS。某个文件的句柄可以通过 name_to_handle_at() 来获得。句柄的 length 可以是任意数量,它包括了一个 generation number,这就很好地解决了文件被删除时的 inode-number 被重复使用的问题。如果用户空间使用 handle 句柄而不是 inode 号来判断两个文件是否相同,那么很多问题都会直接消失了。
当然,又会出现一些新的问题,那就是需要对用户空间接口和程序要进行许多修改了。目前那些内核 export 出来的文件(例如/proc 下的那些文件)都不使用 handle 句柄,所以必须先创建一组新文件,其中包含 handle 句柄。任何查看 inode 号的程序都必须要进行更改。这就会导致大量的用户空间工具都被破坏。Brown 多次重申,他认为这样做可能是可以接受的(而且是必要的):
如果你拒绝冒着破坏现有东西的风险,那么你就无法取得进展。只要人们可以选择好这个破坏会在何时发生,并且进行提前警告提醒,那么他们往往可以很好地应付过来。
不过,导致兼容性问题的改动仍然是很难让人们接受的。除此之外,要想让这个改动达到全部目的,那么就还需要修改 Btrfs,停止使用人工生成的设备号用在 subvolume 上,这也是一个不小的改动。而且,正如 Viro 所指出的,两个不同的文件句柄 handle 有可能会是指向同一文件的。
综上所述,这种方法也没有赢得胜利。
Take 3: mount options
Brown 的第三次尝试则是从一个不同的方向来解决这个问题,那就是让所有的改变都可以是明确选择打开或者关闭的。具体来说,他为 Btrfs 文件系统增加了两个新的 mount option,用来改变文件系统在 inode 和设备号这两方面的行为。
第一个选项是 inumbits= ,它改变了 inode 号的显示方式。默认值零,会导致使用 internal object ID(正如目前 Btrfs 的情况)。而非零值就是告诉 Btrfs 要去生成 "大部分情况下是唯一的" 并且能符合指定的 bit 数限制的 inode 号。具体来说,为了在 subvolume 中为某个特定对象生成 inode 号的时候,Btrfs 会这样做:
根据 subvolume 号来生成一个 "overlay" 值,这是通过对 inode 编号进行 byte-swapping 来得到的,这样一来这些 low-order bit(在各个 subvolume 之间差异最大的那些 bit)就在最高位了(most-significant bit)。
overlay 值会被右移,从而适应 inumbits= 所指定的位数。如果这个参数是 64 的话就不需要进行移位了。
然后,用 overlay 值与 object number 进行 XOR,从而生成用户空间最终看到的 inode 编号。
这样生成的 inode 编号在 subvolume 内部仍然是唯一的,而在一个规模更大的 Btrfs 文件系统中,虽然仍然可能会出现编号碰撞(也就是出现相同的 inode 号),但出现概率会更加小。设置 inumbits=64 的话就可以最大限度地减少 inode 号重复的可能,但是在一些场景下(比如 overlayfs 场景)最高位的这几个 bit 会被其他子系统用到,因此更应该使用一个比较小的数量(比如 56)。
第二个 mount option 是 numdevs= ,它用来控制文件系统中会使用多少个设备号来代表 subvolume。默认值是 many,也就是仍然保留原来的行为,为每个 subvolume 都分配一个单独的设备号。而设置 numdevs=1 的话则会使所有 subvolume 都共用同一个设备号。如果使用这个选项 mount 了一个文件系统的时候,像 find 和 du 这样的工具就会无法检测出跨越了 subvolume 边界的情况,所以那些工具中原有的一些选项本来可以将范围限制在单一文件系统之内的,现在就无法生效了。同样也可以指定 numdevs=2,这样在从一个 subvolume 移动到下一个 subvolume 时,会替换成另一个设备号。总共有两个设备号会接替使用。这就使得 find 这样的工具能如预期正常工作了。
最后,这组 patch set 还增加了一个 "tree ID" 的概念,可以通过 statx() 系统调用来获取到。Btrfs 会用 subvolume ID 来填入这个查询的反馈之中,然后应用程序就可以用它来可靠地确定两个文件是否包含在同一个 subvolume 之中了。
Btrfs 开发者 Josef Bacik 将这项工作描述为 "朝着正确方向迈出的一步",但他说更希望看到一个不需要特殊挂载选项的解决方案。"mount options 总是很混乱的,而且经常会导致发行版在还不了解它的含义的情况下就打开这些选项,然后我们就不得不永远保持支持了"。他认为一个适当的解决方案就不应该给用户提供可能会做出错误决定的机会。他建议在 nfsd 中使用新增的 tree ID 来来解决 NFS 的具体问题,如果需要的话,也可以自己生成新的 inode 号。
Brown 反驳说,如果不增加 mount 选项的话,还不如直接创建一个新的文件系统类型("btrfs2 或者也许是 betrfs"),来直接使用新的行为。不过 Bacik 也同样不喜欢这个主意。Brown 补充说,他不希望在 nfsd 中对 Btrfs 的 inode 号进行一些 "神奇的转换" 操作。如果一个文件系统需要这样的操作,那么就应该在文件系统本身内来完成,这是他的观点。然后他要求 Btrfs 开发者们决定一下他们解决这个问题的首选方式方式是什么,但没有得到答案。
Take 4: the uniquifier
8 月 13 日,Brown 又回来了,这次提供了一个非常小的 patch,旨在解决引发整个讨论的 NFS 问题。这个 patch 会使文件系统能提供一个与文件相关的 "uniquifier "值。这个名字怎么听起来更加像是个职业摔跤手的名字。。。此值只可以在内核中使用。然后 NFS 服务器可以将该值与文件的 inode 号进行 XOR 操作,得到一个更可能是唯一的数字。Btrfs 会相应地提供上述的 overlay 值来作为这个 uniquifier。nfsd 使用这个值的话,问题(基本上)就消失了。
Bacik 说,这种方法是 "合理的",并代表 Btrfs 文件系统提供了 ack。这样看来,它终于可以成为当前问题的解决方案了。或者至少可以说它更接近最终方案了。Brown 后来意识到,在过渡阶段,这个改动之后的 inode 号会导致当前客户端上出现人们十分畏惧的 "stale file handle "错误。后来他更新了一下 patch set,在 NFS 文件句柄的一个未使用的字节中增加了一个新的 flag,表示这种 "new-style" inode 号,从而防止出现上述错误。
第四次方案的第二个版本可能确实可以让 Btrfs 用户的一些 NFS 相关问题消失了。但这种做法还是像是一种 hack 方式,因为它在内部进行了一些 magic number 处理,并且仍然无法保证能创造出唯一的 inode 号。事实上,在谈到这项工作时,Brown 甚至自己也称之为 "这是一种为了绕过文件系统中的错误功能而提供的 hack 做法"。不过,在应对生产系统和大量用户群体时,这类 hack 的解决方式可能就是生活的一部分了。如果不破坏用户系统,就不可能有更干净、更正确的解决方案了。因此,在出现其他一些严重到足以迫使人们接受一个更具破坏性的解决方案之前,这个 uniquifier 方案可能是大家可以得到的最好结果了。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~