关注了就能看到更多这么棒的文章哦~
Bye-bye bdflush()
By Jonathan Corbet
July 5, 2021
DeepL assisted translation
https://lwn.net/Articles/861431/
在 Linux 内核中增加系统调用是经常发生的事情,几乎每个合并窗口期内都有这种动作。相反,删除系统调用的情况就很少见了。不过,随着关于删除 bdflush()的讨论不断推进,似乎很快就会发生一次删除系统调用的事件了。请继续阅读下去,看看这个难以理解的系统调用的目的及其历史,来判断一下你是否会希望保留它(相信你不会的)。
Linux 同大多数操作系统一样,是通过内存来缓冲文件系统的 I/O 操作。一个 write()调用会使得相应数据被复制到内核的 page cache 中去,但不会马上触发对底层的块设备这个真正存储设备的写入操作。只要不是要写入一系列完整的 block,这种缓冲机制对其他任何类型的写入都是必要的。对于文件系统的性能表现也很重要。推迟对 block 的写入可以使多个操作汇聚成起来,从而能更好地优化磁盘中文件的 layout,并可以进行批量处理。
不过,缓冲下来的文件数据不能永远止步在内存之内,最终系统必须安排将其 flush 到磁盘里去。自从 0.01 版的 Linux 开始就包括了一个 sync() 系统调用的实现,用来迫使所有缓存下来的文件系统数据被写出去。虽然内核会在 buffer cache(这个是 page cache 之前的概念,当时是一个固定大小的数组)被填满时 flush 出去一些 buffer,但并没有规定要定期确保所有 buffer 都被 flush 到磁盘中去。如果编者没有记错的话,当时这个工作是由一个用户空间进程来负责的,它会时不时地被唤醒并调用 sync()。
不过,在内核中处理这个任务会更有优势,因为内核对 buffer cache 和其底层的设备的状态有更加深入的了解。作为朝这个方向迈出的一步,在 1994 年 2 月 2 日的 0.99.14y 版本中就加入了 bdflush()系统调用 (当时的内核开发流程跟现在很不一样,其之前的 0.99.14x 版本只比它早发布 7 个小时,而 0.99.14z 则是在 9 小时后发布的)。不过,当时实现的这个版本并没有多大用处;它所做的只是返回一个 "not implemented(尚未实现)" 这样的错误信息。直到 1994 年 4 月的 1.1.3 development kernel,才真正增加了 bdflush() 的实现。
必须指出,bdflush()是一个奇怪的系统调用。它被定义为
int bdflush(int func, long data);
如果 func 是 0 的话,bdflush() 就永远不会返回,它将会在内核内一直循环,时不时地 flush 一些脏 buffer。实际上的效果就是,一个用户空间进程,通过调用这个函数,就变身成了内核里的 buffer-flushing 线程。毕竟,那个时代还没有 kernel thread 的实现。参数 func 如果为 1 会导致一些 buffer 被马上 flush 出去。更高的 func 值则会读取或写入 flush 线程的一些控制参数,这些参数包括激活 flush 操作所需的 dirty buffer 的百分比、每一轮里面要写入的 block 数量等等。
虽然 bdflush()是对系统的一个改进,但它也有一些问题。其中一个是它需要依赖用户空间来完成一个关键性的内核功能。如果没有进程调用 bdflush()进入这个状态,或者如果该进程被 kill 了,系统就不能按预期运行了。在 1.3.50 development release(1995 年 12 月),内核就改为会自动创建一个内核线程(当时已经可以创建了)来完成 flush 动作。用户空间仍然可以调用 bdflush()来调整各种参数,但如果像之前那样试图把自己变成实现 flush 操作的守护进程的话,就会变成立即调用 exit()。这样做之后,旧版 init 系统启动的 flush 进程并不会出现错误,这样就避免了系统启动时出现问题。
bdflush()的另一个问题——或者更确切地说,是其底层实现的问题——就是从一开始它就是一个单线程操作。随着 Linux 的普及以及运行在更大的系统上,单线程越来越成为一个严重的瓶颈。如果你在某个系统上有许多磁盘驱动器,它最终就会需要运行多个线程来让它们同时忙碌起来。因此,Andrew Morton 在 2002 年为 2.5.8 版本的 development kernel 中把遗留 bdflush() 基础设施代码全部替换掉了,新的实现是一组名为 pdflush 的内核线程。每个 pdflush 线程都专门服务于一个独立的物理驱动器,这样一来就切实地解决了人们需要的扩展性(scalability)问题。
2002 年 12 月,Morton 把 Robert Love 的一个 patch 合入了 mainline,从此正式废除了 bdflush() 这个系统调用,并承诺 "将在未来的内核中删除"。pdflush 线程在 2009 年被移除(2.6.32 内核),转而采用了更复杂的、基于 workqueue 的 writeback-control 机制。这些线程仍然会以内核线程的形式被看到,名为 kworker/u8:3-flush-259:0 这样的。同时,bdflush() 在当前内核中仍然存在,尽管它多年以来其实都没有什么实际用处了。
然而,现在 Eric Biederman 提议完全删除 bdflush(),这会作为他重写(rework)内核 exit() 代码的这个更大项目中的一部分。鉴于这个系统调用什么都不做、一开始就没有得到广泛使用、而且已经被废弃了近 19 年,人们可能会自信地得出结论:已经没有什么地方在使用它了。但事实证明,Geert Uytterhoeven 就有一个古老的 m68k 镜像文件,他偶尔会启动运行一下,大概是在他怀旧的时候吧。Michael Schmitz 证明了在没有 bdflush()的情况下,上述镜像文件也仍能成功启动,所以这并不会妨碍删除此系统调用。
目前还没有发现其他使用 bdflush() 的地方,所以似乎没有什么能阻止这个删除操作了。届时,这将是自 2019 年底删除 sysctl() 以来再次删除的第一个系统调用,而且是由同一位 Eric Biederman 删除的。不过,考虑到这个 patch 是最近才发布的,所以很可能不会合入 5.14 版本。这个系统调用在它不再有用之后已经仍然存在了近 19 年,再多保留它两个月等到 5.15 似乎并不是什么让人为难的事情。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

508

被折叠的 条评论
为什么被折叠?



