关注了就能看到更多这么棒的文章哦~
Dentry negativity
By Jonathan Corbet
March 12, 2020
原文来自:https://lwn.net/Articles/814535/
2017年时,Waiman Long提出patch来限制kernel里保存的"negative dentries"的数量。3年过去了,好消息是这个功能还在继续,可是,看起来仍然不能合入mainline。也可以理解,其实大多数人还根本不知道什么是negative dentries,以及为什么kernel开发者会关心这个概念。这里底层的问题其实更加难以解决。
Linux kernel里的dentry是一个目录项在内存中的表现形式。有了它,此前解析过的路径就不需要再逐个目录节点解析一次了,可以直接拿到目标文件或目录。这些dentry cache可以让文件路径lookup工作大大加速。尤其是那些频繁访问的目录(如/tmp, /dev/null, /usr/bin/tetris)都在dentry cache里,可以节省大量filesystem I/O操作。
而negative dentry则不太一样:这是指文件树lookup过程中失败的项目在memory中的记录。如果用户敲入“more cowbell”命令并且当前目录下没有cowbell这个文件,那么kernel就会相应创建一个negative dentry。如果我们这个假象用户很固执,一直重复键入这个命令,kernel就可以以更快的方式来告诉用户“这个文件不存在”,尽管用户更早看到这个消息其实也不会怎么开心。
所以negative dentry可能有助于那些经常敲错命令的场景。不过其实它还有一些其他的重要作用。人们发现真实系统中经常出现对不存在的文件进行lookup的操作,并且经常都是针对一些固定文件。比如寻找动态链接库的动作就是一个典型例子,人们可能会按指示敲入类似下列的命令:
$ strace -eopenat /usr/bin/echo 'Subscribe to LWN'
在我的系统上,输出是这样的:
openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/en_US.UTF-8/LC_IDENTIFICATION", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/lib/locale/en_US.utf8/LC_IDENTIFICATION", O_RDONLY|O_CLOEXEC) = 3
[...]
这么简单的一个echo命令就会在Fedora 31系统上触发13次文件查找失败。如果调用oowriter的话就会有68次,而调用gnucash则会触发277次。对这种应用程序来说,如果能优化这些文件查找失败的情况,就能让人感受到程序启动时间有加快。编译器或者各种语言的运行时(例如Java虚拟机)也会有许多失败的文件查找。可以想象一下C的#include操作或者Python的import操作就能理解了。我在自己的系统上编译了一个"allmodconfig"的kernel,就观察到了52,799,262次文件查找失败,这肯定有优化价值。
不过negative dentry有一个小问题,他们需要占用内存空间。所有这些失败的文件查找算在一起,可能会生成许多许多negative dentries,甚至可能导致内存太紧张从而把其他一些有用数据也挤出去了。LWN在2002年的时候就报道过人们对这个问题的抱怨,来自memory-management的开发者Andrea Arcangeli。不过一般来说kernel里的shrinker机制可以管控好dentry cache整体,同样也能控制住negative dentry不要占用太多。
不过Long主要关注的是普通shrinker无法处理的情况。他在2月底的时候发出了最新版本patch set。他指出,正常的dentry是受限于系统中的文件总数的,而negative dentry代表的系统中不存在的文件,则不会有数量上限。Eric Sandeen就举出了一个例子来做了解释,在NSS library中有些代码在启动时会专门去打开10,000个不存在的文件,目的是搜集一些时间信息。就算不提这些极端情况,其实negative dentries的数量仍然有可能会增长到非常大的。
Long在他的patch set中增加了一个新的sysctl开关/proc/sys/fs/dentry-dir-max。值为0时(缺省情况),系统的行为跟现在保持不变。如果改成某个正整数,那么所有negative dentries的数量就不能超过这个数字。但是这个限制数不可以低于256,避免系统过于频繁地清理dentry。在需要清理时,代码会先挑最近没有引用过的dentry来清理,确保总数小于上限值的7/8。如果这个功能没有打开的话,就会用一个static key(译者注:我不知道怎么翻译了)来避免系统被此机制拖慢。
目前看来大家都不反对限制一下这些negative dentry的数量上限。不过这里所选择的实现方式其实还是有些争议的。每次新增sysctl开关其实都会有人有意见,就像Matthew Wilcox所说:“A sysctl is just a way of blaming the sysadmin for us not being very good at programming”。通常来说,系统管理员很难第一时间发现这些sysctl开关,甚至很难了解清楚该怎么设置。系统管理员又怎么能知道自己的系统和负载情况下应该设置多大的上限值呢?
因此,Wilcox等人认为应该增加一个kernel动态计算出来的数字,并且可以人工调整。Long建议说让系统管理员来配置系统中可以用多大的内存来供negative dentry使用,而不用像现在这样来设置目录数量。Wilcox其实不关心内部实现是怎样的方式,但是他坚持认为应该可以自适应来计算得出。
Dave Chinner则表示怀疑这种机制是否真的有用。他建议把那些违背规则的应用程序直接全部都限制到同一个memory cgroup里面。这样当cgroup内部内存不够的时候,系统会在这一组应用程序中来回收内存,也包括回收它们的negative dentries。他认为既然本来就有限制某个进程使用的内存总量的有效机制了,那么没有必要再增加一个。
Long回答说,尽管cgroup有些帮助,但是没法完全解决问题。系统中negative dentries的数量过多的话会导致触发negative dentry的程序性能下降,哪怕cgroup把它跟其他部分隔离开也不能改变这一点。他还指出,系统守护进程经常是在root cgroup下运行的,这样就没法通过这种方式来限制。
正如这一系列patch此前几次提出的时候一样,讨论还没得到任何结论,就冷却了下来。这版patch进入mainline的机会,不比2年前的版本更高。所以人们试图控制kernel中negative dentries数量的最新努力,又一次没能成功。
全文完
LWN文章遵循CC BY-SA 4.0许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注LWN深度文章以及开源社区的各种新近言论~