Linux 内核测试与调试
测试是任何软件开发周期(无论是开放的还是封闭的)不可或缺的重要组成部分,Linux 内核也不例外。开发者测试、集成测试、回归测试和压力测试具有不同的个体目标,但从宏观角度来看,最终目标是相同的,即确保软件在添加新的代码主体后仍然像以前一样工作,并且新功能按设计工作。
在发布之前确保软件稳定且没有回归,有助于避免在发布后调试和修复客户和用户发现的错误。调试和修复客户发现的问题需要花费更多的时间和精力。因此,测试对于任何软件(不仅仅是 Linux 内核)都非常重要。与封闭和专有操作系统不同,开发过程是开放的,并且没有被锁定。这个过程既是它的优势,也是它的弱点。随着众多开发者不断添加新功能和修复错误,持续集成和测试对于确保内核在新硬件支持和功能添加的同时,继续在现有硬件上工作至关重要。在开源开发中,开发者和用户共同承担测试责任。这是封闭开发模型和开放开发模型之间的另一个重要区别。
几乎所有 Linux 内核开发者(如果不是全部)本身都是非常活跃的 Linux 用户。虽然没有要求测试人员必须是开发者,但是,不熟悉新代码的用户和开发者可能比该代码的原始作者更有效地测试新代码。换句话说,开发者测试是验证功能的重要步骤,但是,仅靠开发者测试不足以发现与其他代码、功能以及配置和/或硬件上的意外回归的交互作用,这些是开发者没有预料到并且没有机会和资源进行测试的。因此,用户在 Linux 内核开发过程中扮演着非常重要的角色。
现在我们了解了持续集成测试的重要性,接下来我们将深入探讨测试本身的细节。在讨论测试之前,我想先介绍一下开发过程本身,以帮助理解其工作原理以及更改如何汇入主线内核。
来自世界各地的 3000 多名内核开发者为 Linux 内核做贡献。这是一个每周 7 天、每天 24 小时、每年 365 天的持续开发过程,每 2 个多月产生一个新的版本,以及多个稳定和扩展稳定版本。新开发和当前版本集成周期并行运行。
有关开发过程的更多阅读材料,请参阅 Greg Kroah-Hartman 关于 Linux 内核开发的演示文稿。
我的目的是,本指南应该对初学者以及经验丰富的贡献者和/或有兴趣参与 Linux 内核开发的人员有所帮助。经验丰富的开发者可以选择跳过介绍基本测试和调试的部分。
本文将讨论如何测试和调试 Linux 内核,以及有助于回归和集成测试的工具、脚本和调试机制。此外,本文将详细介绍如何使用 git bisect 来隔离引入错误的补丁,以及在将补丁发送到 Linux 内核邮件列表之前需要测试的内容。我将使用 Linux PM 作为测试和调试讨论的示例目标领域。即使本文侧重于 Linux 内核测试,测试的重要性也适用于任何软件项目。
配置开发和测试系统
让我们开始吧。首先要务是找到适合您需求的开发系统。x86-64 系统是基本开发系统的不错选择,除非需要特定的架构和/或配置。
第二步是安装您偏好的发行版。我更喜欢 Ubuntu,因此本文档将详细介绍如何配置运行 Ubuntu 发行版的内核开发系统。请按照 How to Ubuntu 安装您选择的 Ubuntu 版本。
在开发和测试系统上,最好确保引导分区中有足够的空间来存放内核。建议选择全盘安装或为引导分区预留 3 GB 磁盘空间。
一旦发行版安装完成并且系统准备好安装开发包,请启用 root 帐户,并为您自己的用户帐户启用 sudo。系统可能已经安装了 build-essential 包,这是您在 x86_64 系统上构建 Linux 内核所需的包。如果 build-essential 尚未安装,请运行以下命令进行安装
sudo apt-get install build-essential
此时,您也可以安装以下软件包,以便系统准备好交叉编译 Linux 内核。请注意,ncurses-dev 是运行 make menuconfig 所需的软件包。
sudo apt-get install binutils-multiarch
sudo apt-get install ncurses-dev
sudo apt-get install alien
现在让我们安装一些每个 Linux 内核开发者都需要的工具。
sudo apt-get install git
sudo apt-get install cscope
sudo apt-get install meld
sudo apt-get install gitk
如果您希望将系统配置为在 x86-64 系统上交叉编译其他受支持的非原生架构,请按照以下步骤操作:在 x86 64 上交叉编译 Linux 内核。
稳定内核
首先克隆稳定内核 git,构建并安装最新的稳定内核。您可以在 Linux 内核归档中找到有关最新稳定版和主线版本的信息。
git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git
上述步骤将创建一个名为 linux-stable 的新目录,并用源代码填充它。
您也可以只下载 Linux 内核源代码 tar 包,而不是克隆 git,然后解压 tar 包。
tar xvf linux-3.x.y.tar.xz
编译和安装稳定内核
如果您克隆了稳定 git
cd linux-stable
git checkout linux-3.x.y
或者如果您使用的是 tar 包cd linux-3.x.y
对于任何系统上的首次内核安装,从发行版配置文件开始是最安全的方法。您可以通过从 /boot 复制当前内核的配置来做到这一点。
cp /boot/config-3.x.y-z-generic .config
运行以下命令以基于当前配置生成内核配置文件。系统将提示您调整配置,以启用自 Ubuntu 快照内核以来添加的新功能和驱动程序。
make oldconfig
完成此步骤后,就可以编译内核了
make all
内核编译完成后,安装新内核
sudo "make modules_install install"
上述命令将安装新内核并运行 update-grub,将新内核添加到 grub 菜单。现在是时候重启系统以启动新安装的内核了。在我们这样做之前,让我们保存当前内核的日志,以便比较并查找回归和新错误(如果有的话)
dmesg -t > dmesg_current
dmesg -t -k > dmesg_kernel
dmesg -t -l emerg > dmesg_current_emerg
dmesg -t -l alert > dmesg_current_alert
dmesg -t -l crit > dmesg_current_alert
dmesg -t -l err > dmesg_current_err
dmesg -t -l warn > dmesg_current_warn
通常,dmesg 应该是干净的,没有 emerg、alert、crit 和 err 级别的消息。如果您看到其中任何一个,则可能表明存在一些硬件和/或内核问题。
在尝试新安装的内核之前,还有几个重要的步骤。不能保证新内核会启动。作为安全措施,请确保至少安装了一个好的内核。更改默认 grub 配置文件 /etc/default/grub
- 使用 earlyprink=vga 内核引导选项启用将早期引导消息打印到 vga
- GRUB_CMDLINE_LINUX="earlyprink=vga"
- 将 GRUB_TIMEOUT 值增加到 10 - 15 秒,以便 grub 在菜单中暂停,留出时间选择要引导的内核
- 取消注释 GRUB_TIMEOUT 并将其设置为 10:GRUB_TIMEOUT=10
- 注释掉 GRUB_HIDDEN_TIMEOUT 和 GRUB_HIDDEN_TIMEOUT_QUIET
- 运行 update-grub 以更新 /boot 中的 grun 配置
- sudo update-grub
现在重启系统。一旦新内核启动,将保存的旧内核 dmesg 与新内核进行比较,看看是否有任何回归。如果新安装的内核无法启动,您将必须启动一个好的内核,然后调查新内核无法启动的原因。
生活在快车道上
如果您喜欢在快车道上行驶并且需要速度,请克隆主线内核 git,或者更好的选择是 linux-next git。引导和测试主线和 linux-next 有助于在内核发布之前发现和修复问题。
- 主线
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
- linux-next
git clone git://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git
编译和安装主线和 linux-next 内核与稳定内核完全相同。请按照前面章节中的说明进行操作。
应用补丁
Linux 内核补丁文件是文本文件,其中包含从原始源代码到新源代码的差异。每个 Linux 补丁都是对代码的独立更改,除非明确地成为补丁系列的一部分。新补丁的应用方式如下
patch -p1 < file.patch
git apply --index file.patch
两者都可行,但是,当补丁添加新文件并且使用 patch 命令应用时,git 不知道新文件,它们将被视为未跟踪文件。“git diff”不会在其输出中显示这些文件,“git status”会将这些文件显示为未跟踪文件。
在大多数情况下,构建和安装内核没有问题,但是,“git reset --hard”不会删除新添加的文件,随后的 git pull 将会失败。有几种方法可以告诉 git 关于新文件并让它跟踪它们,从而避免上述问题
- 选项 1
当使用 patch 命令应用添加新文件的补丁时,在运行“git reset --hard”之前,运行“git clean”以删除未跟踪的文件。例如,git clean -dfx 将强制删除未跟踪的目录和文件,忽略 .gitignore 文件中指定的任何标准忽略规则。如果您不关心知道哪些文件被删除,则可以包含 -q 选项以在安静模式下运行 git clean。
- 选项 2
另一种方法是告诉 git 跟踪新添加的文件,方法是运行“git apply --index file.patch”。这将导致 git 应用补丁并将结果添加到索引。完成此操作后,git diff 将在其输出中显示新添加的文件,并且 git status 将正确报告状态,将这些文件标记为新创建的文件。
基本测试
安装新内核后,下一步是尝试启动它,看看会发生什么。一旦新内核启动并运行,请检查 dmesg 以查找任何回归。运行一些使用测试
- 网络(wifi 或有线)是否正常工作?
- ssh 是否工作?
- 通过 ssh 运行大型文件的 rsync
- 运行 git clone 和 git pull
- 启动 Web 浏览器
- 阅读电子邮件
- 下载文件:ftp、wget 等。
- 播放音频/视频文件
- 连接新的 USB 设备 - 鼠标、U 盘等。
检查内核日志
检查 dmesg 中的回归是识别新代码引入的问题(如果有的话)的好方法。作为一般规则,dmesg 中不应有新的 crit、alert 和 emerg 级别的消息。不应有新的 err 级别的消息。还要密切关注任何新的 warn 级别的消息。请注意,新的 warn 消息并不像想象的那么糟糕。新代码有时会添加新的警告消息,这些消息只是警告。
- dmesg -t -l emerg
- dmesg -t -l crit
- dmesg -t -l alert
- dmesg -t -l err
- dmesg -t -l warn
- dmesg -t -k
- dmesg -t
以下脚本运行上述 dmesg 命令并保存输出,以便与旧版本的 dmesg 文件进行比较。然后,它针对旧版本的 dmesg 文件运行 diff 命令。旧版本是必需的输入参数。如果未提供,它将仅生成 dmesg 文件并退出。回归表明新引入的错误和/或在 linux git 树中包含补丁之前逃脱了补丁测试和集成测试的错误。dmesg 中是否有 WARN_ON 导致的任何堆栈跟踪?这些是需要进一步调查的严重问题。
压力测试
并行运行 3 到 4 个内核编译是一个很好的总体压力测试。下载一些 Linux 内核 git,例如 stable、linux-next 等。并行运行定时编译。将时间与此测试的旧运行时间进行比较,以查找性能回归。更长的编译时间可能是内核模块之一中性能回归的指标。性能问题很难调试。第一步是检测它们。并行运行多个编译是一个很好的总体压力测试,可以用作性能回归测试和总体内核回归测试,因为它会练习各种内核模块,如内存、文件系统、dma 和驱动程序。
time make all
内核测试工具
tools/testing 下有几个测试包含在 Linux 内核 git 中。有很好的自动化和功能测试组合。
ktest 套件
ktest 是一个自动化测试套件,可以测试构建、安装和内核引导。如果系统安装了交叉编译器,它还可以运行交叉编译测试。ktest 依赖于 flex 和 bison 工具。请查阅 tools/testing/ktest 中的 ktest 文档,了解有关如何运行 ktest 的详细信息。这留给读者作为自学。一些详细介绍如何运行 ktest 的资源
tools/testing/selftests
让我们从 selftests 开始。内核源代码包含一组自测,用于测试各种子系统。在撰写本文时,breakpoints、cpu-hotplug、efivarfs、ipc、kcmp、memory-hotplug、mqueue、net、powerpc、ptrace、rcutorture、timers 和 vm 子系统都进行了自测。除了这些之外,用户内存自测通过 test_user_copy 模块测试用户内存到内核内存的复制。以下是如何运行这些自测
- 编译测试
- make -C tools/testing/selftests
- 运行所有测试:(运行某些测试需要 root 访问权限,以 root 身份登录并运行)
- make -C tools/testing/selftests run_tests
- 仅运行针对单个子系统的测试
- make -C tools/testing/selftests TARGETS=vm run_tests
tools/testing/fault-injection
tools/testing 下的另一个测试套件是 fault-injection。failcmd.sh 脚本运行命令以注入 slab 和页面分配失败。这种类型的测试有助于验证内核从故障中恢复的能力。此测试应以 root 身份运行。以下是当前已实现的故障注入功能的快速摘要。随着新的故障注入功能的添加,列表不断增长。请参阅 Documentation/fault-injection/fault-injection.txt 以获取最新信息。
- failslab(默认选项)
- 注入 slab 分配失败。kmalloc()、kmem_cache_alloc()、...
- fail_page_alloc
- 注入页面分配失败。alloc_pages()、get_free_pages()、...
- fail_make_request
在通过设置 /sys/block//make-it-fail 或 /sys/block///make-it-fail 允许的设备上注入磁盘 IO 错误。(generic_make_request())
- fail_mmc_request
在通过设置 debugfs 条目(位于 /sys/kernel/debug/mmc0/fail_mmc_request 下)允许的设备上注入 MMC 数据错误
可以配置故障注入的功能和行为。fault-inject-debugfs 内核模块为运行时提供了一些 debugfs 条目。指定故障的错误概率率、故障注入之间的时间间隔只是故障注入测试支持的配置选择的几个示例。请参阅 Documentation/fault-injection/fault-injection.txt 以获取详细信息。引导选项可用于在 debugfs 可用之前在早期引导期间注入故障。支持以下引导选项
- failslab=
- fail_page_alloc=
- fail_make_request=
- mmc_core.fail_request=[interval],[probability],[space],[times]
故障注入基础设施提供了添加新故障注入功能的接口。以下是添加新功能所涉及步骤的简要概述。有关详细信息,请参阅上述文档
- 使用 DECLARE_FAULT_INJECTION(name) 定义故障属性;
有关详细信息,请参阅 fault-inject.h 中 struct fault_attr 的定义。
- 添加引导选项以配置故障属性
可以使用辅助函数 setup_fault_attr(attr, str) 完成此操作。添加引导选项对于在早期引导时启用故障注入功能是必要的。
- 添加 debugfs 条目
使用辅助函数 fault_create_debugfs_attr(name, parent, attr) 为此新功能添加新的 debugfs 条目。
- 添加模块参数
当新故障功能的范围仅限于单个内核模块时,添加模块参数以配置故障属性是一个不错的选择。
- 添加钩子以插入故障
- should_fail(attr, size);当 should_fail() 返回 true 时,客户端代码应注入故障。
使用此故障注入基础设施的应用程序可以针对特定的内核模块注入 slab 和页面分配失败,以在需要时限制测试范围。
自动化测试工具
有几种自动化测试工具和测试基础设施可供您选择,具体取决于您的特定测试需求。本节旨在简要概述,而不是详细指南,介绍如何使用这些工具和基础设施中的每一个。
- AuToTest
Autotest 是一个用于完全自动化测试的框架。它主要用于测试 Linux 内核,但它对于许多其他功能(例如鉴定新硬件)也很有用。它是一个 GPL 下的开源项目。Autotest 以服务器-客户端模式工作。Autotest 服务器可以配置为在运行 autotest 客户端的多个目标系统上启动、运行和监视测试。Autotest 客户端可以在目标系统上手动运行,也可以通过服务器运行。使用此框架,可以添加新的测试用例。请参阅 Autotest 白皮书 以获取更多信息。
- Linaro 自动化验证架构
- LAVA-Test 自动化测试框架是一个框架,用于帮助自动化安装和执行测试。例如,在 LAVA 框架中运行 LTP 可以通过几个命令完成。运行 lava-test 工具来安装 LTP 将自动安装任何依赖项,下载最新版本 LTP 的源代码,编译它,并将二进制文件安装在独立的区域中,以便用户在运行卸载时可以轻松删除它们。此时,运行带有 ltp 测试选项的 lava-test run 将执行 LTP 测试并将结果与唯一 ID 一起保存,该 ID 包括测试名称、测试执行的时间/日期戳。这些结果被保存以供将来参考。这是一个很好的功能,可以查找测试运行之间是否存在回归。作为示例运行的命令摘要
- 显示 lava-test 支持的测试列表
- lava-test list-tests
- 安装新测试
- lava-test install ltp
- 运行测试
- lava-test run ltp
- 检查结果
- lava-test results show ltp-timestamp.0
- 删除测试
- lava-test uninstall ltp
内核调试功能
Linux 内核包含多个调试功能,例如 kmemcheck 和 kmemleak。
- kmemcheck
kmemcheck 是一种动态检查工具,用于检测和警告有关未初始化内存的某些用法。它的功能与 Valgrind 的 memcheck 相同,Valgrind 的 memcheck 是用户空间内存检查器,而 kmemcheck 检查内核内存。CONFIG_KMEMCHECK 内核配置选项启用 kmemcheck 调试功能。请阅读 Documentation/kmemcheck.txt 以获取有关如何配置和使用此功能以及如何解释报告结果的信息。
- kmemleak
kmemleak 可用于检测可能的内核内存泄漏,其方式类似于跟踪垃圾回收器。跟踪垃圾回收器和 kmemleak 之间的区别在于,后者不释放孤立对象,而是将它们报告在 /sys/kernel/debug/kmemleak 中。Valgrind 的 memcheck --leak-check 使用类似的方法(报告而不是释放)来检测用户空间应用程序中的内存泄漏。CONFIG_DEBUG_KMEMLEAK 内核配置选项启用 kmemleak 调试功能。请阅读 Documentation/kmemleak.txt 以获取有关如何配置和使用此功能以及如何解释报告结果的信息。
内核调试接口
Linux 内核通过配置选项、调试 API、接口和框架支持静态和动态调试。让我们详细了解这些,首先从静态选项开始。
调试配置选项 - 静态
Linux 内核核心和几个 Linux 内核模块(如果不是全部)都包含用于调试的内核配置选项。可以在编译时启用其中的几个静态调试选项。调试消息记录在 dmesg 缓冲区中。调试 API
调试 API 的一个示例是 DMA-debug,它专为调试驱动程序 dma api 使用错误而设计。启用后,它会跟踪每个设备的 dma 映射,检测对未映射地址的取消映射尝试,以及驱动程序代码在 dma 映射尝试后缺少映射错误检查的情况。CONFIG_HAVE_DMA_API_DEBUG 和 CONFIG_DMA_API_DEBUG 内核配置选项在提供支持的架构上启用此功能。启用 CONFIG_DMA_API_DEBUG 选项后,Debug-dma 接口从 DMA API 调用。例如,当驱动程序调用 dma_map_page() 来映射 dma 缓冲区时,dma_map_page() 将调用 debug_dma_map_page() 来开始跟踪缓冲区,直到稍后通过 dma_unmap_page() 释放该缓冲区。有关更多阅读材料,请参阅 使用 DMA Debug API 检测静默数据损坏和内存泄漏
动态调试
动态调试功能允许动态启用/禁用每个调用点的 pr_debug()、dev_dbg()、print_hex_dump_debug()、print_hex_dump_bytes()。这意味着,可以在运行时启用特定的调试消息,以了解有关观察到的问题的更多信息。这非常棒,因为无需重新编译启用调试选项的内核,然后安装新内核,结果却发现问题不再可重现。一旦内核中启用了 CONFIG_DYNAMIC_DEBUG,动态调试功能就可以精细地启用/禁用调试消息。/sys/kernel/debug/dynamic_debug/control 用于指定启用哪些 pr_* 消息。以下是如何按调用级别、按模块级别启用动态调试的快速摘要
- 在 kernel/power/suspend.c 的第 340 行启用 pr_debug()
echo 'file suspend.c line 340 +p' > /sys/kernel/debug/dynamic_debug/control
- 在模块加载时在模块中启用动态调试功能
- 在加载模块时,将 dyndbg="plmft" 传递给 modprobe。
- 在模块中启用动态调试功能以在重启后保持持久
在 /etc/modprobe.d/ 中创建或更改 modname.conf 文件以添加 dyndbg="plmft" 选项。但是,对于从 initramfs 加载的驱动程序,更改 modname.conf 不足以使动态调试功能在重启后保持持久。对于此类驱动程序,请更改 grub 以将 module.dyndbg="+plmft" 作为模块选项作为内核引导参数传递。
dynamic_debug.verbose=1 内核引导选项增加了动态调试消息的详细程度。请参阅 Documentation/dynamic-debug-howto.txt 以获取有关此功能的更多信息。
跟踪点
到目前为止,我们讨论了各种静态和动态调试功能。静态调试选项和调试钩子(例如 DMA Debug API)都在编译时启用或禁用。这两种选项都需要编译和安装新内核。动态调试功能消除了重新编译的需要,但是调试代码与控制是否打印调试消息的条件变量一起编译。它有助于在运行时启用消息,但是,条件代码在运行时执行,以确定是否需要打印消息。另一方面,跟踪点代码可以被触发以仅在启用跟踪点时才包含在运行时。换句话说,跟踪点代码的不同之处在于,除非启用,否则它是非活动的。当启用时,代码将被修改以包含跟踪点代码。它不会增加任何条件逻辑开销来确定是否需要生成跟踪消息。
请阅读 关于如何实现良好的跟踪点代码的技巧,以更深入地了解跟踪的工作原理。跟踪点机制
跟踪点使用跳转标签,这是分支的代码修改。
- 禁用时,代码路径如下所示
-
[ code ] nop back: [ code ] return; tracepoint: [ tracepoint code ] jmp back;
- 启用时,代码路径如下所示:(注意跟踪点代码如何出现在下面的代码路径中)
-
[ code ] jmp tracepoint back: [ code ] return; tracepoint: [ tracepoint code ] jmp back;
Linux PM 子系统测试
使用调试、动态调试和跟踪,让我们运行一些挂起到磁盘 PM 测试。当系统挂起时,内核会在磁盘上创建休眠映像,挂起并使用该映像在恢复时恢复系统状态。
- 启用记录挂起和恢复每个设备所需的时间
-
echo 1 > /sys/power/pm_print_times
- 在重启模式下运行挂起到磁盘测试
echo reboot > /sys/power/disk echo disk > /sys/power/state
- 在关机模式下运行挂起到磁盘测试 - 与重启模式相同,只是需要开机才能恢复
echo shutdown > /sys/power/disk echo disk > /sys/power/state
- 在平台模式下运行挂起到磁盘测试 - 更广泛,并测试 BIOS 挂起和恢复路径,例如:将调用 ACPI 方法。这是休眠的推荐模式,以便 BIOS 了解并意识到挂起/恢复操作。
echo platform > /sys/power/disk echo disk > /sys/power/state
Linux PM 子系统模拟模式测试
Linux PM 子系统提供五种 PM 测试模式,以模拟模式测试休眠。这些模式允许在内核的各个层中练习休眠代码,而无需实际挂起系统。当担心挂起可能在特定平台上不起作用时,这很有用,并且有助于在类似于模拟驾驶飞机的模拟中检测错误。
- freezer - 测试进程的冻结
echo freezer > /sys/power/pm_test echo platform > /sys/power/disk echo disk > /sys/power/state
- devices - 测试进程的冻结和设备的挂起
echo devices > /sys/power/pm_test echo platform > /sys/power/disk echo disk > /sys/power/state
- platform - 测试进程的冻结、设备的挂起和平台全局控制方法(*)
echo platform > /sys/power/pm_test echo platform > /sys/power/disk echo disk > /sys/power/state
- processors - 测试进程的冻结、设备的挂起、平台全局控制方法(*)和非引导 CPU 的禁用
echo processors > /sys/power/pm_test echo platform > /sys/power/disk echo disk > /sys/power/state
- core - 测试进程的冻结、设备的挂起、平台全局控制方法、非引导 CPU 的禁用和平台/系统设备的挂起。注意:此模式在 ACPI 系统上进行测试。
echo core > /sys/power/pm_test echo platform > /sys/power/disk echo disk > /sys/power/state
Linux PM 子系统跟踪事件
PM 子系统支持多个跟踪点和跟踪事件,可以启用这些跟踪点和跟踪事件以在运行时触发。我将概述如何启用其中几个跟踪事件以及在哪里找到它们生成的跟踪信息
- 在运行时启用 PM 事件
-
cd /sys/kernel/debug/tracing/events/power echo 1 > cpu_frequency/enable cat /sys/kernel/debug/tracing/set_event less /sys/kernel/debug/tracing/trace
- 使用内核引导选项在引导时启用事件内核跟踪参数
trace_event=cpu_frequency
有关 Linux PM 测试的更多信息,请参阅 Documentation/power/basic-pm-debugging.txt 和同一目录中的其他文档。
git bisect
git bisect 是一个非常有价值且功能强大的工具,用于隔离有问题的提交。我将介绍非常基本的 git bisect 步骤。
- 以下是流程的工作方式
-
git bisect start git bisect bad # Current version is bad git bisect good v3.14-rc6 # last good version
指定一个错误版本和一个正确版本后,git bisect 将开始通过拉取好版本和坏版本之间的提交来进行二分查找。一旦拉入一组提交,编译内核,安装,测试,并将版本标记为好或坏。这个过程重复进行,直到选定的提交被测试并标记为好或坏。可能有几个内核版本需要测试。当最后一个版本被测试后,git bisect 将标记一个错误的提交。以下有用的 git-bisect 命令可以帮助使用 git-bisect 流程
- 查看逐步二分查找进度
git bisect log
- 如果标记出错,可以使用 Reset git bisect,保存 git log 输出并在重置前重放
git bisect reset
- 重放 git-bisect 日志
git bisect replay git_log_output
如果问题明显在内核源代码树的某个区域,git bisect 可以针对该区域运行。例如,当调试 radeon 驱动程序中的问题时,在 drivers/drm/radeon 上运行 git bisect 将把二分查找的范围限制在 drivers/drm/radeon 驱动程序的提交上。
- 在内核树的某个部分启动 git bisect
git bisect start drivers/drm/radeon
Linux 内核补丁测试
您是否尝试编写内核补丁?本节将介绍如何在将新补丁发送到 Linux 邮件列表之前对其进行测试。此外,我们还将讨论如何发送它。
代码准备就绪后,编译它。将 make 输出保存到文件中,以查看新代码是否引入了任何新的警告。解决任何警告。一旦代码干净地编译通过,安装编译后的内核并启动测试。如果启动成功,请确保 dmesg 中没有新的错误,并将其与之前的内核 dmesg 进行比较。运行一些使用和压力测试。请参考本文前面讨论的测试内容。如果补丁是为了修复特定的错误,请确保补丁确实修复了该错误。如果补丁修复了问题,请确保其他模块回归测试通过。识别已打补丁模块的回归测试并运行它们。当补丁涉及到其他架构时,建议进行交叉编译构建测试。请查看源代码 git 中的以下内容,作为识别测试的参考。- linux_git/Documentation
- linux_git/tools/testing
- 交叉编译参考:在 x86_64 上交叉编译 Linux 内核:入门教程
一旦您对补丁测试感到满意,就可以提交更改并生成补丁了。确保提交消息非常清楚地描述了所做的更改。维护者和其他开发人员能够理解此更改的全部内容非常重要。补丁准备好后,在生成的补丁上运行 scripts/checkpatch.pl。解决 checkpatch 错误和/或警告(如果有)。重新生成并重复,直到补丁通过 checkpatch 测试。除非 checkpatch 错误是次要的空格类型,否则重新测试补丁。将补丁应用到内核 git 的另一个实例,以确保补丁干净地应用。
现在您可以发送补丁了。请运行 scripts/get_maintainer.pl 来确定应该将补丁发送给谁。请记住,补丁需要以纯文本形式发送,而不是作为附件。请确保您的电子邮件客户端可以发送纯文本消息。将补丁通过电子邮件发送给自己以测试您的客户端设置。运行 checkpatch 并应用收到的补丁。如果这两个步骤都通过,那么您就可以将补丁发送到 Linux 内核邮件列表了。git send-email 是发送补丁的最安全方法,可以避免电子邮件客户端的复杂性。请确保您的 .gitconfig 包含带有有效 smtpserver 的 sendemail。请查阅 git 手册页以获取详细信息。
有关发送补丁的规则和指南,请参阅内核源代码中的以下文档
- linux_git/Documentation/applying-patches.txt
- linux_git/Documentation/SubmitChecklist
- linux_git/Documentation/SubmittingDrivers
- linux_git/Documentation/SubmittingPatches
- linux_git/Documentation/stable_kernel_rules.txt
- linux_git/Documentation/stable_api_nonsense.txt
以下是其他测试指南和资源的列表
内核测试套件和项目
除了我们到目前为止讨论的测试资源外,还有一些开源项目和硬件供应商发起的项目值得一提。这些项目中的每一个都专注于内核的特定领域,在某些情况下,例如嵌入式或企业,内核被使用的特定空间。我们将在本节中介绍其中的几个。
Linux 测试项目 (LTP) 测试套件是用于测试 Linux 内核和相关功能的可靠性、鲁棒性和稳定性的工具集合。此测试套件可以通过添加新测试进行自定义,LTP 项目欢迎贡献。runltp 脚本默认测试以下子系统
- 文件系统压力测试
- 磁盘 I/O 测试
- 内存管理压力测试
- ipc 压力
- 调度器测试
- 命令功能验证测试
- 系统调用功能验证测试
LTP-DDT 是一个基于 LTP 的测试应用程序,其重点是测试嵌入式设备驱动程序。
Linux 驱动程序验证 项目的目标是提高 Linux 设备驱动程序的质量,开发用于设备驱动程序验证的集成平台,并采用最新的研究成果来提高验证工具的质量。
合规性测试
如果您曾经不得不将应用程序从一个 Unix 变体移植到另一个 Unix 变体,您就会理解 Linux 标准库 (LSB) 和 LSB 合规性测试套件的重要性。LSB 是 Linux 基金会的一个工作组,旨在通过减少各种 Linux 发行版之间的差异并确保应用程序在发行版之间的可移植性,来降低支持 Linux 平台的成本。如果说 Unix 世界的分歧教会了我们什么,那就是避免在 Linux 世界中出现分歧至关重要。这正是您可以获取 rpm 并将其转换为 deb 并安装和运行它的原因,这有多么棒。
静态分析和工具
静态分析工具在不执行代码的情况下分析代码,因此得名静态分析。有一些静态分析工具专门为分析 Linux 内核代码库而编写。Sparse 是 Linus Torvalds 专门为 Linux 内核编写的静态类型检查程序。Sparse 是一个语义解析器。它创建一个语义解析树来验证 C 语义。它执行惰性类型评估。内核构建系统支持 sparse,并提供 make 选项以启用 sparse 检查来编译内核。
- 对所有将重新编译的内核 C 文件运行 sparse
make C=1 allmodconfig
- 即使不需要重新编译,也对所有内核 C 文件运行 sparse
make C=2 allmodconfig
Sparse 资源
Smatch 分析源代码以检测编程逻辑错误。它可以检测逻辑错误,例如,尝试解锁已解锁的自旋锁。它被积极地用于检测 Linux 内核源代码中的逻辑错误。
- 在 Linux 内核上运行 smatch
make CHECK="~/path/to/smatch/smatch -p=kernel" C=1 bzImage modules | tee warns.txt
请按照说明从 smatch git 仓库获取 smatch 并进行编译。Smatch 正在开发中,说明不断变化。
那么我们如何处理 Sparse 和 Smatch 发现的所有这些语义和逻辑问题呢?其中一些问题仅限于一个例程和/或一个模块,可以很容易地修复。然而,由于代码的剪切和粘贴,其中一些语义问题本质上是全局性的。在某些情况下,当接口过时或略有更改时,需要进行大规模更改以更新多个源文件。这就是 Coccinelle 可以发挥作用的地方。Coccinelle 是一个程序匹配和转换引擎,它提供了 SmPL(语义补丁语言)来指定 C 代码中所需的匹配和转换。Coccinelle 最初的目标是执行 Linux 中的附带演化。例如,foo(int) 接口更改为 foo(int, char *),带有一个可选的第二个输入参数,该参数可以为 null。foo() 的所有用法都需要更新为新约定,这将是一项非常繁琐的任务。使用 Cocinelle,这项任务变得更容易,可以使用一个脚本来查找 foo(parameter1) 的所有实例,并将它们替换为 foo(parameter1, NULL)。完成此任务后,可以检查 foo() 的所有实例,以查看为 parameter2 传入 NULL 值是否是一个好的假设。有关 Cocinelle 以及它如何在修复包括 Linux 内核在内的各种项目中的问题中使用它的更多信息,请参阅项目页面:Cocinelle
参考资料
我们在本文中涵盖了很多内容。我为您留下一些参考资料,供您进一步阅读我们讨论的主题。
致谢
我要感谢 Oracle 的 Khalid Aziz 对本文的审阅、校对和改进提出的宝贵建议。特别感谢三星的 Mauro Chehab 和三星的 Guy Martin 在本文撰写各个阶段的审阅和反馈。我要感谢 Linux 基金会的 Greg Kroah-Hartman 的审阅。我特别感谢三星的 Ibrahim Haddad 的支持和鼓励,如果没有他的支持和鼓励,我可能永远不会着手撰写本文。