跳到主要内容

Btrfs scrub 实用指南:启动、限速、查看进度和速度

· 阅读需 11 分钟

Btrfs 的 scrub 是一个很适合定期运行的在线校验工具。它会遍历 Btrfs 文件系统中的数据和元数据,检查校验和错误、读错误、部分元数据头错误和超级块错误。

如果文件系统使用了有冗余副本的 profile,比如 RAID1、RAID10 或 DUP,读写挂载状态下的 scrub 在发现坏副本时,可以尝试从好的副本修复。它不是 fsck,也不能替代 btrfs check;它更像是一次在线的数据完整性巡检。

这篇文章记录一套实用流程:怎么找可 scrub 的挂载点,怎么启动,怎么降低 I/O 影响,怎么查看状态,以及在旧版工具不显示 Rate / ETA / 百分比时,怎么自己算速度和进度。

scrub 的对象是什么?

btrfs scrub 不是按目录工作的。

你传给它的路径可以是一个 Btrfs 文件系统里的路径,但 scrub 的对象是这个路径所属的 Btrfs 文件系统,或者指定的设备。

例如:

sudo btrfs scrub start /volume3

如果 /volume3 是一个 Btrfs 文件系统的挂载点,那么它会 scrub 这个文件系统。它不会只校验 /volume3 下某个目录。

如果同一个 Btrfs 文件系统有多个子卷分别挂载,比如 //home/var/lib/docker,对其中任意一个挂载点执行 scrub,本质上仍然是在 scrub 同一个 Btrfs 文件系统。

如何找可以 scrub 的挂载点

findmnt 的系统上,最简单:

findmnt -t btrfs

只看挂载点路径:

findmnt -rn -t btrfs -o TARGET

如果系统没有 findmnt,可以直接读 /proc/mounts

awk '$3=="btrfs"{print $2}' /proc/mounts

看更完整的信息:

awk '$3=="btrfs"{print $1, $2, $4}' /proc/mounts

也可以用传统 mount 输出粗筛:

mount | grep ' type btrfs '

拿到挂载点后,通常对每个实际 Btrfs 文件系统选一个代表挂载点运行 scrub 即可。

启动 scrub

后台启动:

sudo btrfs scrub start /volume3

前台启动,直到完成才返回:

sudo btrfs scrub start -B /volume3

-B 的意思是不要后台运行。它适合你想在当前终端等待结果的场景。

如果想在前台运行,并在结束时显示每个设备的统计信息:

sudo btrfs scrub start -Bd /volume3

注意这里的 -dstart 子命令的参数,并且只在 -B 时有效。它表示完成后按设备打印统计。

后台启动后,常用状态命令是:

sudo btrfs scrub status /volume3

按设备查看状态:

sudo btrfs scrub status -d /volume3

status -dstart -d 不一样。status -d 是查看状态时按设备显示,可以单独使用。

暂停、取消和恢复

取消正在运行的 scrub:

sudo btrfs scrub cancel /volume3

恢复上次取消或中断的 scrub:

sudo btrfs scrub resume /volume3

查看状态:

sudo btrfs scrub status /volume3

Btrfs 会把 scrub 状态记录在 /var/lib/btrfs/ 下,文件名通常包含文件系统 UUID。官方文档说明状态文件大约每 5 秒更新一次。

降低 I/O 影响:ionice 优先级

旧版 btrfs scrub start --help 常见参数是:

usage: btrfs scrub start [-BdqrRf] [-c ioprio_class -n ioprio_classdata] <path>|<device>

其中:

-c set ioprio class
-n set ioprio classdata

这相当于内置了类似 ionice 的 I/O 优先级控制。

最保守的后台运行方式:

sudo btrfs scrub start -c 3 /volume3

含义:

-c 3 = idle I/O class

idle 类不需要 -n。它表示只有系统 I/O 比较空闲时才积极执行。

如果想用普通类里的最低优先级:

sudo btrfs scrub start -c 2 -n 7 /volume3

含义:

-c 2 = best-effort
-n 7 = best-effort 里的最低优先级

也可以用外部 ionice 包一层:

sudo ionice -c3 btrfs scrub start /volume3

或者:

sudo ionice -c2 -n7 btrfs scrub start /volume3

需要注意:ionice-c/-n 控制的是 I/O 调度优先级,不是固定 MB/s 限速。并且在现代 Linux 常见 I/O scheduler 下,它不一定都有效。Btrfs 官方文档也提醒,相关优先级机制依赖 I/O scheduler,常见的 mq-deadline 并不一定按预期支持这种优先级控制。

真正按速度限速

较新的 btrfs-progs 支持更直接的 scrub 限速方式,但这里的“新”要看具体版本:

  • btrfs scrub limitbtrfs-progs 6.6.3 引入,用于查看或设置每设备 scrub 限速。
  • btrfs scrub start --limitbtrfs-progs 6.13 引入,用于本次 scrub 运行期间临时设置吞吐上限。

