为什么 2013 年会出现“U 盘失速”问题?为什么现有的“无 I/O 脏节流”代码没有解决这个问题?

为什么 2013 年会出现“U 盘失速”问题?为什么现有的“无 I/O 脏节流”代码没有解决这个问题?

有害的 USB 记忆棒停滞问题- LWN.net,2013 年 11 月。

Artem S. Tashkinov 最近遇到了一个至少对一些 LWN 读者来说很熟悉的问题。将慢速存储设备(例如 USB 记忆棒或媒体播放器)插入 Linux 计算机并向其写入大量数据。整个系统继续挂起,可能持续几分钟。

不过,这一次,Artem 做出了一个有趣的观察:在使用 64 位内核运行时,系统会停止运行,但在同一硬件上使用 32 位内核时,不会出现此类问题。

该文章解释说,对于 64 位内核,默认情况下允许脏页缓存(回写缓存)增长到内存的 20%。对于 32 位内核,它实际上被限制为 ~180MB。

Linus 建议在 64 位上将其限制为 ~180MB,但是当前的 Linux (v4.18) 并没有这样做。比较Linus 建议的补丁,到当前功能在 Linux 4.18 中。反对此类改变的最大论点来自戴夫·钦纳(Dave Chinner)。他指出,过多地减少缓冲会导致文件系统遭受碎片化。他还解释了“对于流 IO,我们通常需要至少 5 秒的缓存肮脏的数据以消除延误。”

我很困惑。为什么 USB 记忆棒停止运行会导致整个系统挂起?

我很困惑,因为我读了一篇之前描述代码的文章2011年合并(Linux 3.2)。它表明内核应该在每个设备的基础上控制脏页缓存:

无 I/O 脏节流- LWN.net,2011

这就是 Fengguang 的补丁集的用武之地。他试图创建一个控制循环,能够确定在任何给定时间每个进程应该允许脏多少页。超出限制的进程会被简单地置于休眠状态一段时间,以允许写回系统赶上它们。

[...]

系统的目标是将脏页数量保持在设定值;如果事情变得不合时宜,就会施加越来越大的力量,使事情回到应有的位置。

[...]

但是,如果不考虑支持设备 (BDI),则无法真正计算该比率。某个进程可能会弄脏存储在给定 BDI 上的页面,并且系统当前可能有过多的脏页面,但限制该进程是否明智还取决于该 BDI 存在多少脏页面。 [...] 具有少量脏页的 BDI 可以快速清除其积压的日志,因此它可能可以承受更多的脏页,即使系统比人们可能希望的脏一些。因此,补丁集使用复杂的公式来调整特定 BDI 的计算 pos_ratio,该公式查看特定 BDI 距离其自身设定点和观察到的带宽有多远。最终结果是修改后的 pos_ratio,描述系统是否应该脏化更多或更少由给定 BDI 支持的页面,以及脏化程度。

每个设备的控制甚至比这更早添加:更智能的写入限制,2007 LWN.net。[补丁 0/23] 每个设备脏节流 -v10。它被合并在Linux 版本 2.6.24

答案1

  1. 2013年的文章有错误
  2. LWN 有错误吗?你确定吗?
  3. I/O 设备中的长队列,由“后台”写回创建
  4. “无 I/O 脏节流”的局限性?
  5. 关于“U 盘失速”问题的真实报告
  6. 脏污限制计算不正确 [2014]
  7. 大页面分配阻塞 IO [2011]
  8. “脏页到达 LRU 末尾”? [2013 年之前]

1. 2013年的文章有错误

“U盘失速”这篇文章给你一个非常误导的印象。它歪曲了原始报告和一系列回应。

当 Artem 将缓存的写入刷新到 USB 记忆棒时,Artem 没有报告整个系统挂起。他的原始报告只是抱怨运行命令“sync”可能需要“几十分钟”。这种区别在莱纳斯·托瓦兹的回应:

