这是一篇真实故障复盘。环境是一块 Western Digital HC620 Host-Managed SMR 硬盘,使用 Btrfs zoned,挂载在 /mnt/disk1,系统为 Ubuntu 26.04 LTS,内核为:
|
|
故障不是普通的硬盘速度下降,而是先后出现了两种严重异常:
btrfs-cleaner和btrfs-transaction进入不可中断的D状态,文件系统无法卸载;- Btrfs 事务以
-11中止,内核主动把文件系统切换成只读。
两次故障都发生在数 TB 级删除、复制和 zoned 空间回收附近。最终没有重装系统,也没有执行 btrfs check --repair。本文尽可能保留现场日志、判断过程、实际命令和没有被证实的部分,供遇到相似问题的人参考。
注意:这不是“HC620 上所有 Btrfs 都必然出错”的证明,也不能仅凭一段调用栈确定具体内核补丁。文中内核和 Ubuntu 软件包版本是 2026 年 7 月 24 日的现场信息,实际操作前应重新查询当前仓库。
环境与触发过程
这块盘主要保存备份和冷数据。第一次故障前的操作大致是:
|
|
HC620 是 Host-Managed SMR。磁盘按 zone 管理,现场显示每个 zone 为:
|
|
在这类设备上,已经写过的 zone 不能像普通 CMR 硬盘一样任意覆盖。删除文件只是解除数据块引用,其中的无效空间需要等 block group 回收、有效数据迁移以及 zone reset 后才能重新利用。Btrfs 会把这部分空间显示为:
|
|
因此,“删除 3TB 后立即再写 3TB”不只是一次删除加一次复制。后台可能同时发生:
|
|
这成为后续分析调用栈的重要背景。
第一次故障:btrfs-cleaner 长时间阻塞
最初日志包含:
|
|
关键调用链是:
|
|
把它从下往上读,过程是:
|
|
日志还提示 mutex 很可能由同一个 btrfs-cleaner 线程持有。结合调用链,现场判断是:清理线程在 block-group 删除、zone finish 和新 chunk 分配交叠时进入了锁等待。随后 btrfs-transaction 和等待 Btrfs flush 的 kworker 也被连带阻塞。
这是对现场调用栈的解释,不等于已经找到了唯一对应的上游 bug。能确定的是:
- 阻塞发生在 Btrfs zoned 的 block-group/chunk 清理路径;
- 线程已在内核态进入
D状态; - 普通
kill -9不能结束内核线程; - 继续执行依赖事务提交的操作可能一起卡住。
第一时间做了哪些检查
先停止 rsnapshot、rsync、Docker 容器或其他写盘任务,然后检查不可中断进程:
|
|
保存本次启动的内核日志:
|
|
保存相关线程堆栈:
|
|
查询文件系统时加超时,避免诊断命令本身也永久阻塞:
|
|
随后尝试一次正常卸载:
|
|
结果是:
|
|
target is busy 与内核锁死不是一回事
target is busy 通常表示仍有以下引用:
- 进程的当前工作目录位于
/mnt/disk1; - 文件仍被打开;
- 备份或复制程序仍在运行;
/mnt/disk1下存在子挂载点;- 用户进程已经因文件系统锁死进入
D状态。
先保证所有终端退出挂载目录:
|
|
检查子挂载和占用者:
|
|
普通用户进程可以先正常终止,再考虑强制终止:
|
|
如果是 rsnapshot 或 rsync:
|
|
但不要试图杀死:
|
|
它们是内核线程。现场检查显示阻塞者主要就是 Btrfs 内核线程,继续 kill、umount 或全局 sync 已经无法解决根因。
为什么没有使用 lazy unmount 或强制卸载
当时没有执行:
|
|
umount -l 只是先从目录树摘除挂载点,真正的文件系统引用仍要等不再繁忙后才能清理。Btrfs transaction 已经锁死时,它可能只隐藏挂载点,并不代表数据已提交或文件系统已完成卸载。
umount -f 也不是解除本地 Btrfs 内核死锁的可靠手段。它更常用于部分网络或用户态文件系统。
同样没有运行:
|
|
这些操作可能等待已经锁住的事务,或者再次进入出问题的 block-group 回收路径。
重启前增加 noauto
为了避免机器重启后立刻用同一个内核自动读写这块盘,先检查 /etc/fstab:
|
|
备份配置:
|
|
例如原来是:
|
|
临时改成:
|
|
noauto 的意思只是开机不自动挂载,不是禁用文件系统。系统启动后仍可手动只读或读写挂载。
从正常重启到最后手段
现场先执行了正常重启:
|
|
这一次正常完成,重启后文件系统也能重新挂载。若正常重启因 Btrfs 卡住,才按风险逐级增加强制程度:
|
|
最后手段才是:
|
|
两个 --force 不再正常停止服务和卸载文件系统,效果接近硬复位,可能丢失尚未提交的数据。它只能用于系统已经无法正常收尾的情况,不能当作普通重启命令。
重启后的检查
先确认实际启动的内核和磁盘:
|
|
确认没有自动挂载:
|
|
第一次先只读挂载:
|
|
实际环境应优先使用 UUID 或 /dev/disk/by-id/,不要默认设备永远是 /dev/sda。
检查内核日志、Btrfs 设备计数和空间状态:
|
|
现场 btrfs device stats 全部为 0:
|
|
这说明 Btrfs 没有记录到设备读写、flush、校验或 generation 错误,是好现象。但它不能证明内核锁死已经修复,也不能排除尚未读取的数据问题。
scrub 做什么,不能做什么
系统稳定后安排了一次 scrub:
|
|
如果希望前台等待结果:
|
|
取消任务:
|
|
Scrub 会读取已存储的数据和元数据,并核对 Btrfs 保存的校验和。它用于发现读错误、数据或元数据校验错误,不是:
|
|
如果结果为:
|
|
它只能说明本次读取和校验没有发现异常,不能证明 btrfs-cleaner 的 zoned 清理路径以后不会再次锁死。
保存结果:
|
|
Scrub 期间不要同时运行 rsync、rsnapshot、balance 或大量删除。对这类备份盘,可以按实际使用强度每一至三个月执行一次。
为什么没有为了“回收空间”运行 balance
现场曾出现:
|
|
这个数字只表示已经分配的 Data block group 使用率,并不代表整块盘只剩 1.33%。当时仍有数 TiB Device unallocated,并不缺少可继续分配的空间。
因此没有执行:
|
|
Balance 会重新定位 block group,而第一次故障正位于:
|
|
在问题内核上主动 balance,可能再次进入相近的 block-group 删除和 zone 回收路径。
如何处理数 TB 删除与写入
最有效的工作流调整是:有足够空间时,先复制并校验新数据,再删除旧数据。
|
|
复制:
|
|
比较容量:
|
|
用内容校验方式演练,不修改目标:
|
|
必须先删除时,不要一次删完数 TB 后马上写入。可以按日期、子目录或容量分为 200~500GiB 一批:
|
|
btrfs filesystem sync 会提交文件系统事务并唤醒相关清理工作,但命令返回不表示所有 block group 和 zone reclaim 已全部完成。
如果删除的是 Btrfs 子卷或快照,还可以执行:
|
|
它等待已提交删除请求的子卷完成清理,不适用于普通 rm -rf 删除。
每批后检查:
|
|
满足以下条件后再继续:
btrfs filesystem sync正常返回;- 没有 Btrfs 线程处于
D状态; - 内核日志没有新的 blocked、hung、abort 或 Btrfs error;
- 磁盘活动已经明显回落;
Device zone unusable没有快速增长。
Device zone unusable 不需要降到 0。它可能在未达到下一轮回收条件时停留在几十 GiB,这本身不等于文件系统仍然卡住。
写入也可以分批,并限制优先级和带宽:
|
|
--bwlimit=100M 只是保守起点。限速不能修复内核 bug,但可以减少 cleaner、事务提交、writeback 和 zone activation 同时处于高负载的概率。不要并发运行多个大型 rsync。
监控 zoned 回收指标
先获得文件系统 UUID:
|
|
查看 data、metadata 和 system 的空间及回收指标:
|
|
其中:
bytes_pinned:通常要等当前事务提交后才能释放的空间;bytes_zone_unusable:已失效但尚不能直接复用的 zoned 空间;reclaim_count:累计回收尝试次数;reclaim_bytes:累计回收字节数;reclaim_errors:累计回收错误数。
还可以查看事务提交统计:
|
|
如果 max_commit_ms 持续达到几十秒或数百秒,或者日志再次出现:
|
|
应停止备份任务并保存日志,不要继续灌入数据等待它自行恢复。
禁止备份任务重叠
所有 rsnapshot 周期使用同一个锁:
|
|
weekly 和 monthly 也使用:
|
|
这样可以避免 daily、weekly、monthly 与大量 rsync 同时进行。Scrub、大量删除和大型复制也应该安排在不同时间段。
第二次故障:事务中止并被强制只读
第一次故障重启后,文件系统可以正常挂载,device stats 也没有错误。但之后再次写入大量数据时,出现了更明确的事务失败:
|
|
运行环境仍是:
|
|
这次不是“暂时卡住”,而是事务已经 aborted,内核为保护文件系统主动切换成只读。-11 对应 EAGAIN,但只凭 errno 不能确定触发它的具体代码。
当时的设备统计仍全部为 0:
|
|
空间状态也不是整盘写满:
|
|
这组证据更支持 zoned Btrfs 内部写入、chunk 分配或事务逻辑异常,而不是普通介质 I/O 错误或整盘 ENOSPC。但在没有完整首条错误和对应源码分析前,不应把原因断言为某一个补丁。
forced readonly 后的正确处理
不要尝试:
|
|
先停止写入任务:
|
|
保存故障前后完整日志。真正导致事务中止的第一条错误可能出现在 forced readonly 之前几十行:
|
|
确认挂载选项:
|
|
其中应出现 ro。随后正常卸载并重启:
|
|
若提示 busy:
|
|
停止列出的普通用户进程后再卸载。
检查未完成的复制
事务中止前已经写入的部分数据不能直接当作完整备份。可能出现:
- 文件尚未创建;
- 文件长度不完整;
- 数据写入了,但目录项或时间戳没有提交;
- 最后一个事务内的改动被回滚。
因此必须保留源盘。切换到稳定环境并重新读写挂载后,用原 rsync 补齐:
|
|
再用校验模式验证:
|
|
没有输出才表示 rsync 没有发现需要更新的内容。
内核方案:先区分测试与长期使用
同一台机器、同一个 7.0.0-28-generic、同一块 HC620 已连续出现:
btrfs-cleaner与 transaction 线程锁死;error -11、transaction aborted、forced readonly。
因此不再继续用该内核对这块盘做数 TB 读写。讨论过三条路线:
| 路线 | 用途 | 局限 |
|---|---|---|
| Ubuntu 26.04 + 7.1 Mainline | 判断新上游内核是否缓解故障 | 非 Ubuntu 生产支持内核,具体修复未确认 |
| Ubuntu 24.04 + 6.8 GA | 判断 7.0 是否引入回归,长期支持较好 | zoned Btrfs 代码较旧 |
| Ubuntu 24.04 + 6.17 | 短期对比测试 zone reclaim 行为 | 支持周期接近结束,不适合长期无人值守 |
这里最重要的不是追求版本号最大,而是:
- 保留一个可以启动的旧内核;
- 第一次只做一次性 GRUB 启动;
- 先只读挂载;
- 从 100~200GiB 逐步测试;
- 不再直接复现“删除 3TB 后立即写入 3TB”。
在 Ubuntu 24.04 确认 6.8 GA
不要在现有 Ubuntu 26.04 中混装 Ubuntu 24.04 软件包。更安全的办法是在另一块 SSD 安装 Ubuntu Server 24.04,再挂载 HC620 数据盘测试。
安装并保留 6.8 GA 元包:
|
|
确认系统和内核:
|
|
关键是 uname -r 以 6.8. 开头,不要把某个补丁版本永久写死。
查询当前仓库,而不是照抄本文的历史版本:
|
|
如果测试目标是 GA 6.8,不要让 linux-generic-hwe-24.04 把系统带回另一条 HWE 内核线。
在 Ubuntu 24.04 短期测试 6.17
先查询当前仓库是否仍提供和维护该包:
|
|
确认包仍可用后,明确安装:
|
|
确认文件:
|
|
第一次只启动一次。下面的完整版本必须替换成机器实际安装的版本:
|
|
重启后检查:
|
|
不应在 Ubuntu 26.04 中添加 24.04 Noble 仓库来强装这套内核,否则可能引入 firmware、DKMS、initramfs 和后续 apt 维护问题。
测试 7.1 Mainline 时的边界
Mainline 包适合验证“上游新内核是否改变故障表现”,不等于 Ubuntu 官方生产内核。安装前必须检查:
|
|
如果 Secure Boot 已启用、存在关键 DKMS 模块或服务器没有带外控制台,测试风险会明显增加。具体版本、文件名和校验值应从当前 Ubuntu Mainline Kernel Archive 重新获取,不应复制过期下载地址。
保留当前可启动内核,通过 GRUB 一次性启动新内核。启动后检查:
|
|
新内核的首次挂载与测试
无论测试 6.8、6.17 还是更新内核,先保留 /etc/fstab 中的 noauto,启动后只读挂载:
|
|
没有新错误后:
|
|
逐步测试:
|
|
不能因为小批量通过,就立即用数 TB 压力证明“已经修复”。至少需要多轮、大量数据和较长运行时间才能提高信心。
是否重新格式化 Btrfs
重新格式化可以得到干净文件系统,但不能单独修复内核 zoned 路径问题。两次异常都发生在运行时的 block-group、zone 和事务路径,且 device stats 没有介质错误。
只有遇到以下情况时,重建文件系统才更有意义:
btrfs check --readonly发现明确结构错误;- scrub 持续出现无法修复的校验错误;
- 无法正常挂载或持续发生 generation 错误;
- 已决定调整数据/元数据 profile;
- 准备更换文件系统。
即使要重建,也应先完成另一份可验证的备份,而不是把格式化当作试错命令。
Btrfs 与 F2FS 怎么选
F2FS 可以绕开这次出现的 Btrfs 特有调用路径:
|
|
但它不是“没有问题”的方案。F2FS 同样存在 GC、segment 搬移、zone reset、掉电恢复和文件系统修复方面的风险。大量删除后立即大量写入,仍可能出现较长的前台 GC 延迟。
两者的主要取舍是:
| 项目 | Btrfs zoned | F2FS |
|---|---|---|
| 文件数据校验和 | 有 | 通常没有同等级端到端数据校验 |
| scrub | 有 | 无 Btrfs 式 scrub |
| 快照 | 有 | 无 Btrfs 式快照 |
| 本次故障路径 | 已实际触发两次 | 可避开 Btrfs 特有路径 |
| 后台空间回收 | block group/zone reclaim | segment GC/zone reset |
如果 HC620 只是多份备份中的一份,F2FS 加外部校验可以接受。如果它是唯一副本,无论使用单盘 Btrfs 还是 F2FS 都不够安全。
使用 F2FS 时可以把 SHA256 清单保存到另一块盘:
|
|
以后验证:
|
|
硬盘自身 ECC、坏扇区重映射和 SATA CRC 仍然会工作,但它们不能覆盖应用、内存、文件系统、控制器和磁盘之间的完整端到端链路。Btrfs 校验和与硬盘 ECC 是互补关系,不是重复功能。
最后的处理原则
这次排障形成了以下实际规则:
- 不再用
7.0.0-28-generic对这块 HC620 做大规模读写; - 保留源数据和至少一份独立副本;
- 先复制、校验,再分批删除;
- 数 TB 操作拆成 200~500GiB 批次;
- rsync、rsnapshot、scrub、大量删除和 balance 不重叠;
- 出现
D状态或 hung-task 日志后立即停止写入; - forced readonly 后不强行 remount,rw;
- 不使用
btrfs check --repair进行盲目尝试; - 测试内核必须保留回退项,并从只读挂载开始;
- 长期可靠性首先来自多副本,其次才是文件系统选择。
整个故障链可以概括为:
|
|
这组结果足以说明当前“HC620 + Btrfs zoned + 7.0.0-28 + 大删大写”组合不适合继续承担无人值守的大规模任务,但还不足以证明所有 HC620、所有 Btrfs zoned 或所有 7.0 内核都有同一问题。