有害的 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 建议的 patch与Linux 4.18中的当前功能进行比较。反对这种变化的最大论据来自戴夫·钦纳。他指出,过多地减少缓冲会导致文件系统出现碎片。他还解释说,“对于流式 IO,我们通常需要至少 5 秒的缓存脏数据来平衡延迟。”
我很困惑。为什么 U 盘停顿会导致整个系统挂起?
我很困惑,因为我读过一篇描述2011 年合并的代码(Linux 3.2)的早期文章。它显示内核应该在每个设备的基础上控制脏页缓存:
无 I/O 脏节流- LWN.net,2011
这就是 Fengguang 的补丁集的用武之地。他正试图创建一个控制循环,能够确定在任何给定时间每个进程应该被允许脏多少页。超出其限制的进程只是简单地休眠一段时间,以允许写回系统赶上它们。
[...]
系统的目标是将脏页数保持在设定值;如果事情变得不正常,将施加越来越大的力量将事情恢复到应有的位置。
[...]
但是,如果不考虑支持设备 (BDI),则无法真正计算出该比率。一个进程可能正在弄脏存储在给定 BDI 上的页面,并且系统目前可能有过多的脏页面,但限制该进程的智慧还取决于该 BDI 存在多少脏页面。[...] 具有少量脏页的 BDI 可以快速清除其积压,因此即使系统比人们可能想要的更脏,它也可能负担得起更多。因此,补丁集使用一个复杂的公式调整特定 BDI 的计算 pos_ratio,该公式查看该特定 BDI 与其自己的设定点及其观察到的带宽之间的距离。最终结果是一个修改后的 pos_ratio,它描述了系统应该对给定 BDI 支持的更多或更少页面以及多少页面进行污染。
甚至比这更早添加了每设备控制:Smarter write throttling , 2007 LWN.net。[PATCH 0/23] 每个设备脏节流 -v10。它被合并到Linux 版本 2.6.24中。
1. 2013年的文章有误
“U盘档”一文,给你的印象是很误导的。它歪曲了原始报告和一系列回应。
当 Artem 将缓存写入刷新到 USB 记忆棒时,它没有报告整个系统挂起。他最初的报告只抱怨运行命令“同步”可能需要“几十分钟”。这种区别在Linus Torvalds 的回应中得到了明确的说明:
2. LWN 中的错误?你确定吗?
Jon Corbet有 15 年的经验,每周报告 Linux 内核开发。从某种意义上说,我希望这篇文章至少接近正确。所以我想处理这两个不同的记录,并寻找他们同意或不同意的详细点。
我使用 lore.kernel.org 上的档案阅读了所有原始讨论。我认为这些信息很清楚。
我 100% 肯定这篇文章曲解了讨论。在文章下方的评论中,至少有两名读者用自己的话重复了错误的说法,没有人纠正他们。文章在第三段中继续了这种混淆:
这可能是因为 Linus 说“事情突然停止了”。“事物”是指“做任何事情
sync
”。但科贝特写道,好像“事物”意味着“整个系统”。按照 Linus 的说法,这是一个现实世界的问题。但是绝大多数“事物”不会调用系统范围的 sync() 操作。 [1]
为什么 Corbet 会将其与“整个系统”混淆?我想已经有很多问题了,过了一段时间,你就很难把它们全部分开了:-)。尽管 LWN 已经描述了每个设备(和每个进程)脏节流的发展,但总的来说,我想关于这些细节的文章并不多。很多文档只描述了全局脏限制设置。
3. I/O 设备中的长队列,由“后台”写回创建
Artem 在线程中发布了第二份报告,其中“服务器几乎停止,其他 IO 请求需要更多时间才能完成”。
第二份报告与有关 USB 记忆棒挂起的说法不符。它是在内部磁盘上创建 10GB 文件后发生的。这是一个不同的问题。
该报告没有确认是否可以通过更改脏限制来改善这种情况。最近对此类案例进行了分析。当它阻塞主磁盘的 I/O 队列时,会出现一个严重的问题。您可能会在您经常依赖的磁盘上遭受长时间的延迟,以按需加载程序代码,使用 write() + fsync() 保存文档和应用程序数据等。
补丁在 2016 年末(Linux 4.10)被合并以改善这一点。此代码称为“写回限制”或 WBT。在网上搜索
wbt_lat_usec
还可以找到更多关于此的故事。(最初的文档写的是wb_lat_usec
,但它已经过时了)。请注意,写回限制不适用于 CFQ 或 BFQ I/O 调度程序。CFQ 作为默认 I/O 调度程序很受欢迎,包括在 Linux v4.20 之前的默认内核构建中。 CFQ 在内核 v5.0 中被移除。在SSD(看起来像 NVMe)和“常规硬盘驱动器”上都进行了测试来说明问题(和原型解决方案)。硬盘驱动器“不如更深的队列深度设备那么糟糕,我们有巨大的突发 IO”。
我不确定排队请求的“数千个”,但至少有 NVMe 设备可以排队数百个请求。大多数 SATA 硬盘允许 32 个请求排队(“NCQ”)。当然,硬盘驱动器会花费更长的时间来完成每个请求。
4. “no-I/Odirty throttling”的局限?
“无 I/O 脏节流”是一个相当复杂的工程系统。它也随着时间的推移进行了调整。我确信这段代码中存在并且仍然存在一些限制。
LWN 文章、代码/补丁注释以及详细演示文稿中的幻灯片表明,已经考虑了大量场景。这包括臭名昭著的慢速 USB 记忆棒与快速主驱动器。测试用例包括短语“1000 concurrent dd's”(即顺序编写器)。
到目前为止,我不知道如何演示和重现脏节流代码中的任何限制。
我已经看到了一些关于脏节流代码之外的问题修复的描述。我发现的最新修复是在 2014 年 - 请参阅后续部分。在 LWN 报告的线程中,我们了解到:
梅尔·戈尔曼还表示存在一些“悬而未决的问题”。
这段话是我在报告的讨论线程中唯一能找到的,几乎支持 LWN 的解释。我希望我能理解它指的是什么:-(。或者如何演示它,以及为什么它在 Artem 和 Linus 运行的测试中似乎没有成为一个重要问题。
5.“U盘卡顿”问题的真实报道
尽管 Artem 和 Linux 都没有报告影响整个系统的“USB 记忆棒停顿”,但我们可以在其他地方找到一些关于此的报告。这包括最近几年的报告 - 在最后一次已知修复之后。
我不知道有什么区别。也许他们的测试条件在某种程度上有所不同,或者自 2013 年以来内核中产生了一些新问题......
6.脏限制计算错误[2014]
2014 年 1 月有一个有趣的修复(在内核 v3.14 中应用)。在问题中,我们说默认限制设置为内存的 20%。实际上,它设置为可用于脏页缓存的内存的 20%。例如,内核缓冲区为 TCP/IP 网络套接字发送数据。套接字缓冲区不能被删除并用脏页缓存替换:-)。
问题是内核正在计算可交换内存,好像它可以交换数据以支持脏页缓存。尽管这在理论上是可能的,但内核强烈倾向于避免交换,而是更喜欢丢弃页面缓存。这个问题通过 - 猜猜 - 一个涉及写入慢速 USB 记忆棒的测试来说明,并注意到它导致整个系统的停顿:-)。
参见Re: [patch 0/2] mm: 减少重匿名和脏缓存的回收停顿
修复方法
dirty_ratio
是现在仅将其视为文件缓存的一部分。根据遇到此问题的内核开发人员的说法,“触发条件似乎很合理——高匿名内存使用率和大量缓冲 IO 和交换配置——而且这种情况很可能发生在野外。” 因此,这可能解释了2013 年左右或更早的一些用户报告。
7. IO 上的巨大页面分配阻塞 [2011]
这是另一个问题:巨大的页面、缓慢的驱动器和长时间的延迟(LWN.net,2011 年 11 月)。现在应该修复大页面的这个问题。
此外,尽管文章说了什么,我认为大多数当前的 Linux PC 并没有真正使用大页面。从 Debian 10 开始,这可能会发生变化。然而,即使 Debian 10 开始尽可能分配大页面,我似乎很清楚它不会施加任何延迟,除非您将另一个名为“always”的设置更改
defrag
为“always”。8.“到达 LRU 末尾的脏页”[pre-2013]
我没有研究过这个,但我发现它很有趣:
如果这是两个不同的“到达 LRU 的尽头”问题,那么第一个听起来可能非常糟糕。听起来当脏页成为最近最少使用的页面时,任何分配内存的尝试都会被延迟,直到该脏页完成写入。
不管这意味着什么,他说问题现在已经解决了。
[1] 一个例外:有一段时间,Debian 包管理器
dpkg
使用 sync() 来提高性能。这已被删除,因为 sync() 可能需要很长时间的确切问题。他们改用sync_file_range()
在 Linux 上使用的方法。请参阅Ubuntu 错误 #624877,评论 62。以前尝试回答这个问题的一部分 - 这应该是多余的:
我认为我们可以将 Artem 的两份报告解释为与“No-I/O 脏节流”代码一致。
脏节流代码旨在允许每个后备设备公平共享“总回写缓存”,“这与其当前相对于其他设备的平均写出速度有关”。此措辞来自/sys/class/bdi/的文档。[2]
在最简单的情况下,只写入一个支持设备。在这种情况下,设备的公平份额为 100%。write() 调用被限制以控制整体回写缓存,并将其保持在“设定点”。
dirty_background_ratio
在启动后台写出的点和dirty_ratio
写回缓存的硬限制之间,写入开始受到限制。默认情况下,这些是可用内存的 10% 和 20%。例如,您仍然可以将最多 15% 的写入写入主磁盘。根据你有多少 RAM,你可能有千兆字节的缓存写入。那时, write() 调用将开始被限制以匹配写回速度——但这不是问题。我预计挂起问题是针对 read() 和 fsync() 调用的,它们会卡在大量不相关的 IO 后面。这是“写回限制”代码解决的具体问题。一些 WBT 补丁提交包括问题描述,显示了这导致的可怕延迟。
类似地,您可以将 15% 的内存完全填满对 USB 记忆棒的写入。对 USB 的进一步 write() 将受到限制。但是主盘不会使用它的任何公平份额。如果你开始在你的主文件系统上调用 write() 那么它就不会被限制,或者至少会被延迟更少。而且我认为 USB write() 会受到更多限制,以使两个写入器达到平衡。
我预计整体回写缓存可能会暂时高于设定值。在一些要求更高的情况下,您可以达到整体回写缓存的硬限制。硬限制默认为可用内存的 20%;配置选项是
dirty_ratio
/dirty_bytes
。或许您可以做到这一点,因为设备可能会变慢(可能是因为更随机的 I/O 模式),并且脏节流不会立即识别速度的变化。[2] 你可能会注意到这个文档建议你可以手动限制写回缓存的比例,这可以用于特定的分区/文件系统。该设置称为
/sys/class/bdi/*/max_ratio
。请注意,“如果您要限制的设备是当前写入的唯一设备,则限制不会产生很大的影响。”