关注了就能看到更多这么棒的文章哦~
Systemd catches up with bind events
By Jonathan Corbet
November 13, 2020
DeepL assisted translation
https://lwn.net/Articles/837033/
kernel 代码在进行改动时,非常注重避免破坏 user space 的应用。也就是说如果某一个版本的内核能够让这个应用程序正常工作,那么在后续内核版本中也要能继续工作。因此,在 systemd 247-rc2 版本的的公告中,大家看到一个明显的会导致 user-space API 不再向后兼容的问题以及相关的一个详细解释的时候,会感到不满。为了保证系统的正常运行,需要对 udev 配置文件进行修改,但 systemd 项目声称,这 "不是 systemd 或 udev 的错,而是由 Linux 4.12 中出现的一个不能向后兼容的内核改动造成的"。这是一个合适的时机来看看这里的细节、管理员需要如何应对这次的改动,以及是否可以采取措施来避免这种事情再次发生。
现代计算机系统往往是拥有复杂的动态变化的。在系统运行时,设备(包括物理设备和虚拟设备)会不断出现和消失。内核会对这些设备事件进行一些底层处理,但剩下的就交给用户空间来处理了。用户空间就需要知道系统的配置何时发生了变化,才能进行及时地处理。
例如,每当一个 USB 设备插入时,会产生一个或多个 ADD event,告诉用户空间有了一个新设备可用。udev daemon(守护进程)就负责根据一组 rule(规则)来对这些事件进行相应的处理。它可以创建设备节点、设置权限、通知其他的用户空间软件等等,所有这些都是获取到 event 的属性之后根据 rule 来进行相应的调用的。这里会用到的 event 种类并不多,而且不会经常变化。
Breaking systemd
不过在 2017 年 7 月,Dmitry Torokhov 增加了两个新的 event 类型,名为 BIND 和 UNBIND。它们的目的是让 user space 来处理那些在提供完整功能之前还需要一些协助的设备,例如需要加载固件(firmware)的设备。在支持这个新机制的驱动程序中,等到 device 准备好了,那么紧跟着 ADD event 之后就会发出一个 BIND event。这一改动是 2017 年 11 月发布的 4.14 内核的一部分(并不是 systemd 公告中所说的 4.12)。
当时那个月底前,KDE 的 bug 跟踪系统上就有人报出了一个 bug,这可能是第一次被人发现的与这个新的 EVENT 有关的事例。不过这个 bug report 直到 2018 年年底才出现在 kernel mailing list 中,这已经有一年多的时间过去了。那时,4.14 已经被定为了一个 long-term support kernel,并且许多 Linux 发行版里面都有使用,用户也很少有抱怨。所以,Greg Kroah-Hartman 感到很困惑,为什么一年后才会出现问题。后来才明白,原来是 systemd 的新改动,开始会把这个 event 上报出去了。
具体来说,这个问题似乎源于 udev(这是 systemd 项目的一部分)给 event 附加 tag 的方式。这些 tag 由 udev rule 来决定怎样设置和使用,用来控制 user space 如何对这个新设备进行配置。之前的实现中,有一个潜在假设,即在新设备出现时,只会发出一个 event,所以只要为这个 event 增加 tag 就足够了。当第二个 event(BIND event)出现时,设备的状态被 reset,这些 tag 就删除了,导致这个设备没有得到正确的配置。
当时临时的 fix 方法就是修改 systemd,直接忽略新的 event。这样做的缺点就是导致新增的 event 被隐藏掉了。这肯定不能作为一个长期解决方案,毕竟增加新的 event 是有用处的,有些设备需要这些 event 才能正确进行配置。所以必须找到一个更好的长期解决方案。这个解决方案既有好处也有坏处,坏处就是可能会对那些已经创建了自己的 udev rule 的用户导致一些麻烦。
Fixing systemd
首先,需要对 udev 提供的 "tag" 机制进行改造。tag 是用来做标记的一种特殊的属性,然后会在后续的规则中进行匹配,或者由 userspace 来使用。udev 没有像之前那样将 tag 附加到 event 上,而是将 tag 附加到了设备上。所以,因为 add 事件而添加的 tag,在 bind 事件中也仍然会存在。对于需要使用 rule 来仅处理当前 event 中添加的 tag 的情况来说,新增了一个 CURRENT_TAGS property,只列出了当前 event 的 tag。因此,它实际上用起来就跟以前版本中的 TAGS 属性用法一致。
不过,还有一些改动是必须要对多个 udev rule 来进行改动的。例如,下面这是一个从 Fedora 32 系统中随机选取的一个 rule 文件(10-dm-disk.rules):
# "add" event is processed on coldplug only!
ACTION!="add|change", GOTO="dm_end"
ACTION 这一行的意思是说,如果碰到的不是 ADD 或 CHANGE 这两个 event 之一,就直接跳到文件末尾,不进行任何操作。现在碰到的问题就是,BIND event 就会落入这种情形之中。这将导致与这些 event 关联起来的 property 都被丢失,而且相关的设备也没有正确设置(甚至可能完全没有进行设置)。这里的解决办法就是将这一行改为下面这样:
ACTION=="remove",GOTO="dm_end"
这样一来,只有当设备被从系统移除掉之时,那些配置 rule 才会被跳过。
这样做的问题在于,这些 rule 编写的时候,是假设不会再增加新的 event 类型。所以,任何没有被识别为 add 或 change 的设备事件都可以被忽略。显然,肯定有很多规则都是这么写的,因此也会有同样的问题。这里实际上存在的是一种协议僵化(protocol ossification)现象,这使得人们要在内核提供的 API 中添加新的 event 种类会变得越来越困难。事实上,在 2018 年,Torokhov 就曾指出:
好吧,看来我们不能再增加新的 uevent 类型来扩展这个接口了,至少在我们修复完所有的 udev-derivatives,并花一些时间解决这个问题之前,我们不能再扩展 uevent 接口。
当时,有人讨论是不是能 revert 那个 kernel patch,把新增的 event 拿掉。但这种做法也会再导致一个 regression 问题,因为有些系统可能已经开始使用这些 event 了,毕竟加入这些 event 的内核版本已经发布有一年了。我们也曾讨论过添加例如 sysctl 设置来决定是否启用 BIND 和 UNBIND 这两个 event,但没有通过。而 Torokhov 介绍了可以在 systemd 项目中做出上述改动,后面 Kroah-Hartman 就回应说:"那么就一切都解决了".
A regression?
运气好的话,一切都解决了,但这个代价就是过去两年 systemd 社区内的不少工作。systemd 开发者已经表达了他们的不满:
我们对这次导致大家现有的 rule 可能无法正常工作、从而需求要求大家更新相关配置,感到非常抱歉。我们想再次强调,这不是系统/udev 的变化造成的,而是 kernel 行为改变的结果。
这是否违反了内核的 "no regressions" 规则?答案几乎可以肯定是 "Yes";在 4.13 下工作的代码在 4.14 下不再能正常工作。应该如何处理这个问题,就不是很明确了。如果这个问题能更快地被报告给内核社区,也许可以 revert 掉,并重新进行设计。但在它被部署了一年之后才报出来,这时候就很难进行抉择了。人们当然可以说,内核社区应该找到其他方法来修复这个 regression 问题。systemd 247-rc2 公告就是在提出这样的观点。但是当 Torokhov 声明说这个问题会在 systemd 这边解决,内核社区这边就没有动力继续去尝试解决方案了。
也许这里真正的教训是,如果内核项目和像 systemd 这样的底层管理工具之间的关系能更加紧密,就会对社区更有好处。有时候他们的关系会比较紧张,并且没有什么渠道专门用来进行项目之间的合作讨论。systemd 开发者在 Linux Plumbers Conference 这样的活动中也是很少出现的,而且这些开发者感觉 kernel mailing list 不是很欢迎他们(这种看法也是有确实事例的)。不过,我们都是在同一个系统上工作,如果我们能多进行交流,大家都会更轻松一些。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~