Linux dm-flakey 使用详解:基于 Device Mapper 周期性模拟设备故障与数据损坏的故障注入 target
dm-flakey 是 Linux 内核 Device Mapper 子系统中的一个专用 target,它建立在 linear 映射之上,但会按固定周期在"正常可用"与"不可靠故障"两种状态之间切换,从而以低成本、高可控的方式模拟磁盘随机掉盘、间歇性坏盘等场景,是文件系统与块层代码故障路径测试的常用工具。本文以 dm-flakey.rst 为主线,结合其内核实现 dm-flakey.c,完整讲解表语法、全部 feature 参数、数据破坏(corruption)的底层执行机制,并给出可直接复制运行的 dmsetup 实战示例,帮助你快速搭建属于自己的"故障注入测试盘"。
dm-flakey 定位:一个能周期"犯病"的线性映射
Device Mapper(DM)允许通过用户态工具(如 dmsetup)把若干块设备按某种 target 规则重映射为一个新的逻辑设备。dm-flakey 属于块层 DM 框架下的一个 target 类型,官方文档开篇即给出其定位:
该 target 与 linear target 相同,区别在于它会周期性表现出不可靠行为,在模拟故障设备以进行测试方面很有用。
从内核实现看,其目标类型声明位于 dm-flakey.c,名称为 "flakey",版本号 {1, 5, 0},并声明了 DM_TARGET_ZONED_HM | DM_TARGET_PASSES_CRYPTO 特性。头文件注释同样强调"仅供测试,模拟间歇性、灾难性设备故障"(Flakey: Used for testing only, simulates intermittent, catastrophic device failure.)。
该功能默认不启用,需在内核配置中打开 CONFIG_DM_FLAKEY(drivers/md/Kconfig 中描述为 "A target that intermittently fails I/O for debugging purposes."),其编译产物为独立的 dm-flakey.o 模块(见 drivers/md/Makefile)。配置开启后可选择静态编译或按需加载模块,测试环境常用 modprobe dm-flakey。
失效模型:up/down 间隔构成的"可用—故障"循环
dm-flakey 的失效模型非常朴素:从表加载那一刻开始计时,设备先可用 <up interval> 秒,随后进入 <down interval> 秒的不可靠期,如此循环往复。
对应到源码 flakey_map,每次 I/O 到达时都会根据"表加载时记录的时间戳 fc->start_time(jiffies)"计算已流逝的秒数并判定当前所处阶段:
elapsed = (jiffies - fc->start_time) / HZ;
if (elapsed % (fc->up_interval + fc->down_interval) >= fc->up_interval) {
/* 进入 down 期:按 feature 配置返回错误、静默丢弃或破坏数据 */
}
也就是说,每个完整周期长度为 up + down 秒,其中前 up 秒正常透传、后 down 秒施加故障。文档中还特别建议将 dm-flakey 与 dm-delay target 组合使用——后者可以延迟读写、或将读写定向到不同的底层设备(见 delay.rst),从而在同一套测试床中叠加"延迟 + 故障"两种异常。
表结构与参数语法
dm-flakey 的表(table)完整语法为:
<sector_start> <sector_len> flakey <dev path> <offset> <up interval> <down interval> \
[<num_features> [<feature arguments>]]
其中前两列 <sector_start> <sector_len> 是 Device Mapper 所有 target 共有的"逻辑起始扇区与长度",文档中展开的 target 专属参数依次如下:
| 参数 | 含义 |
|---|---|
<dev path> |
底层块设备的完整路径(如 /dev/sdb),或 major:minor 设备号 |
<offset> |
映射在底层设备上的起始扇区 |
<up interval> |
设备正常可用(透传 I/O)的秒数 |
<down interval> |
设备进入故障状态、开始返回错误的秒数 |
[<num_features> [...]] |
可选特性参数个数及具体参数(见下节) |
源码侧对强制参数做了严格校验(见 flakey_ctr):
- 参数少于 4 个直接报 "Invalid argument count";
<offset>必须是无符号整数扇区号;up、down各自的取值范围为0 ~ UINT_MAX,二者之和既不能为 0("Total (up + down) interval is zero"),也不能发生回绕溢出("Interval overflow")。
特性参数个数如何数?
<num_features> 计的是该特性组后续所有参数令牌的总数。这一点可以从 flakey_status 生成 dmsetup table 输出时的计数规则得到印证:corrupt_bio_byte 记为 5(关键字自身 + 4 个参数),random_read_corrupt / random_write_corrupt 各记为 2(关键字 + 1 个参数),error_reads、drop_writes、error_writes 各记为 1。解析侧通过 dm_arg_group 限定最多 11 个特性参数(见 parse_features)。
可选特性参数:五种"坏法"全解析
当不提供任何 feature 参数时,down 期间所有读、写 I/O 一律返回错误——对应源码中的默认回退逻辑(dm-flakey.c),即同时置位 ERROR_WRITES 与 ERROR_READS。
error_reads:读全部报错,写正常
down 期间所有读 I/O 都被以错误信号终止,而写 I/O 正常处理。源码中当读方向命中该 flag 时直接返回 DM_MAPIO_KILL(dm-flakey.c),相当于在映射层"掐死"这次读请求。文档提示它常用来模拟"读到一半设备就绪状态不稳"的场景。
drop_writes:写被静默吞掉,读正常
down 期间所有写 I/O 被静默忽略:既不下发到底层设备,也不报错,上层看到的是"写成功了",但数据实际丢失——这正是模拟"写缓存丢失、掉电丢数据"类故障的利器。源码实现为直接调用 bio_endio(bio) 并返回 DM_MAPIO_SUBMITTED(dm-flakey.c),即伪造一次成功结束。
error_writes:写全部报错,读正常
与 drop_writes 相反,down 期间写 I/O 显式返回错误,便于测试上层对"写失败"的报错与重试路径。源码通过 bio_io_error(bio) 结束请求(dm-flakey.c)。
三个基础 flag 之间存在互斥校验:drop_writes 与 error_writes 不能同时出现(重复出现或相互冲突都会在 parse_features 被拒绝)。
corrupt_bio_byte:定向篡改 bio 中的第 N 个字节
这是 dm-flakey 最具"破坏力"的特性,语法为:
corrupt_bio_byte <Nth_byte> <direction> <value> <flags>
<Nth_byte>:要替换的字节偏移,从 1 开始计数(1 即替换第一个字节)。源码内部会先减 1 再作为数组下标使用(corrupt_bio_data),取值范围校验为1 ~ UINT_MAX;<direction>:r表示破坏读数据,w表示破坏写数据,大小写均可(解析代码用strcasecmp判断,dm-flakey.c);方向为w时与drop_writes不兼容;<value>:要写入的字节值,合法区间0-255;<flags>:仅当bio->bi_opf包含全部所选标志时才执行替换,选中的过滤逻辑见宏all_corrupt_bio_flags_match(dm-flakey.c):(bio->bi_opf & flags) == flags。当flags = 0时不加过滤,命中所有该方向的 bio。
文档给出的两个可直接照抄的示例:
# 将 READ 类型 bio 数据中的第 32 个字节替换为值 1
corrupt_bio_byte 32 r 1 0
# 将携带 REQ_META(=32) 标志的写 bio 中的第 224 个字节替换为 0
corrupt_bio_byte 224 w 0 32
第二个例子演示了"精准定向投毒":REQ_META 标志值恰好为 32,配合方向 w,可将范围精确到文件系统元数据写路径,非常适合模拟"元数据被损坏"的介质故障。方向 r/w 与 error_reads/error_writes/drop_writes 之间还有一组成体系的互斥校验(见 dm-flakey.c),例如"drop_writes 不能与随机/定向的写破坏共存"。
random_read_corrupt / random_write_corrupt:按概率随机破坏
语法分别如下:
random_read_corrupt <probability>
random_write_corrupt <probability>
down 期间按 <probability> 概率在随机位置写入一个随机字节来破坏数据。probability 为 0 ~ 1000000000 的整数,语义是 0% ~ 100% 的破坏概率(因为 PROBABILITY_BASE = 1000000000,见 dm-flakey.c)。源码实现上取一个 64 位随机数并对 PROBABILITY_BASE 取模,模值小于概率参数即触发破坏(dm-flakey.c 与 flakey_end_io)。
各特性关键字都不允许重复出现;无法识别的关键字会得到 "Unrecognised flakey feature requested" 的报错。
源码视角:写破坏为什么要克隆 bio,读破坏又为何放在 end_io
深入 flakey_map 与 flakey_end_io 可以看到,读、写两类"破坏"的执行路径完全不同:
- 写方向的破坏:down 期间命中
corrupt_bio_byte(w) 或random_write_corrupt时,内核不会直接修改原 bio 的页——因为写 bio 的页可能正承载着上层用户的真实数据。它先调用 clone_bio 把数据整体拷贝到新分配的内存页上构成克隆 bio,只对克隆体实施字节篡改,再提交克隆体完成真正的写盘(dm-flakey.c)。克隆完成后通过clone_endio把bi_status回传给原始 bio。若内存分配失败,则该次写退化为正常透传(不破坏、不报错)。 - 读方向的破坏:读数据来自底层设备,
map阶段拿不到内容,因此读破坏被延后到 I/O 完成之后的flakey_end_io回调中执行:只有当读成功结束(!*error)且该 bio 在 down 期被标记为可破坏(pb->bio_can_corrupt)时,才按保存好的原始迭代器pb->saved_iter定位并替换对应字节(dm-flakey.c)。这也解释了为何error_reads与读破坏互斥——报错路径上没有可返回给上层的数据可供篡改。
字节定位与写入的公共实现是 corrupt_bio_common:它遍历 bio 的各个 segment,定位到目标字节所在的页,通过 kmap_local 写入后落盘,并打印一条包含 rw、bi_opf、扇区与尺寸的 DMDEBUG 日志(Corrupting data bio=...),供调试时用 dmsetup 配合动态调试开关观察。
实战:构造一条完整可用的 flakey 映射
环境准备
- 内核已启用
CONFIG_DM_FLAKEY(可静态编译或modprobe dm-flakey); - 用户态具备
dmsetup(device-mapper 工具); - 准备一块测试用底层设备,例如
/dev/sdb(切勿使用承载重要数据的设备)。
下面的示例沿用文档相邻 target 文档(如 linear.rst)中 blockdev --getsz 的惯用法,自动把整个设备映射为一块 flakey 盘。
示例一:每 10 秒"健康"、2 秒"全 I/O 报错",不指定任何 feature(默认读、写全部返回错误):
echo "0 $(blockdev --getsz /dev/sdb) flakey /dev/sdb 0 10 2" | dmsetup create flaky-ioerr
示例二:挂载点写入"静默丢失"(30 秒正常 + 5 秒丢弃一切写,期间读正常返回)——可用于验证掉电/写缓存丢失场景下的上层行为:
echo "0 $(blockdev --getsz /dev/sdb) flakey /dev/sdb 0 30 5 1 drop_writes" | dmsetup create flaky-drop
示例三:down 期把每次读请求的第 32 个字节篡改为 1(对应文档首个示例;5 表示后续共有 5 个令牌):
echo "0 $(blockdev --getsz /dev/sdb) flakey /dev/sdb 0 10 5 5 corrupt_bio_byte 32 r 1 0" \
| dmsetup create flaky-corrupt
示例四:对写路径的元数据精确投毒(写 bio 第 224 字节且仅当 bio 带 REQ_META(=32) 标志时改写为 0,对应文档第二个示例):
echo "0 $(blockdev --getsz /dev/sdb) flakey /dev/sdb 0 10 5 5 corrupt_bio_byte 224 w 0 32" \
| dmsetup create flaky-meta
上述设备创建后可正常执行 mkfs、mount、运行目标测试程序或故障演练脚本;在 down 窗口内即可观察到预期的 I/O 错误、数据丢失或字节损坏。常用的核查与清理命令:
dmsetup table flaky-corrupt # 回显当前表,可用于核对 feature 参数是否完整
dmsetup status flaky-corrupt # 查看状态信息
dmsetup remove flaky-corrupt # 卸载该映射
其中 dmsetup table 的输出直接来自 flakey_status 的 STATUSTYPE_TABLE 分支,会按上文计数规则重新拼出带 num_features 的完整表行,非常适合用来确认手工拼写的参数个数是否正确。
边界行为与使用注意事项
从源码可以确认以下边界细节,使用时应心中有数:
- Zone 管理类操作透传:对 zoned 设备的 zone mgmt 类 bio,在 flakey_map 中直接走正常映射,不参与故障注入;在启用
CONFIG_BLK_DEV_ZONED时还实现了flakey_report_zones以支持 host-managed zoned 设备场景; - flush/discard 支持:
num_flush_bios与num_discard_bios均置为 1,即每个 flush/discard 请求会被透传处理,故障期对其施加的仍是上述错误/丢弃/破坏策略; - ioctl 透传受限:flakey_prepare_ioctl 规定只有
offset == 0且映射长度与底层设备完全一致时,才把 ioctl(如设备参数查询)转发给底层设备,否则拒绝转发,避免因映射错位产生误导性的设备信息; - 状态回显:
dmsetup status(STATUSTYPE_INFO)默认返回空串,完整表信息应通过dmsetup table查看; - 仅用于测试:该 target 从 Kconfig 到代码注释都明确其为调试用途("for debugging purposes"),不应作为生产存储方案的一部分;它与相邻的 dm-dust、dm-delay 等一样属于 Device Mapper 的"故障与异常注入"工具族,dm-init.rst 甚至将
flakey明确标注为 "constrained, meant for test",即也支持在内核引导早期通过dm-mod.create=参数加载,但用途限定为测试。
组合思路:把 flakey 放进更真实的故障场景
单靠 flakey 只能模拟"周期性整机故障",而真实系统故障往往叠加延迟、部分设备失效等因素。文档明确建议与 dm-delay 联合使用:dm-delay 可分别针对读、写、flush 设置毫秒级延迟,甚至把写操作重定向到另一块设备(delay.rst 提供了完整的 3/6/9 参数三套示例脚本)。一个典型的叠加玩法是先用 dm-delay 构造"写慢盘",再把 flakey 或 flakey+drop_writes 套在其上层或并行映射,从而在测试文件系统时同时覆盖"慢写 + 静默丢写 + 周期性全故障"的复合故障面;配合 corrupt_bio_byte 224 w 0 32 这类元数据定向破坏,还能精准验证日志型文件系统对元数据校验失败的恢复路径。
相关仓库文件速查
- 文档正文:Documentation/admin-guide/device-mapper/dm-flakey.rst
- 内核实现:drivers/md/dm-flakey.c
- 内核配置项与模块编译:drivers/md/Kconfig、drivers/md/Makefile
- 相邻参考文档:Documentation/admin-guide/device-mapper/linear.rst、Documentation/admin-guide/device-mapper/delay.rst、Documentation/admin-guide/device-mapper/dm-init.rst
- Device Mapper target 总索引:Documentation/admin-guide/device-mapper/index.rst
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00