实际上,复制起来非常简单,只需拿起普通的 USB 密钥并尝试写入即可。我只是用一个随机的 ISO 镜像来做这件事,这很痛苦。这并不是说在后台做大多数其他事情是痛苦的,但如果你碰巧运行任何“同步”的东西(并且它发生在脚本中),事情就会突然停止。几分钟。

2.LWN有错误吗?你确定吗?

乔恩·科贝特拥有十五年的经验,每周报告 Linux 内核开发。我预计这篇文章至少关闭从某种意义上说,要做到正确。所以我想处理这两个不同的记录,并找出它们同意或不同意的详细点。

我读了所有的原始讨论,使用 lore.kernel.org 上的档案。我认为这些信息非常明确。

我 100% 确定这篇文章误解了讨论。在文章下方的评论中,至少有两名读者用自己的话重复了错误的说法,但没有人纠正。文章在第三段中继续了这种混乱:

所有这些数据都会堵塞 I/O 队列,可能会延迟其他操作。而且,只要有人打电话同步(),事情会停止,直到整个队列被写入。

Linus 说“事情就这样戛然而止”,这可能会造成混淆。 “事物”指的是“做任何事sync”。但科贝特写道,好像“那个东西”意味着“整个系统”。

按照 Linus 的说法,这是一个现实世界的问题。但绝大多数“事情”都是如此不是调用系统范围的sync()操作。[1]

为什么科贝特会将其与“整个系统”混淆?我想已经出现了很多问题,过了一段时间,你就很难将它们全部分开:-)。尽管 LWN 描述了每设备(和每进程)脏节流的开发,但总的来说,我认为关于此类细节的文章并不多。很多文档只描述了全局脏限制设置。

3. I/O 设备中的长队列,由“后台”写回创建

Artem 发布了第二次报告在线程中,“服务器几乎停止运行,其他 IO 请求需要花费更多时间才能完成”。

第二份报告与有关 USB 记忆棒挂起的说法不符。它是在创建一个 10GB 文件后发生的内部的磁盘。这是一个不同的问题。

该报告没有确认是否可以通过改变脏限制来改善这一问题。最近对此类案例进行了分析。当它堵塞主磁盘的 I/O 队列时,就会出现一个严重的问题。您可能会在经常依赖的磁盘上遭受长时间延迟,以按需加载程序代码、使用 write() + fsync() 保存文档和应用程序数据等。

减少烦人的后台写回——LWN.net,2016

当内存管理代码决定写入一系列脏数据时,结果是向块子系统提交 I/O 请求。该请求可能会在 I/O 调度程序中花费一些时间,但最终会分派给目标设备的驱动程序。

问题是,如果有大量脏数据要写入,最终可能会有大量(如数千个)请求排队等待设备。即使是相当快的驱动器也可能需要一些时间来处理这么多请求。如果某些其他活动(例如,单击 Web 浏览器中的链接或启动应用程序)在同一块设备上生成 I/O 请求,这些请求将排在长队列的后面,并且可能在一段时间内无法得到服务。如果生成多个同步请求(例如,新启动的应用程序产生的页面错误),则每个请求可能必须依次通过这个长队列。这就是事情似乎停止的时刻。

[...]

大多数块驱动程序还在内部维护自己的队列。这些较低级别的队列可能特别有问题,因为当请求到达那里时,它不再受 I/O 调度程序的控制(如果有 I/O 调度程序的话)。

合并补丁以改进这一点2016 年底(Linux 4.10)。此代码称为“写回限制”或 WBT。在网上搜索wbt_lat_usec还发现了更多有关此的故事。 (最初的文档写了有关 的内容wb_lat_usec,但它已经过时了)。请注意,写回限制不适用于 CFQ 或 BFQ I/O 调度程序。 CFQ 作为默认 I/O 调度程序一直很流行,包括在 Linux v4.20 之前的默认内核构建中。 CFQ 在内核 v5.0 中被删除

有测试来说明问题(和原型解决方案)固态硬盘(看起来像 NVMe)和一个“普通硬盘”。硬盘驱动器“不像更深的队列深度设备那么糟糕,我们有巨大的突发IO”。

