关注了就能看到更多这么棒的文章哦~
Diagnosing workqueues
By Daroc Alden
April 9, 2024
SCALE
ChatGLM translation
https://lwn.net/Articles/967016/
在 Linux 内核中,有许多用于延迟工作的机制。其中之一,工作队列(workqueues)用的越来越广泛,得益于越来越多从软件中断(software interrupts)转变而来的场景。Alison Chaiken 在 SCALE 会议上发表了一次演讲,讲述了工作队列与软件中断的比较、它们为系统管理员带来的新挑战,以及内核开发者可用的那些诊断工作队列问题的工具。
软件中断(software interrupts)的背景
软件中断是一种机制,允许 Linux 将中断处理程序的工作分成两部分。由硬件调用的中断处理程序完成最少量的工作,然后触发一个软件中断,以便内核稍后执行实际工作。这可以减少在注册中断处理程序中花费的时间,从而确保有效地处理中断。
Chaiken 解释说,当硬件中断触发软件中断时,有两种可能的情况。当 CPU 上没有正在运行的软件中断时,新的软件中断可以立即开始运行。当 CPU 上已经在运行软件中断时,新的中断会排队等待稍后处理 — 即使新中断的优先级实际上高于当前正在运行的中断。有十种不同类型的软件中断,每种类型都有特定的优先级。Chaiken 展示了这些优先级的列表,并指出即使不了解软件中断设计的其他信息,当看到网络中断的优先级高于定时器中断的时候读者可能也会“感到一些不安”。
这些优先级反转本身就是一个问题,因为它们会导致高优先级任务的延迟和抖动,但优先级系统还会引入其他问题。优先级最低的中断是内核的读取-复制-更新(RCU)系统的一部分。Chaiken 将 RCU 系统称为“基本上是内核的垃圾收集器”。这意味着如果没有足够快的中断处理,实际上可能会导致内核内存耗尽。
另一方面,过多地服务软件中断可能会干扰那些延迟敏感的操作,如音频处理 — 内核维护者常见的问题是软件中断运行时间过长且拒绝让出 CPU,从而事实上完全占用了一个 CPU 核心。
为了平衡这两个问题,有两种启发式限制用于平衡延迟与公平性。 MAX_SOFTIRQ_TIME
是软件中断允许运行的最长时间;它被设置为 2 毫秒。 MAX_SOFTIRQ_RESTART
是软件中断被其他事物中断后重启的最大次数;它被设置为进行十次尝试。不幸的是,这些参数是写死在内核内部的。它们据说是通过实验设定出来到一个比较好的值,但 Linux 在如此多种类的设备上运行,不太会有哪个设置能够对所有设备都最优。"没有人有勇气改变它们",她说,这"不是一个很好的情况"。她总结说,软件中断是"内核中不太受欢迎的特性",在内核的许多版本中已经有过几次尝试想要摆脱它们。
但是,移除软件中断的进展很慢。尽管有这些努力,仍然有 250 个 local_bh_disable()
的调用点 — Chaiken 将其称为"这次演讲中这部分内容的反对者"。 local_bh_disable()
阻止软件中断在特定 CPU 上运行。然而,实际上,它充当了一个锁,用于保护数据结构不被软件中断处理程序同时访问。一位听众问哪些资源被下半部分锁保护着,Chaiken 回答说"没有人真正知道",因为调用分散在内核中各处。
更糟糕的是,软件中断在很大程度上是不透明的,因为它们在中断上下文中运行 — 就像硬件中断一样。它们无法访问许多内核设施 — 如调试日志(debug logging)。"你不能在中断处理程序中打印"。有一些方法可以获得更好的可见性,但与内核其余部分可用的功能相比,显得更加笨拙。
尽管与软件中断打交道很困难,但仍有一些可观察性工具。Chaiken 在她的笔记本电脑上演示了如何使用 stackcount 程序获取当前运行的所有软件中断的堆栈跟踪。
人们在越来越多地推动将一些由软件中断完成的工作转移到工作队列机制,Chaiken 称之为“全方位更好的设计”。
工作队列
工作队列在内核中已经存在很长时间了,但它们最近增加了很多新功能。"这次演示中最困难的部分是工作队列在过去 18 个月里变化如此之大,我难以跟上"。
工作队列是驱动程序和其他内核组件调度延迟工作的通用方式。每个工作队列理论上与一个单一组件关联,该组件可以将其喜欢的任何工作添加到队列中。实际上内核的很多部分在共享使用工作队列,这些队列并不特定于某个组件。每个工作队列还与一组内核线程池关联,这些线程池将服务该队列的任务。
默认情况下,Linux 为每个 CPU 创建两个工作池(worker pool),一个正常优先级和一个高优先级。这些池包含专用的 worker,内核将根据需要生成更多 worker 或移除一些。这些池自动调整也意味着,管理员遇到行为异常的工作队列项问题时,不能通过更改 worker 优先级或将其固定到单独的核心来解决。随着更多功能转移到工作队列,问题和错误报告无疑会开始变得更加常见。
改变工作队列中每一项的处理情况的正确方法是使用“管理工作队列的 API”,而不是直接来操作 worker。Chaiken 展示了一个演示,说明了如何做到这一点。她挑选了一个工作队列,并显示它正在运行一个特定的池,该池也在为许多其他工作队列提供服务。然后她改变了工作队列本身的优先级,并显示这导致工作队列更改为另一个工作池 — 一个符合其新属性的池。在回答观众问题时,她澄清说,“如果没有匹配工作队列的工作池,内核会创建新的工作池。”
“工作队列的亲和性(affinity)处理在最近的内核中真的改进了”,她评论道。由于无法将单个 worker 固定到 CPU 核心,最近的内核允许用户更改工作队列本身的 CPU 亲和性。添加了像这样的特性意味着工作队列在过去的 18 个月里变得更加有用,Chaiken 称之为“大幅进步”。
Chaiken 还展示了工作队列提供的更加灵活的跟踪和调试功能。她使用了 LGPL 许可的 drgn
调试器以及来自内核的一组特定于工作队列的调试脚本。 wq_dump.py
显示当前工作队列配置,包括哪些工作池存在以及它们如何在核心之间排列。 wq_monitor.py
显示工作队列的实时行为,这对于诊断工作调度问题非常有帮助。
工作队列在 sysfs 文件系统下的 /sys/devices/virtual/workqueue
中也有显示,这可以作为一种快速获取工作队列信息的方式,而无需使用调试器。只有使用 WQ_SYSFS
标志配置的工作队列才会出现在那里,因此 Chaiken 指出,“如果工作队列让您感到不适,您可以做的一件事就是制作一个微小的内核补丁”来打开该标志。
最后,工作队列 worker 在进程上下文中运行,而不是中断上下文 — 这意味着当处理工作队列中的项目时,可以访问内核的许多正常调试设施,如跟踪点、调试日志等。
在演讲后的问答环节中,一位观众询问了他们可以使用哪些资源来了解更多关于工作队列的信息。Chaiken 回答说,“工作队列的文档非常出色”。“您可以通过阅读内核的条目文档和使用这些工具来学习很多东西。”她还提供了 她演讲的幻灯片 的链接,其中包含许多她在准备演讲时引用的资源链接。
另一位观众询问是否已有工具可以根据观察到的延迟从而进在池之间迁移工作。Chaiken 回应说,“很多东西都还非常新颖,人们还没有真正理解它”,但也警告说,任何创建此类工具的人都“真的需要[…]测试来刻画您的工作负载及其性能”。
希望深入了解更多细节的读者可以在 这里 找到 Chaiken 演讲的录制。她的演讲给我留下了这样的印象:工作队列有望比软件中断更容易管理和调试。尽管有这些好处,工作队列也有难以缓解的缺点,比如增加的延迟。在软件中断可以完全消除之前,还需要很长一段时间,而且在如此多的内核部分使用软件中断的情况下,切换工作肯定会很痛苦。内核开发者和系统管理员都需要对工作队列有很好的了解,但这类知识可以通过文档和新工具轻松获取。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~