内核测试指南

有很多不同的工具用于测试 Linux 内核,因此了解何时使用它们可能是一个挑战。本文档概述了它们之间的差异,以及它们如何组合在一起。

编写和运行测试

大多数内核测试都是使用 kselftest 或 KUnit 框架编写的。它们都提供了基础架构,以帮助更轻松地运行测试和测试组,并提供帮助编写新测试的助手。

如果您想验证内核的行为(特别是内核的特定部分),那么您需要使用 KUnit 或 kselftest。

KUnit 和 kselftest 之间的区别

KUnit (KUnit - Linux 内核单元测试) 是一个完全在内核中的“白盒”测试系统:由于测试代码是内核的一部分,它可以访问未暴露给用户空间的内部结构和函数。

因此,最好针对可以单独测试的内核的小型、自包含的部分编写 KUnit 测试。这与“单元”测试的概念非常一致。

例如,KUnit 测试可能会测试单个内核函数(甚至是通过函数的单个代码路径,例如错误处理情况),而不是整个功能。

这也使得 KUnit 测试的构建和运行速度非常快,允许它们作为开发过程的一部分频繁运行。

有一个 KUnit 测试风格指南,可以在测试风格和命名法中提供进一步的指导

另一方面,kselftest (Linux 内核自测试) 主要在用户空间中实现,并且测试是普通的用户空间脚本或程序。

这使得编写更复杂的测试或需要更多地操作整个系统状态的测试(例如,生成进程等)更容易。但是,不可能直接从 kselftest 调用内核函数。这意味着只能使用 kselftest 测试以某种方式暴露给用户空间的内核功能(例如,通过系统调用、设备、文件系统等)。为了解决这个问题,一些测试包含一个配套的内核模块,该模块暴露了更多信息或功能。但是,如果测试主要或完全在内核中运行,则 KUnit 可能是更合适的工具。

因此,kselftest 非常适合测试整个功能,因为这些功能将向用户空间公开一个接口,可以对其进行测试,而不是实现细节。这与“系统”或“端到端”测试非常一致。

例如,所有新的系统调用都应附带 kselftest 测试。

代码覆盖率工具

Linux 内核支持两种不同的代码覆盖率测量工具。这些可用于验证测试是否正在执行特定的函数或代码行。这对于确定内核中有多少被测试,以及查找适当测试未涵盖的边界情况非常有用。

在 Linux 内核中使用 gcov是 GCC 的覆盖率测试工具,可用于内核以获取全局或每个模块的覆盖率。与 KCOV 不同,它不记录每个任务的覆盖率。覆盖率数据可以从 debugfs 读取,并使用常用的 gcov 工具进行解释。

KCOV:用于模糊测试的代码覆盖率是一项可以构建到内核中的功能,允许在每个任务级别捕获覆盖率。因此,它对于模糊测试和其他情况下非常有用,在这些情况下,例如,有关单个系统调用期间执行的代码的信息很有用。

动态分析工具

内核还支持许多动态分析工具,这些工具尝试在运行的内核中发生问题时检测问题类别。这些工具通常各自查找不同类别的错误,例如无效的内存访问、诸如数据竞争之类的并发问题,或其他未定义的行为(如整数溢出)。

下面列出了一些此类工具

这些工具倾向于测试整个内核,并且不会像 kselftest 或 KUnit 测试那样“通过”。它们可以通过在启用这些工具的内核上运行测试来与 KUnit 或 kselftest 结合使用:然后您可以确保在测试期间不会发生这些错误。

其中一些工具与 KUnit 或 kselftest 集成,如果检测到问题,将自动使测试失败。

静态分析工具

除了测试运行中的内核外,还可以使用静态分析工具直接(在编译时)分析内核源代码。内核中常用的工具允许检查整个源树或其中的特定文件。它们可以更容易地在开发过程中检测和修复问题。

Sparse 可以通过执行类型检查、锁检查、值范围检查以及在检查代码时报告各种错误和警告来帮助测试内核。有关如何使用它的详细信息,请参阅Sparse文档页面。

Smatch 扩展了 Sparse,并为编程逻辑错误提供了额外的检查,例如 switch 语句中缺少 break、错误检查时未使用的返回值、忘记在错误路径的返回中设置错误代码等。Smatch 还会针对更严重的问题进行测试,例如整数溢出、空指针解引用和内存泄漏。请参阅 http://smatch.sourceforge.net/ 上的项目页面。

Coccinelle 是我们可以使用的另一个静态分析器。Coccinelle 通常用于辅助源代码的重构和协同演化,但它也可以帮助避免在常见代码模式中出现的某些错误。可用的测试类型包括 API 测试、测试内核迭代器的正确使用、检查空闲操作的健全性、分析锁定行为,以及已知有助于保持一致内核使用的进一步测试。有关详细信息,请参阅 Coccinelle文档页面。

但请注意,静态分析工具会遭受误报。在尝试修复错误和警告之前,需要仔细评估它们。

何时使用 Sparse 和 Smatch

Sparse 执行类型检查,例如验证带注释的变量是否会导致字节序错误,检测不正确使用 __user 指针的位置,以及分析符号初始化程序的兼容性。

Smatch 执行流分析,如果允许构建函数数据库,它还会执行跨函数分析。Smatch 尝试回答诸如此类的问题:此缓冲区在何处分配?它有多大?此索引可以由用户控制吗?此变量是否大于该变量?

通常,在 Smatch 中编写检查比在 Sparse 中编写检查更容易。尽管如此,Sparse 和 Smatch 检查之间存在一些重叠。

Smatch 和 Coccinelle 的优点

Coccinelle 可能是最容易编写检查的。它在预处理器之前工作,因此使用 Coccinelle 更容易检查宏中的错误。Coccinelle 还会为您创建补丁,而其他工具不会这样做。

例如,使用 Coccinelle,您可以从 kmalloc(x * size, GFP_KERNEL) 大规模转换为 kmalloc_array(x, size, GFP_KERNEL),这非常有用。如果您只是创建了一个 Smatch 警告并尝试将转换工作推给维护人员,他们会感到恼火。您必须讨论每个警告是否真的会溢出。

Coccinelle 不分析变量值,这是 Smatch 的强项。另一方面,Coccinelle 允许您以简单的方式做简单的事情。