ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Linux 内核 Selftests(kselftest)实战指南:构建、运行、打包与贡献内核测试

Linux 内核 Selftests(kselftest)实战指南:构建、运行、打包与贡献内核测试 Linux 内核 Selftestskselftest实战指南构建、运行、打包与贡献内核测试【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux本文基于内核官方文档 kselftest.rst 及其对应的框架源码自测框架顶层 Makefile、运行入口脚本、测试运行器 与 TAP 输出头文件系统讲解 Linux 内核自测套件 kselftest 的完整使用流程从构建与运行、按子系统选取测试、超时控制与安装部署到打包分发和编写新测试用户态 Test Harness 与内核态 Test Module。读完后你可以独立完成 kselftest 的全流程操作并按照内核社区规范为自己的驱动或子系统添加符合 TAP 标准的自测用例。什么是 kselftest设计定位与两种测试形态内核在tools/testing/selftests/目录下维护了一整套 self tests。这些测试的设计目标是小体量、高针对性——每个测试只演练内核中某一条代码路径exercise individual code paths而不是做大规模压力测试。官方文档明确要求测试应在构建、安装并启动内核之后再运行即测试对象是正在运行的这个内核本身。kselftest 有一个重要的可移植性约定主线mainline的 kselftest 可以在较老的 stable 内核上运行。多个测试环test ring正是在 stable 版本上跑主线 kselftest 套件的。背后的理由是当一个新测试被加入、用于回归验证某个历史 bug 时它必须同样能在旧内核上跑起来。因此编写测试时要保证代码能够继续测试旧内核并且在新版本上优雅地跳过skip gracefully不再适用的用例。kselftest 以用户态进程方式运行由此衍生出两种测试形态形态适用场景核心设施Test Harness用户态可以在用户空间编写和运行的测试kselftest_harness.h、kselftest.hTest Module内核态必须在内核内部才能验证的逻辑kselftest_module.h、module.sh另外hot-plugCPU/内存热插拔测试在某些系统上可能永远挂起等待 CPU 或内存进入可下线状态。为此框架专门设置了 hot-plug 运行档位默认受限模式run_tests默认以 safe mode 运行 hot-plug 测试范围受限——CPU hotplug 只在单个CPU 上运行而不是所有支持热插拔的 CPUmemory hotplug 只操作可热插拔内存的2%而不是 10%。全量模式通过专门的hotplug/run_hotplug目标运行完整范围的 hot-plug 测试。从源码可以印证这一设计自测框架 Makefile 中单独定义了TARGETS_HOTPLUG cpu-hotplug和memory-hotplug且run_hotplug目标调用的是各子目录的run_full_test与常规run_tests路径分离。构建并运行 selftests最基础的三步流程如下工作目录为内核源码根目录# 1. 准备用户态头文件并构建测试 $ make headers $ make -C tools/testing/selftests # 2. 构建后运行测试hotplug 测试默认走受限模式 $ make -C tools/testing/selftests run_tests # 3. 一条命令完成构建 运行 $ make kselftest注意部分测试需要 root 权限建议以 root 运行以获得完整覆盖。顶层make kselftest的实际行为可以在 顶层 Makefile 中确认PHONY kselftest kselftest: headers $(Q)unset sub_make_done; $(MAKE) -C $(srctree)/tools/testing/selftests run_tests kselftest-%: headers FORCE $(Q)unset sub_make_done; $(MAKE) -C $(srctree)/tools/testing/selftests $*即kselftest依赖headers随后等价于进入tools/testing/selftests执行run_tests而kselftest-%通配目标如kselftest-clean、kselftest-install、kselftest-merge会把-后的后缀直接透传给自测 Makefile。顶层make help对这几个目标的说明是kselftest—— 构建并运行内核自测需先构建、安装并启动内核root 运行以获得完整覆盖kselftest-all—— 仅构建kselftest-install—— 构建并安装kselftest-clean—— 清除所有生成文件kselftest-merge—— 把所有 kselftest 的config依赖合并进现有.config。将构建产物输出到独立目录O 与 KBUILD_OUTPUTkselftest 支持把输出文件保存在独立目录中。两种写法都要求工作目录是内核源码根目录# 写法一O 命令行变量 $ make O/tmp/kselftest kselftest # 写法二KBUILD_OUTPUT 环境变量 $ export KBUILD_OUTPUT/tmp/kselftest; make kselftestO的赋值优先级高于KBUILD_OUTPUT环境变量。这一优先级在 自测 Makefile 中直接体现当O来自命令行时覆盖KBUILD_OUTPUT若指定了独立输出目录实际构建根会被设为输出目录/kselftest并且KHDR_INCLUDES指向该目录下的usr/include即make headers安装好的头文件避免污染源码树ifeq ($(origin O), command line) KBUILD_OUTPUT : $(O) endif ... BUILD : $(abs_objtree)/kselftest KHDR_INCLUDES : -isystem ${abs_objtree}/usr/includesummary 模式默认情况下命令会运行测试并打印完整的 pass/fail 报告。kselftest 支持summary选项让结果更易读——启用后每个测试的详细结果写入/tmp/testname文件$ make summary1 kselftest这一机制同样适用于后文的运行部分自测一节。运行部分 selftestsTARGETS 与 SKIP_TARGETS可以在 make 命令行上用TARGETS变量指定单个测试或一组测试。所有合法 target 的完整清单见 tools/testing/selftests/Makefile 开头的TARGETS ...列表保持字母序当前版本涵盖了acct、bpf、cgroup、futex、kvm、mm、net、ptrace、rust、seccomp、sched_ext、timers、x86等数十个子系统目录。只运行单个子系统的测试$ make -C tools/testing/selftests TARGETSptrace run_tests一次构建并运行多个测试$ make TARGETSsize timers kselftest配合独立输出目录$ make O/tmp/kselftest TARGETSsize timers kselftest $ export KBUILD_OUTPUT/tmp/kselftest; make TARGETSsize timers kselftestSKIP_TARGETS排除列表用SKIP_TARGETS变量可以从TARGETS中排除一个或多个目标# 运行除 ptrace 之外的所有测试 $ make -C tools/testing/selftests SKIP_TARGETSptrace run_tests # 跳过多个测试 $ make SKIP_TARGETSsize timers kselftest # 受限运行列表 专门的排除列表组合使用 $ make TARGETSbreakpoints size timers SKIP_TARGETSsize kselftest两个值得注意的实现细节均可在 Makefile 中确认默认就有排除项由于 BPF 测试的构建期依赖较新、安装成本较高Makefile 默认设置SKIP_TARGETS ? bpf sched_ext。因此直接make kselftest时 BPF 相关套件是被排除的要跑它需要显式TARGETSbpf同时注意该默认值可以被命令行覆盖的语义——用户提供的SKIP_TARGETS会替换默认值。quicktest 模式timers默认在TARGETS之外仅当quicktest ! 1时加入make quicktest1 kselftest可以跳过耗时较长的 timers 测试。强制要求所有 target 构建成功FORCE_TARGETS1默认情况下只要至少一个target 构建无错整体构建就算成功——这对 CI 环境可能具有误导性静默的部分构建。设置FORCE_TARGETS1后make 会在任何一个 target 构建失败时立即中止$ make -C tools/testing/selftests FORCE_TARGETS1该约束同时作用于all和install目标。对应实现见 Makefile# User can set FORCE_TARGETS to 1 to require all targets to be successfully # built; make will fail if any of the targets cannot be built. FORCE_TARGETS ?在all目标的循环里每个子目录构建失败时若设置了FORCE_TARGETS就|| exit否则只是把ret累乘全部跑完后返回。运行完整范围的 hotplug 自测如前所述run_tests里 hot-plug 测试默认以受限范围运行。若要运行完整范围全部可热插拔 CPU、更大比例的内存# 构建 hotplug 测试 $ make -C tools/testing/selftests hotplug # 运行 hotplug 测试 $ make -C tools/testing/selftests run_hotplug同样注意部分测试需要 root 权限。从 Makefile 看hotplug目标只对TARGETS_HOTPLUGcpu-hotplug、memory-hotplug构建run_hotplug依赖hotplug并在各子目录调用run_full_test另有配套的clean_hotplug目标负责清理。安装 selftests使用install目标底层调用 kselftest_install.sh 工具链可以把 selftests 安装到默认位置tools/testing/selftests/kselftest_install也可以通过INSTALL_PATHmake 变量指定用户自定义位置# 安装到默认位置 $ make -C tools/testing/selftests install # 安装到用户指定位置 $ make -C tools/testing/selftests install INSTALL_PATH/some/other/path从 Makefile 的 install 目标 可以看到安装动作的完整构成默认安装根为$(BUILD)/kselftest_installKSFT_INSTALL_PATH ? $(BUILD)/kselftest_install最终导出为INSTALL_PATH复制框架脚本kselftest/module.sh、kselftest/runner.sh、kselftest/prefix.pl、kselftest/ktap_helpers.sh、kselftest/ksft.py以及run_kselftest.sh对每个 TARGET 递归执行install并调用emit_tests生成kselftest-list.txt运行清单构建失败导致不存在的目录会被跳过且不写入清单若处于 git 仓库中还会把git describe HEAD的版本号写入安装目录的VERSION文件。运行已安装的 selftestsrun_kselftest.sh安装目录以及自测 tarball中带有run_kselftest.sh脚本直接运行即可$ cd kselftest_install $ ./run_kselftest.sh # 部分测试需要 root 权限 # 列出所有可用测试collection:test 条目 $ ./run_kselftest.sh -l # -c 运行整个 collection-t 运行单个测试二者都可多次使用 $ ./run_kselftest.sh -c size -c seccomp -t timers:posix_timers -t timer:nanosleep # 查看完整帮助 $ ./run_kselftest.sh -h对照 run_kselftest.sh 源码完整的选项集比文档列举的更丰富选项作用-s/--summary打印摘要详细日志写入output.log与-p互斥-p/--per-test-log [DIR]每个测试单独输出日志默认/tmp文件名即测试名与-s互斥-t/--test COLLECTION:TEST运行指定 collection 中的某个测试-S/--skip COLLECTION:TEST跳过指定测试-c/--collection COLLECTION运行整个 collection-l/--list列出全部collection:test条目-d/--dry-run只打印将执行的命令不真正运行-f/--no-error-on-fail测试失败时不返回错误退出码-n/--netns每个测试在独立的网络命名空间中运行-o/--override-timeout覆盖每个测试的超时秒数-h/--help显示用法脚本的工作方式是读取同目录的kselftest-list.txt得到可用测试列表按 collection 分组后cd进各 collection 目录调用 runner.sh 中的run_many执行最后汇总 TAP 计数只要存在失败且未加-f就以KSFT_FAIL退出。超时控制settings 文件与 --override-timeoutSelftests 的设计目标是快速因此每个测试默认超时 45 秒。这个默认值定义在 runner.sh# Defaults for settings file fields: # timeout how many seconds to let each test run before running # over our soft timeout limit. export kselftest_default_timeout45测试可以通过在自己的目录下放一个settings文件来覆盖默认超时——在settings里设置timeout变量为该测试期望的上限。目前只有少数测试把超时提高到 45 秒以上且项目致力于维持这种快的约束。runner 的加载逻辑runner.sh是每运行一个测试先重置kselftest_timeout为默认值然后解析$BASE_DIR/$DIR/settings中形如fieldvalue的行#开头为注释写入对应变量命令行的--override-timeout优先级高于 settings 文件。超时通过系统timeout命令实现双重调用--foreground timeout s timeout s以便前台信号也能终止测试超时退出码 124 会被识别并在 TAP 输出中标记# TIMEOUT n seconds。如果你有权限控制运行测试的系统可以在命令行直接配置更大的或更小的超时。例如使用 165 秒$ ./run_kselftest.sh --override-timeout 165官方文档特别强调selftests 中的超时被设计为非致命not fatal——因为运行环境的变化本身就可能改变测试耗时。查看 TAP 输出即可判断是否发生了超时了解某测试必须跑在特定时间内完成的运行器可以自行选择把超时当作致命错误处理。runner.sh 还提供一个文档未展开的实用细节测试可以通过环境变量KSELFTEST_测试名大写化_ARGS传入命令行参数非字母数字字符替换为_例如rtctest对应KSELFTEST_RTCTEST_ARGS/dev/rtc1、cpu-on-off-test.sh对应KSELFTEST_CPU_ON_OFF_TEST_SH_ARGS-a -p 10这对需要指定设备节点或参数的测试很有用。打包 selftestsgen_tar当测试需要在另一台系统上运行时如交叉测试目标板打包就很有用$ make -C tools/testing/selftests gen_tar这会在INSTALL_PATH/kselftest-packages目录生成 tarball。默认使用.gz格式压缩格式可用 make 变量FORMAT覆盖——任何 tar 的 auto-compress 选项可识别的扩展名都支持例如$ make -C tools/testing/selftests gen_tar FORMAT.xz实现见 Makefile 的 gen_tar 目标FORMAT ? .gz最终执行tar caf TAR_PATH ...-a即按扩展名自动选择压缩器。由于make gen_tar会先调用make install因此可以直接复用运行部分自测一节的变量来打包子集$ make -C tools/testing/selftests gen_tar TARGETSsize FORMAT.xz编写新测试总体规则在贡献 selftest 之前需要遵循官方文档给出的通用规则非 root 时尽可能多做graceful degradation不要耗时太久不要在任何架构上破坏构建当你的特性未配置时不要让顶层make run_tests失败测试输出必须遵循 TAPTest Anything Protocol标准以保证测试质量并捕获带有具体细节的失败/错误。kselftest.h 与 kselftest_harness.h 提供了输出测试结果的封装pass / fail / exit / skip 消息都应通过这些封装产生。CI 系统可以方便地解析 TAP 输出来判定测试结果。TAP 输出的具体形态可以从 kselftest.h 中确认ksft_print_header()输出TAP version 13ksft_set_plan(n)输出1..n每条用例输出ok n .../not ok n ...skip/xfail/xfail 以# SKIP、# XFAIL、# XPASS指令标注结束时输出# Totals: pass:.. fail:.. xfail:.. xpass:.. skip:.. error:..。对应的进程退出码定义kselftest.h退出码含义KSFT_PASS 0通过KSFT_FAIL 1失败KSFT_XFAIL 2预期失败KSFT_XPASS 3预期失败但通过了KSFT_SKIP 4跳过如 module.sh 中非 root 或模块缺失时以 4 退出编写新测试Makefile 与目录规范细节复用 lib.mk不要重造轮子。在你的测试 Makefile 中 include 它CFLAGS、TEST_GEN_PROGS等标志和二进制生成变量应在 include 之前按需指定。文档给出的最小示例CFLAGS $(KHDR_INCLUDES) TEST_GEN_PROGS : close_range_test include ../lib.mk各类TEST_*变量的语义变量含义TEST_PROGS/TEST_GEN_PROGS默认被测试的可执行文件前者为仓库中现成的脚本/二进制后者是编译期生成的TEST_GEN_MODS_DIR需要在测试开始前先构建内核模块的测试使用变量包含模块所在目录名TEST_CUSTOM_PROGS需要自定义构建规则、不能用通用规则构建的测试由通用run_tests执行TEST_PROGSshell 脚本时测试 shell 脚本务必确保可执行位已设置否则 lib.mk 的 run_tests 会产生警告TEST_PROGS_EXTENDED/TEST_GEN_PROGS_EXTENDED默认不测试的可执行文件按需运行TEST_FILES/TEST_GEN_FILES测试用到的数据文件TEST_INCLUDES类似TEST_FILES列出导出/安装测试时需要携带的文件但有两点不同跨目录的符号链接会被保留复制到输出目录时保留tools/testing/selftests/之下的路径部分。它专门用于列出 selftests 层级中其他目录里的依赖其他规范要求优先使用内核源码树git 仓库内的头文件其次才是系统头文件。以本内核发布对应的头文件而非发行版安装的头文件为主要对象才能发现真正的回归。在 Makefile 中用KHDR_INCLUDES引用内核源码头文件。测试需要特定内核配置项时在测试目录里放一个config文件来启用它们文档举例tools/testing/selftests/android/config。顶层的make kselftest-merge会找出所有这类config含config.$(UTS_MACHINE)并用merge_config.sh合并进当前.config见 顶层 Makefile。在测试目录创建.gitignore把所有生成物写进去。把新测试名加入tools/testing/selftests/Makefile的TARGETS例如TARGETS android。所有改动必须通过以下验证矩阵kselftest-{all,install,clean,gen_tar} kselftest-{all,install,clean,gen_tar} Oabs_path kselftest-{all,install,clean,gen_tar} Orel_path make -C tools/testing/selftests {all,install,clean,gen_tar} make -C tools/testing/selftests {all,install,clean,gen_tar} Oabs_path make -C tools/testing/selftests {all,install,clean,gen_tar} Orel_pathTest Module把内核模块接入 kselftestkselftest 从用户态测试内核但有时必须在内核内部才能验证——做法是编写一个测试模块test module再用一个 shell 脚本测试运行器把它接入 kselftest 框架。两个关键文件tools/testing/selftests/kselftest_module.h —— 辅助编写 kselftest 用内核模块的头文件tools/testing/selftests/kselftest/module.sh —— 负责模块加载/卸载的通用运行器。污点taint规则测试模块应当以TAINT_TEST污点内核。位于tools/testing/目录下的模块、或使用了kselftest_module.h的模块会自动做到这一点否则需要在模块源码中显式添加MODULE_INFO(test, Y)。不加载模块的 selftests 通常不应污点内核而在确实加载了非测试模块的场景下可以从用户态通过写/proc/sys/kernel/tainted施加 TEST_TAINT。典型接入步骤以 lib/ 测试为例创建测试模块创建负责加载/卸载模块的测试脚本如tools/testing/selftests/lib/bitmap.sh在 config 文件中添加配置行如tools/testing/selftests/lib/config把测试脚本加入 Makefile如tools/testing/selftests/lib/Makefile验证假设已启动了一棵新鲜构建的内核# Assumes you have booted a fresh build of this kernel tree cd /path/to/linux/tree make kselftest-merge make modules sudo make modules_install make TARGETSlib kselftestmodule.sh的实际行为源码很直观解析description module_name [args]参数 → 检查 root[ -w /dev ]否则skip please run as root以 4 退出→ 用modprobe -q -n module预检模块是否存在不存在则 skip→modprobe -q module加载、成功后modprobe -q -r卸载并输出ok失败则fail退出 1。最简测试模块示例// SPDX-License-Identifier: GPL-2.0 #define pr_fmt(fmt) KBUILD_MODNAME : fmt #include ../tools/testing/selftests/kselftest_module.h KSTM_MODULE_GLOBALS(); /* * Kernel module for testing the foobinator */ static int __init test_function() { ... } static void __init selftest(void) { KSTM_CHECK_ZERO(do_test_case(, 0)); } KSTM_MODULE_LOADERS(test_foo); MODULE_AUTHOR(John Developer jdfooman.org); MODULE_LICENSE(GPL); MODULE_INFO(test, Y);配套的测试脚本只需一行#!/bin/bash # SPDX-License-Identifier: GPL-2.0 $(dirname $0)/../kselftest/module.sh foo test_fooTest Harness用户态测试的 C 单测框架kselftest_harness.h 提供了一组构建用户态测试的助手内核态测试请参考上一节的 Test Module。官方文档指定的参考实现是tools/testing/selftests/seccomp/seccomp_bpf.c。头文件自带的示例展示了基本用法#include kselftest_harness.h TEST(standalone_test) { do_some_stuff; EXPECT_GT(10, stuff) { stuff_state_t state; enumerate_stuff_state(state); TH_LOG(expectation failed with state: %s, state.msg); } more_stuff; ASSERT_NE(some_stuff, NULL) TH_LOG(how did it happen?!); last_stuff; EXPECT_EQ(0, last_stuff); } FIXTURE(my_fixture) { mytype_t *data; int awesomeness_level; }; FIXTURE_SETUP(my_fixture) { self-data mytype_new(); ASSERT_NE(NULL, self-data); } FIXTURE_TEARDOWN(my_fixture) { mytype_free(self-data); } TEST_F(my_fixture, data_is_good) { EXPECT_EQ(1, is_my_data_good(self-data)); } TEST_HARNESS_MAIN框架暴露的核心宏见 kselftest_harness.h 的 kernel-doc 注释定义与组织TEST独立测试、TEST_Ffixture 测试、FIXTURE/FIXTURE_DATA/FIXTURE_SETUP/FIXTURE_TEARDOWNfixture 声明与前后置钩子、TEST_HARNESS_MAIN自动生成 main 并注册所有测试、TEST_SIGNAL信号处理测试、FIXTURE_VARIANT/FIXTURE_VARIANT_ADD参数化 fixture 变体日志TH_LOG(fmt, ...)可选调试日志可用#define TH_LOG_ENABLED 1/0开关默认开启SKIP(statement, fmt, ...)报告跳过原因并置KSFT_SKIP退出码断言算子ASSERT_EQ/NE/LT/LE/GT/GE/NULL/TRUE/FALSE/STREQ/STRNE—— 失败即中止当前测试EXPECT_EQ/NE/LT/LE/GT/GE/NULL/TRUE/FALSE/STREQ/STRNE—— 失败仅记录、继续执行ASSERT 系列可在失败后追加TH_LOG(...)打印现场信息。需要注意的一个实现细节harness 内部有自己的TEST_TIMEOUT_DEFAULT 30秒用于子进程类测试的看门狗这与 runner 层的 45 秒软超时kselftest_default_timeout45是两个不同层级的机制——前者管单个 C 测试用例内部的执行后者管整个测试可执行文件在 runner 里的运行时间。小结与延伸阅读kselftest 的完整工作链路是make headers准备头文件 → 各子目录经lib.mk统一构建TARGETS选择范围SKIP_TARGETS/quicktest/FORCE_TARGETS控制行为→run_tests由 runner.sh 逐个执行、以 45 秒软超时保护并产出 TAP 输出 →install生成含run_kselftest.sh与kselftest-list.txt的独立部署目录 →gen_tar打包分发到其他系统。编写新测试时用户态优先选用kselftest_harness.h的TEST/TEST_F框架内核态则用kselftest_module.hmodule.sh的组合并严格遵守 TAP 输出与kselftest-{all,install,clean,gen_tar}验证矩阵。相关文档与源码入口官方文档Documentation/dev-tools/kselftest.rst本文主体来源、设备类测试文档框架核心自测 Makefile、lib.mk、run_kselftest.sh、runner.sh测试编写kselftest.h、kselftest_harness.h、kselftest_module.h、module.sh【免费下载链接】linuxLinux kernel source tree项目地址: https://gitcode.com/GitHub_Trending/li/linux创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表