我不确定“数千”个排队请求,但至少有 NVMe 设备可以对数百个请求进行排队。大多数 SATA 硬盘驱动器允许 32 个请求排队(“NCQ”)。当然,硬盘需要更长的时间来完成每个请求。

4.“无I/O脏节流”的局限性?

“无 I/O 脏节流”是一个相当复杂的工程系统。随着时间的推移,它也进行了调整。我确信曾经并且仍然存在一些这段代码内部的限制。

LWN 文章、代码/补丁注释以及幻灯片从详细的演示中可以看出,已经考虑了大量的场景。这包括臭名昭著的慢速 USB 记忆棒与快速主驱动器。测试用例包括短语“1000个并发dd”(即顺序写入器)。

到目前为止,我不知道如何演示和重现脏节流代码中的任何限制。

我已经看到了一些问题修复的描述,这些描述超出了脏节流代码的范围。我发现的最新修复是在 2014 年 - 请参阅后续部分。在 LWN 报道的主题中,我们了解到:

在过去的几个版本中,此类问题是由回收问题引起的,回收问题因看到大量脏页/写回页不足而感到厌倦,并最终陷入等待 IO 完成的状态。

[...] systemtap 脚本捕获了这些类型的区域,我相信它们已被修复。

梅尔·戈尔曼也存在一些"未决问题"。

但仍然存在问题。如果所有脏页都由慢速设备支持,那么脏限制最终仍然会导致脏页平衡停滞 [...]

这篇文章是我在报道的讨论线索中能找到的唯一内容,几乎可以支持 LWN 的解释。我希望我能理解它指的是什么:-(。或者如何演示它,以及为什么它在 Artem 和 Linus 运行的测试中似乎没有成为一个重要问题。

5.“U盘卡顿”问题的真实报告

尽管 Artem 和 Linux 都没有报告影响整个系统的“USB 记忆棒失速”,但我们可以在其他地方找到一些有关此问题的报告。这包括近年来的报告——在最后一次已知修复之后。

我不知道有什么区别。也许他们的测试条件在某种程度上有所不同,或者也许自 2013 年以来内核中出现了一些新问题......

6.脏污限值计算错误[2014]

2014 年 1 月有一个有趣的修复(应用于内核 v3.14)。在问题中,我们说默认限制设置为内存的 20%。实际上,它被设置为可用于脏页缓存的内存的 20%。例如,内核缓冲 TCP/IP 网络套接字发送的数据。套接字缓冲区不能被删除并用脏页缓存替换:-)。

问题在于内核正在计算可交换内存,就好像它可以交换数据以支持脏页缓存一样。尽管这在理论上是可能的,但内核强烈倾向于避免交换,并且更喜欢删除页面缓存。这个问题通过 - 你猜怎么着 - 一项涉及写入慢速 USB 记忆棒的测试来说明,并注意到它导致整个系统停顿:-)。

回复:[补丁 0/2] mm:减少大量匿名和脏缓存的回收停顿

解决方法是dirty_ratio现在仅将其视为文件缓存的一部分。

据遭遇此问题的内核开发人员称,“触发条件似乎相当合理 - 高匿名内存使用率、大量缓冲 IO 和交换配置 - 并且这种情况极有可能在野外发生。”因此,这可能解释了一些用户报告大约在 2013 年或更早。

7. IO 上的大页面分配阻塞 [2011]

这是另一个问题:大页面、慢速驱动和长时间延迟(LWN.net,2011 年 11 月)。大页面的问题现在应该是固定的

另外,不管文章怎么说,我认为当前大多数 Linux PC 并不真正使用大页面。从 Debian 10 开始,这种情况可能会发生变化。然而,即使 Debian 10 开始尽可能分配大页面,我似乎很清楚它不会造成任何延误,除非您将另一个设置更改defrag为“始终”。

8.“脏页到达 LRU 末尾”[2013 年之前]

我没有研究过这个,但我发现它很有趣:

