LWN: 关闭SIGBUS信号!
关注了就能看到更多这么棒的文章哦~
Suppressing SIGBUS signals
By Jonathan Corbet
June 25, 2021
DeepL assisted translation
https://lwn.net/Articles/860419/
mmap()系统调用可以给一段虚拟地址区域创建一个映射(mapping),这个函数有一长串的参数来具体控制这个 mapping 应该是怎样的。Ming Lin 建议新增一个选项,即 MAP_NOSIGBUS。可以用它来改变进程访问未映射(unmapped)地址时内核如何响应。这个选项的作用还是比较容易理解的,但为什么需要用到它,则需要进行一些解释。
通常情况下,当一个进程进行内存相关的操作时,它希望能够从指定的位置读出或写入数据。但有时会出现异常情况,导致进程收到一个 fatal signal(默认设置下)。一个 "segmentation violation"(SIGSEGV)信号是在试图以违背保护方式的情况下访问一个有效的内存地址时报出的,比如说对只读内存进行的写入操作。而试图访问一个无效的地址的情况下就会导致 "bus error"(SIGBUS)。bus error 可能是在多种情况下出现的,比如使用的地址未正确对齐,或者使用一个根本没有被映射过的地址。如果某个进程使用 mmap() 创建的 mapping 超出了这个相应文件的末尾,那么如果试着去访问超过文件末尾的 page 时就会触发 SIGBUS 信号。
然而,如果一个内存区域在映射时采用了这里提议新增的 MAP_NOSIGBUS flag,那么在访问这块区域的无效地址(invalid address)进行访问时就不会再产生 SIGBUS 信号了。相反,这个导致出错的进程将会得到一个内容全是 0 的新 page。如果这个映射区域对应的是磁盘上的一个文件,那么新的 page 也不会被追加写入到该文件中。简单来说,新的选项只是消除了 SIGBUS 信号,因此进程甚至不知道它曾试图访问过一个无效的地址。
OK…but why?
这种行为看起来很怪异。人们通常不会希望某个被映射的区域包含了无效的地址,而且人们通常总是希望能知道某个程序是否在生成并使用无效地址的。事实上,在正常使用情况下,映射的区域很可能会包含无效地址:如果该区域映射的是一个文件,并且它包含了超过磁盘文件末尾的地址。在试图访问超出文件末尾的 page 时就将产生一个 SIGBUS 信号;这种情况可以通过在试图通过 mapping 来访问文件之前对文件进行扩大操作(extend)来避免。
不过 MAP_NOSIGBUS 与这种工作方式是完全冲突的;它在针对无效地址所创建的全是 0 的 page 并没有跟底层文件关联起来,这样一来要想 extend 这个文件的话就必须要重新生成 mapping 才可以。实际上,发明这个选项是为了解决另一个问题:图形客户端(graphical client)可能会意外地(或有意地)导致 compositor 发生 crash。
图形化的应用程序经常需要向 compositor 传递大量数据。一种有效方式就是对一个文件生成 mapping 然后将描述符传递给 compositor,该文件(可能位于一个完全在内存中的文件系统里)就成为了两个进程之间的共享内存区域。然而,如果客户端进程后面调用了 ftruncate()来缩短文件的大小,那么就会导致这个 mapping(在 compositor 中)超出了该文件的末尾。如果 compositor 试图访问超出文件末尾位置的共享内存,就会得到一个 SIGBUS 信号;如果没有采取相应措施的话,这会导致 compositor 发生 crash,这是致力于改善用户体验的开发者们要尽力避免的事情。SIGBUS 信号可以在 compositor 中被捕获并处理掉,但这个过程可能很复杂,也很难做到完全正确。
正如从事 Wayland compositor 开发工作的 Simon Ser 在四月份时指出的那样,还有一种机制可以用来在两个进程之间传递数据:memfd abstravtion。一个 memfd 可以被 "sealed(密封起来)",这意味着创建者无法像上面说的那样来缩小这个区域(或者说,实际上根本不能对其进行改动);接收者知道这个区域不会意外发生变化,那么就可以安心地进行访问了。但是,正如 Ser 所指出的,没有一个 compositor 要求必须 sealed memfd,因为有的客户端不愿意或不能使用 sealed memfd。因此,compositor 要么小心翼翼地处理好各种 SIGBUS,要么就得冒着不断触发 core dump 的风险。
但是,如果 compositor 能够以一种不会在无效地址上产生 SIGBUS 信号的方式来映射一段内存的话,整个问题就会消失了。Ser 建议把 OpenBSD 支持的 __MAP_NOFAULT 标志作为一个可能解决方案。在六月初,Lin 相应地提出了 MAP_NOSIGBUS 的实现代码,它与 __MAP_NOFAULT 有许多区别。最初的实现版本只适用于内存中的 tmpfs 文件系统,但 Hugh Dickins 提出了反对意见,认为它应该适用于任何 mapping;第二个版本 (也是目前修改过的版本) 就是针对这一点进行了修改,无论 mapping 背后对应的是什么,都可以使用 MAP_NOSIGBUS。
Limitations
当前实现中还有一个重要限制,它只适用于 MAP_PRIVATE mapping。对于一个旨在用于客户端和 compositor 之间共享的 mapping 的机制来说,这是一个致命的缺陷。但是,正如 Ser 所解释的,private mapping 几乎在所有情况下都能使用,因为数据传输都是单向的,是从客户端送到 compositor 的,这个 mapping 在合成器那边可以是只读属性的。最明显的例外情况是屏幕捕捉,如果不能支持 shared mapping 的话,这里就需要进行一些特别处理。所以这个解决方案并不完整,但完成度 90% 的方案已经是朝着正确方向迈出的一大步了。
第二版 patch set 的讨论相对较少,似乎相关的开发者对其目前状况已经比较满意了(尽管有人听到 Kirill Shutemov 对 "one-user features" 这一点有过抱怨)。尽管没有人能够提供保证,但似乎看起来很大可能会在 5.14 版本中将这个功能合入 mainline。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~