本节摘要:驱动的测试要主动制造恶劣环境。本节给出功能、并发、异常三个层次的测试设计,讲清错误注入框架如何把"失败路径"从理论变成用例,压力工具如何逼近真实负载,以及长时间浸泡测试该盯哪些指标。
功能测试回答"正常用好不好使",压力测试回答"往死里用会不会坏"。驱动上线后面对的是真实世界:多进程并发抢设备、内存不足时的分配失败、设备中途被拔、系统在任意瞬间休眠——这些场景每一条都对应一类真实故障,而它们几乎都不会在功能测试里自然出现。测试的责任是把它们提前逼出来。
把接口的输入空间切成正交维度,每个维度单独测边界:长度零、长度上限、长度超限;只读命令写只读寄存器;设备未打开就发控制命令。第 6.4 节的校验清单直接翻个面就是用例清单——每条校验规则都应该有一条"喂给它非法值"的用例。这个层次的价值是把"实现里写了但没测过"的分支清零,产出快、成本低。
6.1 节的教训是竞态"不可稳定复现",压力测试的对策是提高碰撞频率:把本该几天一遇的交错压缩到几分钟内高频出现。
# 终端一与终端二同时高频写设备,制造 6.1 节式对撞 $ while true; do echo 1 > /dev/led0; done $ while true; do echo 0 > /dev/led0; done # 第三路做开关联锁:反复打开关闭设备 $ while true; do cat /dev/led0 > /dev/null; sleep 0.001; done
压测的同时开着锁检查器与内存检查器(7.2 节工具谱系的运行期两员),任何锁序反转、休眠违规、越界访问都会当场自陷。判定标准不是"跑一会儿没崩就过":每轮压测要有明确时长(小时级起步)、有资源水位监控(内存与描述符是否缓慢增长——泄漏的典型症状)、有压测后的功能回归。
资源泄漏的监控有个简单有效的土办法:压测前后各读一次系统内存与设备引用计数,长期浸泡中每小时记录一次——平稳的水位线是唯一合格的曲线。
驱动的错误处理代码(probe 的回滚链、每处分配失败的出口)在正常环境下永远不执行——设备好好的,分配次次成功。但 3.1 节就说过,错误路径是资源泄漏与状态错乱的高产区。错误注入框架的做法是主动让它们失败:
# 内核错误注入框架:让指定概率的内存分配失败 $ cd /sys/kernel/debug/fail_page_alloc $ echo 10 > probability # 十个百分点失败率 $ echo 1 > task-filter # 只影响带标记的目标进程 $ echo 1 > verbose # 记录每次注入 # 对目标驱动的测试程序打上标记后反复执行 $ ./led_test --fail-mark & # 测试程序自带注入标记
这套机制的威力在于系统性:失败率从零缓慢上调,驱动的每条失败路径都会被轮流踩到。合格的驱动在注入下表现是"优雅降级"——操作返回错误、资源不泄漏、设备可继续使用;不合格的驱动当场死机或进入永久失效态。注入测试跑一轮,等于把错误处理代码的测试覆盖率从近乎零拉满。
除了内存分配,注入点还包括:DMA 映射失败、设备探测超时、中断丢失——每一个都对应 7.1 节案例二那类"低概率状态故障"的诱发条件。
功能与异常测的是"对不对",负载测的是"扛不扛"。块设备用标准块级压测工具打混合读写(按目标设备的介质特性调队列深度与块大小);网络设备用流量发生器打线速小包(最恶劣的包率场景);输入类设备用事件注入工具模拟高频事件。压测时盯三个指标:吞吐是否达设计值、延迟直方图的尾部(百万分位)是否失控、处理器占用是否异常爬升——第三项是许多"越跑越慢"缺陷的第一信号。
| 层次 | 目标 | 手段 | 时长量级 |
|---|---|---|---|
| 功能正交 | 校验规则逐条验证 | 边界值用例矩阵 | 分钟 |
| 并发压力 | 竞态与锁序 | 多路并发脚本加运行期检查器 | 小时 |
| 异常注入 | 失败路径全走通 | 错误注入框架分级注入 | 小时 |
| 负载仿真 | 吞吐与尾延迟 | 压测工具按设备类型施压 | 小时 |
| 长期浸泡 | 泄漏与老化 | 全部手段低速混合长跑 | 天 |
所有测试通过后,还要让设备在"接近真实但更恶劣"的混合负载下连跑数天:并发读写、周期性休眠唤醒、随机注入、定时拔插(可热拔的设备)。浸泡的价值是捕捉累积效应——每次泄漏一个字节的内存、每次唤醒后概率性丢失的一个状态位,短期测试全数漏网,浸泡七天后水落石出。7.1 节案例二那个"每二十次唤醒一次"的故障,在浸泡测试的休眠循环加速下,从"老化测试偶见"变成"半小时必现"——浸泡不是时间够长,是让触发条件高频化。
⚠️ 常见坑:压测只跑通不检查。压力下"没崩"不等于"没坏"——数据可能悄悄错了、水位可能慢慢涨了。每轮压测必须配结果校验(读回校验、状态一致性断言)与水位快照,否则只是表演。
设计用例不凭空想,三个源头供你源源不断取材。源头一:校验规则的镜像。6.4 节的每条校验(长度上限、白名单、只读保护)翻一面就是一组非法输入用例——校验清单多长,用例清单就该多长。源头二:回调契约的对称面。probe 与 remove 要对称,那就设计"装入后立刻卸出、卸出后再装入"的循环用例;打开与关闭要配对,那就并发开关数千次——对称契约的破坏往往在循环中现形。源头三:故障史。7.1 节两起案例的触发条件(匹配串错版、休眠唤醒循环)固化成回归用例后,同类故障永不复发。测试资产的复利来自故障史的沉淀:每次排错结束多一条用例,测试套件越用越锋利。
# 一条最小回归脚本:案例一与案例二的触发条件固化 for i in $(seq 1 200); do modprobe board_led && rmmod board_led # 循环装卸:probe/remove 对称 done for i in $(seq 1 500); do rtcwake -m mem -s 2 # 循环休眠唤醒:电源回调检验 done grep -c "board-led" /proc/device-tree/led/compatible # 匹配串在位确认
这段脚本正是浸泡测试的骨架:两个高危循环加一项静态确认,跑一夜等于手动验证数周。把它交给持续集成,故障回归线就算立起来了。
追问一:压力测试要压到什么程度才算过关? 分两层看。功能层以"零泄漏、零告警"为合格线——内存与引用计数在压测前后必须持平,运行期检查一个不报;性能层则以"不劣化"为合格线:对比压测前后的尾延迟与处理器占用,爬升即判负。绝对吞吐数字反而不必强求——它与硬件规格强相关,驱动验收要看的是自己的改动有没有让曲线变差,而不是跑赢某块特定的板子。数字目标留给产品团队定,驱动工程师守住的是趋势。
追问二:测试通过了,就等于驱动稳定了吗? 测试只能证明"已知故障模式不存在",不能证明稳定。三层用例覆盖的是设计时可预见的失败面,真正的稳定性的另一半在 7.1 节那种现场——用户会用你没想到的姿势组合操作。所以测试的输出不只是一份"通过"的结论,还有一份"已覆盖清单":哪些路径测了、哪些测不了(真实断电、电磁干扰、老化),后者交给设计余量与现场反馈。把测试报告当边界声明来读,比当安全证明来读更诚实。
静态与动态手段之外,还有最后一类极端场景——驱动真的崩了。下一节学习读懂内核留下的"遗书"。