关注了就能看到更多这么棒的文章哦~
Negative dentries, 20 years later
By Jonathan Corbet
April 11, 2022
DeepL assisted translation
https://lwn.net/Articles/890025/
文件系统和虚拟文件系统层的任务就是管理实际存在的文件,但 Linux 的 "dentry cache" (用来记住文件名查询的结果),也同样会跟踪那些不存在的文件。这个被称为 "negative dentries" 的 cache 在系统的整体性能优化中起到了非常重要的作用,但如果这个 cache 增长得过大的话,它的本身功能就会带来更多消极影响。随着 2022 年 Linux Storage, Filesystem, and Memory-Management Summit(LSFMM)的临近,"negative dentries" 的话题再次被提及;人们目前尚不清楚这次是否真的能解决这个问题。
内核里的 dentry cache 保存了在文件系统中查找一个文件所得到的结果。如果下次需要再查找同一个文件就可以使用 cache 的结果,避免了再次走一遍底层文件系统和访问存储设备。重复查询文件名的情况很常见,比如说 usr/bin/bash 或~.nethackrc 都是例子,所以这是一个重要的优化。
至于为什么要用 negative dentries 来记录那些失败的查找,可能人们就不那么容易一下子理解了。事实上经常出现反复尝试查找一个不存在的文件的情况,比如每次用户输入 "vi" 时,shell 都会遍历 PATH 里面的各个目录(Emacs 用户不会受影响因为他们启动一次编辑器之后就再也不会离开这个舒服的环境)。更常见的情况是 program loader 寻找共享库,或者编译器寻找 include 文件时产生的查询失败。在当今社会中,人们也经常收到建议说需要 "快速失败(fail fast)",在查找那些不存在的文件时,这确实是个好建议。
因此,negative dentries 是个好东西,但正如我们都知道的,过犹不及。虽然正常 dentry 会受到实际存在的文件数量的限制,但对不存在的文件数量却没有什么限制。因此,一个恶意(或根本不知道这里门道的)应用程序很容易就会创造出大量的 negative dentry 。如果内存紧张,内存管理子系统最终会努力将其中一些 negative dentry 回收掉。但是,在没有内存压力的情况下这些 negative dentry 可以无限积累下去,内存总是会被耗尽的,那时就会留下一个大麻烦需要清理了。
有些内核问题很快就能解决,有些则需要更长时间。LWN 早在 2002 年就简要地报道了关于 negative dentry 内存消耗的投诉了,差不多正好是 20 年前。在 2020 年初,LWN 也曾报道过一个最新的解决这个问题的尝试。虽然随着时间的推移,许多开发者已经对 negative dentry 问题进行过尝试了,但核心问题仍然存在。这些 dentry 仍然在占据宝贵的内存,而且它们还可能造成其他问题(如 soft lockup)。
A new discussion
在 3 月中旬 Matthew Wilcox 建议,negative-dentry 问题可以成为一个很好的 LSFMM 主题。"也许对这个问题进行一些集中的头脑风暴可以引出一些真正的解决方案"。通常情况下,仅仅提出这样一个话题就能引起为解决这个问题所需的头脑风暴。这一次没有发生,但它确实导致了 Stephen Brennan 发布了一个 patch set,展示了一个解决这个问题的新方法。
negative-dentry 问题带来的困难之一是很难知道什么时候该开始回收。系统的规模和工作负载千变万化,所以任何一种简单的限制都有可能导致某个场景出现性能 regression。可以考虑为系统管理员提供一个调节这个阈值的开关,但是这只是把问题推给了用户,人们普遍认为内核应该能够自己解决这些问题。但是,正如 Brennan 指出的,这并不容易:
很难通过查看一个 hash bucket 或 LRU list 来设计一个启发式规则从而确定合理的 negative dentry 数量,因为这个方法无法从小型系统轻易扩展到大型系统上去。但是针对每个目录来设置启发式规则,就会更容易扩展,而且更容易推理。
这组 patch 提出的具体启发式方法是,任何一个目录的 negative dentry 数量不应超过 positive dentry 数量的五倍。如果一个目录的 cache 中有 20 个 positive dentry,那么 negative dentry 就不能超过 100 个。这是一个很好的想法,只是有一个小问题:内核并没有针对每个目录来统计相关的 dentries 的数量或类型进行统计。
为了解决这个问题,Brennan 添加了一些代码,在与每个目录相关的 dentries list 中保持一个 "cursor"。每当有一个 dentry 操作(创建或删除)发生时,这部分代码会在 list 中把 cursor 后移 6 个 dentry 位置;如果连一个 positive dentry 都没有碰到过,那么就可以认为已经超过了限制,从而进行一些 negative dentry 的清理。将这项工作安排到 dentry 操作本身里面,就意味着这个开销会由那些创建了大量 dentry 的进程本身来承担,这似乎是个正确做法。
当然,这种方法的问题在于,没有任何方法可以迫使 dentry 是以特定的顺序添加到目录的 list 中的。取决于创建 dentry 的顺序,这种算法可能会对 positive 和 negative dentry 的实际比例得出错误结论,从而做错事。布伦南承认这个问题确实存在("确实这里会对各种 workload 有不同结果,我承认这个问题"),但还没有想出一个更好的主意。就目前的情况来看,这种算法似乎肯定会导致一些极端案例,这可能会阻止人们接受这个 patch set,即使没有其他什么担忧了。
The bigger problem
不过,在正常讨论中,Dave Chinner 认为,对 negative dentry 的关注只是解决了问题的表象,而忽略了更大的问题。他说,真正的问题是,内存压力是内核控制它所维护的许多 cache 的大小的唯一决定因素:
是的,这里的根本问题是,当 没有内存压力 的时候,内存回收机制对管理这些长期积累下来的单一使用目的的 cached object (缓存对象)毫无帮助。有[大量]的空闲时间和闲置资源可以合理地用来管理 cache,但我们没有。例如,没有对 cache 进行定期 rotate 从而触发对这些一次性使用的对象的检测和回收(例如在使用之后几分钟内进行检查),从而防止它们不必要地占满所有的内存,并在内存最终被用光时产生短暂的内存回收和分配延迟的高峰。
他说,与其担心 dentry cache,开发者不如想出一种机制来管理所有内核内 cache 的大小。Wilcox 原则上同意,但警告说不要把问题搞得太广泛,以至于变得难以解决。Chinner 则重申了这一点,他说有多个内核 cache 都有同样的问题,而基于某种定期扫描以使 cache 内的内容老化的解决方案,可以立即适用于所有的这些类型的 cache 中。
这个讨论已经结束了,没有人提出要建立 Chinner 希望看到的更通用的缓冲区老化机制。但问题仍然存在,而且似乎不太可能自己消失。所以这个讨论出现在 LSFMM 档期的可能性似乎相当高。也许三年来第一次在内存管理和文件系统领域的讨论将导致对解决方案达成某种程度的共识,最好是在已经过去了 20 年后的今天就能实现。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~