关注了就能看到更多这么棒的文章哦~
Efficient Rust tracepoints
By Daroc Alden
October 8, 2024
Kangrejos 2024
Gemini-1.5-flash translation
https://lwn.net/Articles/992455/
Alice Ryhl 一直致力于将内核中广泛使用的 tracepoint(跟踪点) 无缝地放入 Rust 代码中。她在 Kangrejos 会议上介绍了她的方法。她的 补丁集 支持高效使用静态跟踪点,但支持动态跟踪点还需要一些额外工作。
Ryhl 将跟踪点描述为一种日志记录,用于记录内核中特定位置在被访问时的信息。她在 幻灯片 中以 binder_ioctl() 为例讲解了什么是跟踪事件(trace event);每当 Android 的 binderfs 文件系统发生 ioctl()
时,就会触发该跟踪点。试图调试内核问题的开发人员可以查看驱动程序命中的跟踪点日志,以确定发生了什么。
在 C 语言中,程序员使用一行代码放置跟踪点,该代码看起来像一个普通的函数调用。大多数情况下,此调用不做任何事情。使用时,程序员可以在运行时将任意函数附加到该跟踪点,当跟踪点被命中时将调用该函数。由于大多数跟踪点在大多数情况下都被禁用,因此 Linux 使用 static key(静态密钥)(在运行时将调用修补到代码中)来提高效率。
Ryhl 说,生产环境可用的 Rust 驱动程序必须能够支持相同的调试标准,因此必须能够放置跟踪点。这在今天就可以做到,方法是将现有的 C 跟踪点封装在 Rust 包装器中,但这失去了跟踪点最重要的优势之一:它们的低开销。理想情况下,从 Rust 命中禁用的跟踪点应该与 C 语言具有相同的性能成本(即几乎没有性能损失)。
她的解决方案是一个小的 Rust 宏,它在 Rust 端创建必要的静态密钥机制。Rust 代码使用 declare_trace!()
来引用 C 语言中定义的跟踪点;该宏在 Rust 端创建一个内联的 unsafe 函数,可用于触发跟踪点。生成的函数使用内联汇编来定义一个位置,以便静态密钥机制在必要时修补对 C 跟踪点的调用。
Ryhl 说,她之所以采用这种方法,是因为它代表着在 Rust 中实现了最低限度的功能,将大部分跟踪点实现保留在未更改的 C 语言中。为了提高性能,必须在 Rust 端实现静态密钥功能,但这样一来,她就不必重新实现任何用于定义跟踪点的功能,而只需链接到 C 代码即可。
不过,这里有一个问题。C 语言中的静态密钥也使用内联汇编来创建修补跳转的目标。Ryhl 在第一次尝试中,复制了内联汇编以在 Rust 端使用。这被 拒绝 了,因为它引入了代码重复,这在内核中通常是不受欢迎的。
为了解决这个问题,Ryhl 采取了一种“可怕的”方法,即使用 C 预处理器生成一个 Rust 源文件,该文件包含在宏中。原始的 C 源代码中有一个注释,用于显示共享内联汇编的位置,构建系统使用 sed
将其提取出来并放入生成的 Rust 文件中。这避免了任何代码重复,但代价是增加了构建的复杂性。
与会者对提出的解决方案感到有些惊讶。Paul McKenney 介绍了一些关于内核开发人员为何如此关心避免代码重复的背景信息:除了代码质量的正常原因外,它还使 rebase 更改更容易。内核需要处理大量的补丁,任何存在于两个地方的代码都很容易不同步。Ryhl 表示同意,并说有很多理由不重复代码。她开玩笑说,这让她很头疼,但她明白为什么静态密钥维护者坚持这样做。
Gary Guo 说,使用 C 预处理器生成 Rust 代码可能不是一个好主意。Ryhl 回答说,如果可以接受的话,也许可以从一种通用格式生成 C 和 Rust 代码。另一种方法是教会 Rust 自己读取 C 头文件,但这需要做更多的工作。还提出了一些其他的替代方案。McKenney 认为任何方法都是可以接受的,只要它有文档记录,否则所有这些不寻常的代码共享都会让未来的程序员感到困惑。
动态跟踪(Dynamic tracing)
Richard Weinberger 询问了动态跟踪点(Kprobes),它允许用户使用 BPF 在代码中的任何位置附加跟踪点。这在 Rust 中能用吗?Ryhl 对这种机制并不熟悉。Andreas Hindborg 建议先解决静态跟踪点,然后再研究动态跟踪点,这样比较合理。Weinberger 认为最终需要支持动态跟踪点,因为人们希望他们的调试工具能够在整个内核中工作。
根据 Hindborg 对内核 函数跟踪 代码的描述,Ryhl 认为需要向 Rust 编译器添加对动态跟踪的支持。然而,静态跟踪点仍然是需要的,因为在某些情况下,它们也被用作供应商连接到驱动程序函数的一种方式。(例如,一些 Android 硬件供应商依靠跟踪点来响应内核中的事件)Boqun Feng 表示同意,并说这两种跟踪点在不同的用例中都是需要的。Hindborg 指出,函数跟踪与函数内联的交互也很奇怪——在内联之后找到钩子的位置取决于是否有 BTF 信息可用。因此,Rust 需要 原生 BTF 支持 才能做到这一点。
Hindborg 担心,如果采用一种解决方案,也需要在 C 语言中定义跟踪点,那么将来就更难实现纯 Rust 解决方案。Ryhl 回应说,虽然她目前只解决了在 Rust 中声明跟踪点的问题,但将来有人可以添加对跟踪点的定义。
尽管讨论了未来的工作,但与会者对 Ryhl 目前的设计没有异议。看起来静态跟踪点很快就可以在内核的 Rust 代码中使用,这将使供应商能够与用 Rust 编写的驱动程序集成。动态跟踪点和其他调试功能还需要一些时间。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~