关注了就能看到更多这么棒的文章哦~
Capability analysis for the kernel
By Jonathan Corbet
March 10, 2025
Gemini-1.5-flash translation
https://lwn.net/Articles/1012990/
Rust 类型系统的一个优势是它能够将程序状态的需求封装在类型系统中;通常,这种状态包括为了能够执行特定操作而必须持有的锁。C 语言缺乏表达这些需求的能力,但如果能够将这种特性移植到 C 语言中,将会带来明显的好处。Clang 编译器在其 线程安全分析 功能方面取得了一些进展;两位开发者一直在独立地努力,以便在 Linux 内核中利用这项工作。
Clang 的这个功能是基于“capability”(能力)的概念,程序可以在编译时被确定在任何给定点持有(或不持有)这些能力。能力通常是数据结构的地址;例如,特定 spinlock(自旋锁)的地址可以被指定为一个能力,程序可以通过 lock 操作来获取它。函数可以被注解,以表明它们获取或释放一个能力;开发者还可以表明,函数的调用者必须持有(或 不 持有)特定的能力。
向内核添加分析功能
Bart Van Assche 在 2 月 6 日发布了 一个补丁系列,展示了如何将 Clang 的线程安全功能与内核的 mutex(互斥锁)类型一起使用。这项工作的核心可以在 这个补丁 中找到,它注解了各种与 mutex 相关的函数;例如, mutex_lock()
和 mutex_unlock()
的原型被修改为:
void mutex_lock(struct mutex *lock) ACQUIRE(*lock);void mutex_unlock(struct mutex *lock) RELEASE(*lock);
第一行说明,调用 mutex_lock()
将获得一个 capability,其形式为被指向的 mutex,而调用 mutex_unlock()
将放弃该 capability。=ACQUIRE()= 和 RELEASE()
宏是构建在 Clang 的较低级别宏之上的;该集合中还有 相当多的 其他宏。有了这个基础设施,任何需要持有特定 mutex 的函数都可以相应地进行注解;例如:
static struct devfreq_governor *try_then_request_governor(const char *name)REQUIRES(devfreq_list_lock);
如果无法确定是否持有指定的锁( devfreq_list_lock
),编译器将警告对该函数的任何调用。还有一系列名称类似于 GUARDED_BY() 的宏,用于记录对特定数据(例如结构体成员)的访问需要持有某个 mutex。虽然在已发布的系列中没有实际使用这些宏。
Van Assche 的补丁集侧重于 mutex
类型,并尝试注解整个内核中的用法(尽管许多注解是 NO_THREAD_SAFETY_ANALYSIS
,这会禁用分析,因为锁定对于 Clang 来说过于复杂而无法弄清楚 — 这种情况经常发生)。这项工作最终形成了一个 巨大的补丁,涉及 800 多个文件,这是一个相当大的代码变动。这项工作已经发现了一些锁相关的 bug,其修复程序已包含在该系列中。
一种替代方法
同一天,Marco Elver 发布了 他自己的补丁集,采用了略有不同的方法来使用相同的功能;该系列后来 进行了更新,采用了术语“capability analysis”(能力分析)来代替“thread-safety analysis”(线程安全分析)。当 Van Assche 使用 breadth-first(广度优先)方法处理 mutex 时,Elver 采用了 depth-first(深度优先)方法,该系列为几个 locking primitive(锁定原语)添加了注解,但仅在明确选择加入的子系统中处于活动状态。这样,可以为维护者和开发者感兴趣的代码启用警告(并对其采取措施),而其余内核则保持关闭状态。
注解的语法与 Van Assche 的方法略有不同,但意图显然是相同的:
void mutex_lock(struct mutex *lock) __acquires(lock);void mutex_unlock(struct mutex *lock) __releases(lock);
不过,Elver 的系列 patch 不仅局限在 mutex,还添加了对 spinlock、reader-writer lock (读写锁)、seqlock (顺序锁)、single-bit spinlock(单比特自旋锁)、read-copy-update (RCU,读复制更新)、local lock (本地锁) 和 wait/wound mutex (等待/伤亡互斥锁) 的注解。在许多情况下,已经存在于内核的 locking correctness validator (锁定正确性验证器) (lockdep) 的注解已被重新设计,以添加所需的能力声明。有一个 __guarded_by()
注解,用于记录必须持有哪个锁才能访问特定的结构体成员;它的使用可以在 这个补丁 中看到,该补丁对 kfence 子系统进行了检测。 capability_unsafe()
标记禁用对代码块的检查。大多数新注解以及文档都可以在 这个补丁 中找到。
Elver 方法中的另一个不同之处是显式的 opt-in(选择加入)设计,它允许每个子系统启用或禁用该功能。默认情况下,任何给定的子系统都不会被此分析覆盖;可以通过向子系统的 makefile 中添加一行或多行来更改它:
CAPABILITY_ANALYSIS := yCAPABILITY_ANALYSIS_foo := y
第一行将为通过该 makefile 编译的所有代码启用分析,而第二行将仅为创建 foo.o
的编译启用分析。该补丁集为许多内核子系统启用了该功能,包括 debugfs、kernel fence(内核防护)、rhashtable、tty 驱动程序、TOMOYO 安全模块、crypto 子系统等。
接下来是什么?
社区似乎拥有丰富的资源:两个相互竞争的补丁集,旨在利用相同的编译器功能来提高内核中的正确性检查。任何一个系列都可以增加对锁定处理正确的信心,并且两者都在编译时完全工作,没有运行时开销。对这项工作的反响非常积极,唯一悬而未决的问题似乎是哪个系列会被接受,或者内核是否会采用两者的组合。
对于这个问题没有明确的答案,但可以在 Van Assche 一直在为 Elver 的补丁集发布评论(和 Reviewed-by 标签)这一事实中找到线索。Peter Zijlstra 也 尝试 在 scheduler(调度器)子系统中使用 Elver 的工作,并说“这太棒了”。该尝试指出了一些需要的更改;似乎 Zijlstra 也设法使 Clang 编译器崩溃了。他后来 指出,capability analysis 在简单情况下有效,但对于“任何稍微有趣的情况,即你真正想要获得帮助的情况,它都会失效”。
在内核(及其他领域)中的实际使用可能有助于推动 Clang 社区的开发工作,以改进此分析功能,使其能够常规地用于验证锁定模式。可能需要在将对此类 capability analysis 的支持添加到内核之前完成一些工作。但是,由于它是一个 opt-in 的编译时功能,因此即使需要进一步的工作,相对较早地添加它也可能很有价值。内核社区似乎非常渴望获得这种支持。值得一提的是,`non-merge changeset` (未合并的变更集)或者进入 `mainline` (主线)的代码, 都可能会从中受益。
全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。
欢迎分享、转载及基于现有协议再创作~
长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~