所以 btrfs-progs v6.2 没有 scrub limitscrub start --limit 是正常的,即使发行版或 NAS 系统本身已经是最新版本。

如果 btrfs scrub start --help 里有 --limit,可以这样:

sudo btrfs scrub start --limit 100m /volume3

如果有 btrfs scrub limit 子命令,可以查看或设置每个设备的限制:

sudo btrfs scrub limit /volume3
sudo btrfs scrub limit --all --limit 100m /volume3

Linux 5.14 之后,还可以通过 Btrfs 的 sysfs 文件设置每设备 scrub 速度上限:

echo 100m | sudo tee /sys/fs/btrfs/<FSID>/devinfo/<DEVID>/scrub_speed_max

这个设置不是持久化配置,卸载文件系统后会消失。

如果用户态工具还是 btrfs-progs 6.2 这类旧版本,但内核足够新,可以先检查 sysfs 入口是否存在:

ls /sys/fs/btrfs/*/devinfo/*/scrub_speed_max

如果存在,可以绕过 btrfs scrub limit 命令,直接写入该文件。写入前需要确认对应的 FSID 和 DEVID 属于目标文件系统。

另一种通用做法是使用 cgroup v2 / systemd 的 I/O 限制,例如官方文档给出的形式类似:

sudo systemd-run -p "IOReadBandwidthMax=/dev/sdx 10M" btrfs scrub start -B /

这种方式比 -c 3 更接近真正限速,但配置复杂度更高,而且需要确认实际底层设备路径。

查看状态:新版本的理想输出

新版本 btrfs scrub status 可能会直接显示这些字段:

Status: running
Duration: 0:00:05
Time left: 0:00:05
ETA: Wed Apr 10 12:35:01 2023
Total to scrub: 28.32GiB
Bytes scrubbed: 13.76GiB (48.59%)
Rate: 2.75GiB/s
Error summary: no errors found

这种情况下很简单:

  • 速度看 Rate
  • 进度看 Bytes scrubbed 和百分比
  • 剩余时间看 Time leftETA
  • 错误看 Error summary

实时刷新:

watch -n 5 'sudo btrfs scrub status -d /volume3'

如果没有 watch

while true; do
clear
date
sudo btrfs scrub status -d /volume3
sleep 5
done

旧版本只显示已 scrub 字节数怎么办?

有些 NAS 或旧版 btrfs-progs 的输出比较简略,例如:

scrub status for d1a06ecc-e68a-46c5-9741-7bd9ab592c4b
scrub device /dev/mapper/cachedev_0 (id 1) status
scrub started at Sun Jun 28 09:15:15 2026, running for 00:00:56
total bytes scrubbed: 22.22GiB with 0 errors

这种输出没有 Rate,也没有总进度。平均速度可以手算:

平均速度 = total bytes scrubbed / 已运行秒数

上面的例子:

22.22GiB / 56s ≈ 406MiB/s

但这只是从启动到现在的平均速度,不是瞬时速度。

如果支持 -R,可以查看原始状态:

sudo btrfs scrub status -R /volume3

可能会得到类似:

data_extents_scrubbed: 2809542
tree_extents_scrubbed: 55582
data_bytes_scrubbed: 184036638720
tree_bytes_scrubbed: 910655488
read_errors: 0
csum_errors: 0
verify_errors: 0
uncorrectable_errors: 0
corrected_errors: 0
last_physical: 186868826112

这里可以用:

已 scrub 字节 = data_bytes_scrubbed + tree_bytes_scrubbed
实时速度 = 两次已 scrub 字节差值 / 间隔秒数

last_physical 表示当前扫到的物理位置。它可以用来粗略估算进度,但不是一个完美的百分比分母。

一个简易 scrubtop 脚本

下面这个脚本每隔 5 秒调用一次 btrfs scrub status -R,根据两次采样的字节差计算实时速度。

#!/bin/sh
# usage: scrubtop /volume3

path="${1:-/volume3}"
interval="${INTERVAL:-5}"

prev_bytes=""
prev_time=""

while true; do
out="$(sudo btrfs scrub status -R "$path" 2>/dev/null)"
now="$(date +%s)"

data="$(printf '%s\n' "$out" | awk -F': ' '/data_bytes_scrubbed/ {print $2}')"
tree="$(printf '%s\n' "$out" | awk -F': ' '/tree_bytes_scrubbed/ {print $2}')"
errors="$(printf '%s\n' "$out" | awk -F': ' '/uncorrectable_errors/ {print $2}')"
read_errors="$(printf '%s\n' "$out" | awk -F': ' '/read_errors/ {print $2}')"
csum_errors="$(printf '%s\n' "$out" | awk -F': ' '/csum_errors/ {print $2}')"
last_physical="$(printf '%s\n' "$out" | awk -F': ' '/last_physical/ {print $2}')"