摩根2011:这是一种与 USB 相关的新型停顿,因为它是由于同步压缩写入造成的,而在过去,最大的问题是脏页到达 LRU 的末尾并由reclaim编写

摩根2013:该一般区域的工作处理诸如脏页到达 LRU 末尾之类的问题(CPU使用率过高)

如果这是两个不同的“到达 LRU 末尾”问题,那么第一个听起来可能会非常糟糕。听起来好像当脏页成为最近最少使用的页时,任何分配内存的尝试都会被延迟,直到该脏页完成写入。

不管这意味着什么,他说问题现在已经解决了。


[1] 一个例外:有一段时间,Debian 包管理器dpkg使用sync()来提高性能。由于sync()可能需要很长时间的确切问题,这一点已被删除。他们转而采用在 Linux 上使用的方法sync_file_range()。看Ubuntu bug #624877,评论 62


之前尝试回答这个问题的一部分 - 这应该是多余的:

我认为我们可以将 Artem 的两个报告解释为与“无 I/O 脏节流”代码一致。

脏节流代码的目的是允许每个支持设备公平共享“总回写缓存”,“这与其相对于其他设备的当前平均写出速度有关”。该措辞来自文档/系统/类/bdi/.[2]

在最简单的情况下,仅写入一个支持设备。在这种情况下,设备的公平份额为 100%。 write() 调用受到限制以控制整体写回缓存,并将其保持在“设定点”。

dirty_background_ratio写入开始在- 启动后台写出的点 - 和dirty_ratio- 写回缓存的硬限制之间的中间受到限制。默认情况下,分别是可用内存的 10% 和 20%。

例如,您仍然可以只向主磁盘写入最多 15% 的数据。根据您拥有的 RAM 量,您可能拥有千兆字节的缓存写入。那时, write() 调用将开始受到限制以匹配写回速度 - 但这不是问题。我预计挂起问题是针对 read() 和 fsync() 调用,它们会卡在大量不相关的 IO 后面。这是“写回限制”代码解决的具体问题。一些 WBT 补丁提交包含问题描述,显示了由此导致的可怕延迟。

同样,您可以通过写入 USB 记忆棒来完全填满 15%。对 USB 的进一步 write() 将受到限制。但主磁盘不会使用其任何公平份额。如果您开始在主文件系统上调用 write() ,那么它不会受到限制,或者至少延迟会少得多。我认为 USB write() 会受到更多限制,以使两个写入器达到平衡。

我预计整体写回缓存可能会暂时升至设定值以上。在一些要求更高的情况下,您可以达到总体写回缓存的硬限制。硬限制默认为可用内存的20%;配置选项是dirty_ratio/ dirty_bytes。也许您可以遇到此问题,因为设备可能会减慢速度(可能是由于更随机的 I/O 模式),并且脏节流无法立即识别速度的变化。


[2] 您可能会注意到本文档建议您可以手动限制可用于特定分区/文件系统的回写缓存的比例。该设置称为/sys/class/bdi/*/max_ratio.意识到 ”如果您要限制的设备是当前唯一写入的设备,则限制不会产生太大影响”。

答案2

截至 2024 年 2 月,该问题仍未解决。

您可以通过创建包含以下内容来解决/解决它/etc/sysctl.d/limit_dirty_caches.conf

# Per Linus Torvalds advice
vm.dirty_background_bytes = 33554432
vm.dirty_bytes = 134217728

作为通用 sysctl 规则,这是次优的因为在复制/创建大文件时可能会导致更高级别的碎片。请继续阅读。

从 Linux 6.2 开始,有一个全新的块设备接口写回 sysfs API /sys/class/bdi(即strict_limitmin_bytesmax_bytesmin_ratio_finemax_ratio_fine)来配置此行为,但不幸的是有人必须针对 USB 设备进行调整,但没有人这样做:

https://docs.kernel.org/admin-guide/abi-testing.html#abi-file-testing-sysfs-class-bdi

一个简单的udev规则就可以永远解决这个问题。

相关内容