LWN: 对文件打洞,以及与page-cache操作的冲突!

关注了就能看到更多这么棒的文章哦~

Hole-punching races against page-cache filling

By Jonathan Corbet
July 29, 2021
DeepL assisted translation
https://lwn.net/Articles/864363/

文件系统的开发者在很多事情上都会意见相左,但他们在一件事情上基本上态度一致:不喜欢 truncate() 系统调用,这个系统调用可以将数据从文件的尾部截断。实现 truncate() 功能的时候往往充满了细微的陷阱——还都是那种可能会导致数据丢失的问题。但事实证明,另一个名为 "hole punching" 的类似功能实际上会更麻烦。多年来,在许多文件系统中实现的这个功能都存在着一些难以碰到、但却真实存在的 race condition 。来自 Jan Kara 的一组 patch 可能终于能够实现让这些 hole punching 功能不再有漏洞的目标。

hole punching (打洞),顾名思义就是在文件中间创建一个空洞,这是通过 fallocate() 系统调用中的 FALLOC_FL_PUNCH_HOLE 这个选项来调用的。调用者需要提供一个 offset 和相应的 length ,然后内核负责从提供的 offset 这个位置开始擦除文件中给定 length 字节数的内容。底层存储设备上的相关 block 就可以被释放出来用于其他用途了。不过,文件的长度本身并没有改变。这个操作实际上创建了一个空洞,在读取时将返回 0。从本质上讲,这是一种向文件中的指定范围写 0 的有效方法。

注意,offset 和 length 可能都不是按 page 对齐的。如果空洞的开头和结尾部分有不按 page 对其的一小段区域的话,那么内核就会在这部分位置上写 0。这个边界条件的处理工作本质上只是调用几次 write() 而已。不过,hole punching 功能的效率之所以高,主要就是因为它能够直接从文件中删除一些完整的 page 而不用写任何东西进去。自然,这也是难于处理的地方。

为了实现对这些完整的 page 进行 whole punching ,文件系统(至少)就必须做两件事情:从 page cache 中删除相关 page,还要释放存储设备上的 block。如果不做完这两件事的话就会在文件中留下旧的数据,完全无法满足用户空间的请求。但是,即使这两个动作都正确执行了,那么还有另一种可能性会导致出错,那就是在目前的内核中,并没有任何可以将这两个操作绑定在一起,按照原子操作来完成这两件事,这就意味着在两者之间可能会有其他事件发生。

具体来说,可能文件系统正在按照通常的方式从 page cache 中清除掉这些 page 的时候,跟另一个试图访问同一文件的 task 产生了竞争。如果另一个 task 访问到了文件中的那些正在进行 hole punching 的 page,那么在文件系统完成清理磁盘上的 block 的动作之前,这些 page 就会重新出现在 page cache 中,导致 page cache 中留下的还是过时的旧数据,这些信息可能会在未来的某个时间被重新写入外设,人们肯定不会对相关结果表示满意的,例如看到旧数据一直存在、不相关的数据的被暴露出去、或文件系统发生了损坏。这肯定会导致用户对系统整体不再那么信任了。

这个 race condition 显然很难碰到,否则十年前在 2.6.38 中加入 hole punching 功能之后就会有大量的数据损坏的报告出来了。但它确实是一个真正存在的 race condition,迟早会伤到人们的。它需要得到 fix。为了能正确地实现这个 fix,Kara 自 2021 年初以来提出了十个版本的 patch set(算上最新一版)。

解决方案的概念很简单:文件系统必须实现一种锁,来防止 hole punching 和 page cache entry 创建动作同时发生。但在文件系统领域内,"simple" 和 "locking" 这两个词简直就是一对矛盾。在这种情况下,通常用来保证 page cache 相关 page 操作顺序执行的 lock 无法直接使用,因为这些 page 都应该是不存在的。使用其他的现成的 lock 的话,就会遇到 locking-order 问题。所以 Kara 不得不在 address_space structure(描述了 page cache 和 file 的对应关系)中添加一个新的锁(具体来说是一个 reader-writer semaphore)。这个锁被称为 invalidate_lock,可以保护创建 page-cache page 的操作(对锁来说是 reader 角色),不会与那些对 page 和底层存储进行 invalidate 的操作(writer 角色)相竞争。

每个文件系统使用这个锁的方式会有些不同,因为这取决于它们的内部结构,但最终的效果是相同的:在那些早已支持 hole punching 的文件系统中,基本上不再有 race condition 问题。不过有几个例外,主要是 GFS 和 OCFS2 cluster 文件系统,那里的逻辑都更复杂,需要维护者的直接参与。这些文件系统中的 fix 仍在开发中。

人们认为这项工作已经算是完成了,也推送给 Linus Torvalds 希望合入 5.14,但 Torvalds 并不这么想。他回答说:"我不可能合并这么差的一个功能"。他对这里使用的新的锁很不满意,因为这样依赖,哪怕相关的 page 已经存在于 page cache 中所以不需要重新创建的情况下,也还要先去获取到这把锁。在 page cache 中寻找 page 是内核里最关键的功能之一,所以人们非常不希望在那里增加不必要的开销。修复这个问题需要对 patch set 再修改一轮,并且等待下次合并窗口开启的时候才可能合入了。

现在看来,这项工作已经准备好进入 5.15 版本了。一旦 5.15 版本得到发布,这个特殊的、很难碰到的 race condition 就不会再存在了。尽管这个问题看起来确实很难发生,但只要人们对其稳定性有足够的信心的话,就很可能会看到看到这项工作被 backport 到旧版本的内核上。这一定会帮助那些进行了 hole-punch 的文件,确保它们仍然是完整无缺的。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值