bytes=$((data + tree))

clear
date
printf 'Path: %s\n' "$path"
printf 'Scrubbed: %.2f GiB\n' "$(awk "BEGIN {print $bytes/1024/1024/1024}")"
printf 'Last physical: %.2f GiB\n' "$(awk "BEGIN {print $last_physical/1024/1024/1024}")"

if [ -n "$prev_bytes" ]; then
dt=$((now - prev_time))
db=$((bytes - prev_bytes))
if [ "$dt" -gt 0 ]; then
printf 'Rate: %.2f MiB/s\n' "$(awk "BEGIN {print $db/$dt/1024/1024}")"
fi
else
printf 'Rate: collecting baseline...\n'
fi

printf 'Errors: read=%s csum=%s uncorrectable=%s\n' "$read_errors" "$csum_errors" "$errors"
printf '\nRaw status:\n%s\n' "$out"

prev_bytes="$bytes"
prev_time="$now"
sleep "$interval"
done

保存为 scrubtop 后运行:

chmod +x scrubtop
./scrubtop /volume3

如果想改刷新间隔:

INTERVAL=10 ./scrubtop /volume3

进度百分比怎么估算?

精确进度依赖工具是否能给出 Total to scrub。如果 btrfs scrub status 没有这个字段,只有 -R 的 raw 输出,那么只能估算。

常见估算口径有两个:

1. 用 last_physical / 设备大小

查设备大小:

sudo blockdev --getsize64 /dev/mapper/cachedev_0

估算:

进度 ≈ last_physical / 设备大小

这个口径适合单设备、物理布局相对直观的场景,但不等于 Btrfs 逻辑层面的真实待 scrub 数据量。

2. 用 Btrfs 已分配空间做分母

查看 Btrfs 空间使用:

sudo btrfs filesystem usage -b /volume3

估算:

进度 ≈ 已 scrub 字节 / 已分配空间

这个口径更贴近 scrub 扫描已分配数据/元数据块的事实,但在 RAID、DUP、压缩、快照、多设备场景下仍然只是估算。

结论是:如果工具不显示 Total to scrub,就不要把估算百分比当成精确进度。速度和错误计数更可靠。

错误字段怎么看?

常见字段:

read_errors
csum_errors
verify_errors
super_errors
uncorrectable_errors
corrected_errors
unverified_errors

重点关注:

  • corrected_errors:发现坏块,并且已经从好副本修复。
  • uncorrectable_errors:发现错误,但没有可用好副本修复,需要进一步处理。
  • csum_errors:数据校验和错误。
  • read_errors:底层读失败,可能涉及磁盘、线缆、控制器或阵列层问题。
  • verify_errors:元数据块头校验类错误。

如果出现 uncorrectable_errors,不要只重跑 scrub。应该先保存状态输出,再检查内核日志、设备 SMART、Btrfs device stats 和备份状态。

dmesg | grep -iE 'btrfs|i/o error|checksum|read error'
sudo btrfs device stats /volume3

推荐实践

日常后台低影响运行:

sudo btrfs scrub start -c 3 /volume3

查看状态:

sudo btrfs scrub status -d /volume3

旧版工具查看 raw 状态:

sudo btrfs scrub status -R /volume3

新版工具如果支持真正限速:

sudo btrfs scrub start --limit 100m /volume3

或:

sudo btrfs scrub limit --all --limit 100m /volume3
sudo btrfs scrub start /volume3

如果要在维护窗口里跑:

sudo btrfs scrub start -c 3 /volume3

到时间后:

sudo btrfs scrub cancel /volume3

下次继续:

sudo btrfs scrub resume /volume3

小结

btrfs scrub 的关键点可以压缩成几句话:

  • scrub 面向 Btrfs 文件系统或设备,不面向目录。
  • 默认后台运行;-B 才是前台等待完成。
  • -c 3 / ionice -c3 是降低 I/O 优先级,不是真正 MB/s 限速。
  • btrfs scrub limit 需要 btrfs-progs 6.6.3 及以上;scrub start --limit 需要 btrfs-progs 6.13 及以上。
  • Linux 5.14 及以上内核可能支持通过 scrub_speed_max 做每设备限速,即使旧版 btrfs-progs 没有对应子命令。
  • 新版 status 可能直接显示 RateETA 和百分比。
  • 旧版可用 status -Rdata_bytes_scrubbed + tree_bytes_scrubbed 计算实时速度。
  • 没有 Total to scrub 时,进度百分比只能估算,不应当当作精确值。

参考资料

  • Btrfs scrub documentation
  • Btrfs progs changelog
  • 本文示例来源于 2026-06-28 的一次实际排查记录:旧版 btrfs-progs 只显示 total bytes scrubbed,但 status -R 能提供 data_bytes_scrubbedtree_bytes_scrubbedlast_physical