跳到主要内容

4 篇博文 含有标签「NAS」

NAS相关内容,包括Linux系统配置、网络配置、存储等

查看所有标签

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

从零认识 rootfs:它到底是文件系统、分区,还是 Linux 里的角色?

· 阅读需 17 分钟

学习 Linux、嵌入式系统、Docker 或容器时,经常会遇到一个词:rootfs

它看起来像某种文件系统格式,但又不像 ext4xfsbtrfs 那样能被 mkfs.ext4 之类的命令创建。它有时又和分区、镜像、容器、initramfs、overlayfs 一起出现,让人很容易混淆:

rootfs 到底是什么?是文件系统?是分区格式?还是磁盘上的某个区域?

最直接的答案是:

rootfs 是 root filesystem 的缩写,指 Linux 系统中作为根目录 / 使用的那个文件系统。它是一个文件系统/挂载命名空间层面的角色,不是磁盘角色,也不是分区格式。

这句话看起来简单,但要真正理解它,需要先从 Linux 的目录树讲起。

Linux 为什么需要 rootfs?

在 Linux/Unix 系统里,所有路径都挂在同一棵目录树下面。这棵树的根是 /

比如:

/
├── bin
├── boot
├── dev
├── etc
├── home
├── proc
├── sys
├── usr
└── var

无论底层有多少块磁盘、多少个分区、多少种文件系统,用户和进程看到的都是这棵统一的路径树。

这和 Windows 里常见的 C:\D:\ 多盘符模型不同。Linux 的模型是:

先有一棵以 / 为根的目录树
再把不同文件系统挂载到这棵树的不同位置

例如:

/ 来自 /dev/nvme0n1p2
/boot 来自 /dev/nvme0n1p1
/home 来自 /dev/nvme0n1p3
/proc 来自 proc 虚拟文件系统
/sys 来自 sysfs 虚拟文件系统
/run 来自 tmpfs

其中最关键的是 /。因为其他挂载点都要挂到这棵树的某个位置,而这棵树本身必须先存在。

所以 Linux 启动时必须先解决一个根本问题:

系统启动后,哪个文件系统来承担 / 这个根?

承担这个角色的文件系统,就叫 rootfs

rootfs 是一种角色,不是一种格式

理解 rootfs 最容易出错的地方,是把它和磁盘、分区、文件系统格式混在一起。

可以按层次拆开:

磁盘设备: /dev/nvme0n1、/dev/sda、/dev/mmcblk0
分区: /dev/nvme0n1p2、/dev/sda1
文件系统格式: ext4、xfs、btrfs、squashfs、erofs
挂载点: /、/boot、/home、/var
系统角色: rootfs,也就是挂载到 / 的那个文件系统

举一个普通 Linux 主机的例子:

/dev/nvme0n1p2 是一个分区
这个分区里创建了 ext4 文件系统
这个 ext4 文件系统被挂载到 /
于是它在这个系统里承担 rootfs 角色

所以:

  • rootfs 不是磁盘。
  • rootfs 不是分区。
  • rootfs 不是 GPT、MBR 这样的分区表格式。
  • rootfs 也不是 ext4xfsbtrfs 这样的文件系统格式。
  • rootfs 是“哪个文件系统正在作为 / 使用”的角色名。

同样是 rootfs,底层可以完全不同:

ext4 挂载到 / -> ext4 文件系统承担 rootfs 角色
btrfs 挂载到 / -> btrfs 文件系统承担 rootfs 角色
squashfs 挂载到 / -> squashfs 镜像承担 rootfs 角色
tmpfs 挂载到 / -> 内存文件系统承担 rootfs 角色
NFS 挂载到 / -> 网络文件系统承担 rootfs 角色
overlayfs 挂载到 / -> overlayfs 叠加结果承担 rootfs 角色

这就是为什么 rootfs 这个词在嵌入式、Live CD、容器、Docker、initramfs 里都能出现。它不是描述底层材料,而是描述这个文件系统在当前系统中的职责。

为什么要单独给它一个名字?

因为 / 不是普通挂载点。

普通挂载点可以晚一点出现。比如 /home 不挂载,系统可能还能进入维护模式;/mnt/data 不挂载,最多是某些数据不可用。

/ 不存在,系统就没有路径命名空间的起点。内核后续要启动第一个用户态进程,要找 /sbin/init/init 或 systemd;用户态工具要访问 /etc/dev/usr;这些都依赖根目录树已经存在。

因此 rootfs 是系统启动链路中的核心角色:

内核启动
-> 准备早期根文件系统
-> 找到或构造真正的 rootfs
-> 切换到这个 rootfs
-> 启动 init / systemd
-> 系统进入正常运行

给它一个单独名字,可以跨越很多不同场景讨论同一件事:

  • 嵌入式里说“烧录 rootfs”,通常指烧录根文件系统镜像或目录树。
  • Docker 里说“容器 rootfs”,指容器进程看到的 /
  • initramfs 里说“早期 rootfs”,指内核启动初期解包出来的临时根。
  • LXC 里说“rootfs tarball”,指能作为容器根目录的完整目录树。

如果不用 rootfs 这个词,每次都要说“那个会被挂载为 /、让系统或容器进程作为根目录看到的文件系统”,太长,也不利于讨论启动流程和容器隔离。

rootfs 里面通常有什么?

一个能作为 Linux 根文件系统使用的目录树,通常至少要包含用户态运行所需的基本目录和文件。

常见结构类似:

/
├── bin
├── dev
├── etc
├── lib
├── lib64
├── proc
├── root
├── run
├── sbin
├── sys
├── tmp
├── usr
└── var

不同发行版、不同用途会有差异。桌面/服务器发行版的 rootfs 通常很大,包含包管理器、系统服务、shell、库文件、配置文件等。嵌入式系统的 rootfs 可能非常小,只包含 BusyBox 和少量配置。容器镜像的 rootfs 则可能只包含运行某个应用所需的最小依赖。

从这个角度看,rootfs 也经常被用来指“一个可作为根目录使用的目录树内容”。

例如:

rootfs.tar.gz 一个打包好的根文件系统目录树
rootfs.ext4 一个 ext4 格式的根文件系统镜像
rootfs.squashfs 一个 squashfs 格式的只读根文件系统镜像

这里的 rootfs 不是说它们的格式都叫 rootfs,而是说它们的用途是“作为根文件系统”。

Linux 启动时 rootfs 怎么来?

一个简化的 Linux 启动过程可以这样理解:

1. Bootloader 加载内核和启动参数
2. 内核初始化 CPU、内存、驱动等基础设施
3. 内核准备早期根文件系统
4. 内核根据 root= 参数或 initramfs 逻辑找到真正的根
5. 挂载真正的 rootfs 到 /
6. 启动 /sbin/init、/init 或 systemd

启动参数里常见的 root= 就是在告诉内核真正的根文件系统来源。

比如:

root=UUID=xxxx-xxxx
root=/dev/nvme0n1p2
root=/dev/mmcblk0p2
root=/dev/nfs

这不是指定“rootfs 格式”,而是指定“哪个设备或来源要被挂载成 /”。

很多现代发行版还会使用 initramfs。它是一个早期用户态环境,通常被内核解包到内存中的早期根文件系统里。它的任务包括加载磁盘驱动、解密磁盘、组装 RAID/LVM、寻找真正的根文件系统,然后切换过去。

所以启动早期可能存在两个阶段:

早期 rootfs:initramfs 提供的临时根
真正 rootfs:磁盘、网络或其他来源上的最终根

这也是为什么在启动相关文档里,rootfs 这个词有时会显得更加“内核化”:Linux 内核本身确实有早期 rootfs 的概念。

临时 rootfs 如何切换到真正 rootfs?

从启动过程看,临时 rootfs 切换到真正 rootfs 的过程通常发生在 initramfs 阶段。

这个过程常被描述为:

从 initramfs 切换到 real rootfs

在用户态工具里,常见命令名是 switch_root。底层可能涉及 mount、移动挂载点、chrootpivot_root 等机制,但现代 initramfs 里经常由 switch_root 或发行版自己的 initramfs 脚本封装。

一个更完整的启动链路可以这样看:

Bootloader
-> 加载 Linux kernel
-> 加载 initramfs
-> kernel 启动
-> kernel 解包 initramfs 到早期 rootfs
-> 执行 initramfs 里的 /init
-> /init 找到真正 rootfs
-> 挂载真正 rootfs
-> 切换 / 到真正 rootfs
-> 执行真正 rootfs 里的 /sbin/init 或 systemd

关键点是:切换 rootfs 的工作通常不是内核全自动完成的,而是 initramfs 里的早期用户态脚本完成的。

启动早期,当前 / 还是 initramfs 提供的临时 rootfs:

当前 /
= initramfs 提供的临时 rootfs

initramfs 里的 /init 通常会做这些事:

# 1. 挂载早期需要的虚拟文件系统
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs devtmpfs /dev

# 2. 加载必要驱动、等待设备出现
# 例如磁盘、USB、NVMe、RAID、LVM、加密卷、网络等

# 3. 找到真正的 root 设备
# 可能来自 /proc/cmdline 里的 root=UUID=...
# 也可能来自脚本、网络、标签、LVM 名称等

# 4. 把真正 rootfs 挂到临时目录
mount /dev/nvme0n1p2 /newroot

# 5. 切换到真正 rootfs
exec switch_root /newroot /sbin/init

切换前,文件系统视图大概是:

/ initramfs 临时 rootfs
├── init 早期启动脚本
├── bin
├── dev
├── proc
├── sys
└── newroot 真正 rootfs 临时挂载点
├── sbin/init
├── etc
├── usr
└── var

执行:

exec switch_root /newroot /sbin/init

之后,文件系统视图变成:

/ 真正 rootfs
├── sbin/init 或 systemd
├── etc
├── usr
├── var
├── dev
├── proc
└── sys

原来的 initramfs 临时 rootfs 会被释放,或者至少从正常系统视图里消失。随后系统继续从真正 rootfs 里的 initsystemd 启动。

为什么不让内核直接挂载真正 rootfs?因为真正 rootfs 很多时候不是一开始就能直接访问的。

它可能位于:

  • 加密磁盘里,需要先解锁;
  • LVM 或 RAID 上,需要先组装卷;
  • NVMe、USB、SCSI 设备上,需要先加载驱动;
  • 网络 NFS 上,需要先配置网络;
  • btrfs 子卷、只读 squashfs、overlay 组合上,需要先执行额外挂载逻辑。

所以 initramfs 的角色是:

提供一个最小临时 Linux 环境,用来找到、准备、挂载真正的 rootfs,然后把系统控制权交给它。

这里还有一个容易误解的地方:临时 rootfs 并不是被“转换成”正式 rootfs。真实发生的是:

1. 临时 rootfs 先作为 /
2. 真正 rootfs 被挂载到 /newroot
3. 启动脚本把进程视角里的 / 切换到 /newroot
4. 新的 /sbin/init 接管系统启动
5. 临时 rootfs 退出历史舞台

也就是说,这是一次“根目录视角切换”,不是文件系统格式转换。

在 Linux 上怎么查看当前 rootfs?

最推荐的命令是:

findmnt /

它会显示当前 / 的来源、文件系统类型和挂载选项。

也可以只看关键字段:

findmnt -no SOURCE,FSTYPE,OPTIONS /

可能输出类似:

/dev/nvme0n1p2 ext4 rw,relatime

这说明当前作为 / 的文件系统来自 /dev/nvme0n1p2,格式是 ext4

在 Docker 容器里可能看到:

overlay overlay rw,relatime,lowerdir=...,upperdir=...,workdir=...

这说明容器里的 / 来自一个 overlayfs 叠加结果。

还可以看内核提供的挂载信息:

cat /proc/mounts | awk '$2 == "/" {print}'

或者:

awk '$5 == "/" {print}' /proc/self/mountinfo

查看启动参数:

cat /proc/cmdline

如果里面有 root=UUID=...root=/dev/...,那就是内核启动时被告知的根文件系统来源。

需要注意的是,正常系统启动完成后,findmnt /mount 里不一定会直接出现 rootfs 这个关键词。你更常看到的是具体来源和具体格式,比如:

/dev/nvme0n1p2 on / type ext4

或者:

overlay on / type overlay

能不能看到 rootfs 这个词,取决于当前环境和启动阶段。早期启动环境、initramfs、某些容器或特殊系统里可能能看到:

rootfs / rootfs rw 0 0

但看不到 rootfs 这个词,并不代表没有 rootfs。只要系统里存在 /,就一定有某个文件系统承担了 rootfs 角色。

rootfs 和 /boot、/home 有什么区别?

/boot/home/var 这些目录可以是 rootfs 内部的普通目录,也可以是额外挂载进来的文件系统。

比如一个简单系统可能只有一个分区:

/dev/nvme0n1p2 -> ext4 -> 挂载到 /

这时 /boot/home/var 都只是 rootfs 里的普通目录。

另一个系统可能拆成多个分区:

/dev/nvme0n1p2 -> ext4 -> /
/dev/nvme0n1p1 -> vfat -> /boot/efi
/dev/nvme0n1p3 -> xfs -> /home

这时 /home 是另一个文件系统挂载过来的。但从路径树上看,它仍然位于 / 下面。

所以 rootfs 的特殊性在于:它是整棵树的根。其他文件系统都要挂到这棵树的某个节点上。

Docker 容器里的 rootfs 是什么?

Docker 容器也有自己的 /

这并不是说 Docker 启动了一个新内核。普通 Docker 容器和宿主机共享同一个 Linux 内核,但它通过 namespace 让容器进程看到一套隔离后的文件系统视图。

Docker 镜像本质上可以理解为一组文件系统层:

只读镜像层 1
只读镜像层 2
只读镜像层 3
容器可写层

启动容器时,Docker 通常通过 overlay2 把这些层叠加成一个最终目录树。容器进程看到的 /,就是这个叠加结果。

简化表示:

image readonly layers
+
container writable layer
=
container rootfs

所以当你运行:

docker run -it ubuntu:24.04 /bin/bash

容器里的 / 来自 ubuntu:24.04 镜像层加上这个容器自己的可写层。

而:

docker run -it alpine:3.20 /bin/sh

容器里的 / 来自 Alpine 镜像层加上可写层。

这两个容器看到的 rootfs 内容不同,因为镜像不同。

Docker 启动时能不能更改容器 rootfs?

可以,但要分层理解。

普通 Docker 使用方式里,你通常不是在 docker run 时直接指定一个任意目录作为 /,而是通过镜像决定 rootfs 的来源:

docker run ubuntu:24.04
docker run alpine:3.20
docker run debian:bookworm

如果你想让容器使用自己的 rootfs,常见做法是构建或导入一个镜像。

例如,把一个已有 rootfs 目录打包成镜像:

tar -C /path/to/rootfs -cf rootfs.tar .
docker import rootfs.tar my-rootfs:latest
docker run -it my-rootfs:latest /bin/sh

这时容器内的 / 就来自你导入的那个目录树。

也可以用 Dockerfile 构建:

FROM debian:bookworm
RUN apt-get update && apt-get install -y curl
COPY ./app /app
CMD ["/app/start.sh"]

构建出的镜像会形成新的 rootfs 内容。

但普通 docker run 没有一个类似这样的参数:

docker run --rootfs /some/host/path

Docker 把底层 rootfs 管理封装在镜像、存储驱动和 OCI runtime 后面。

更底层的 OCI runtime,比如 runc,确实有 rootfs 路径的概念。Docker 最终会把容器配置转换成 OCI 规范,再交给底层 runtime 执行。底层 runtime 会设置 mount namespace,并让容器进程进入指定的 rootfs。

可以这样理解:

Docker CLI
-> 通过 image 选择 rootfs 内容
-> containerd 准备容器运行配置
-> OCI spec 描述 rootfs path
-> runc 设置 namespace、mount、pivot_root/chroot
-> 容器进程看到自己的 /

所以答案是:

Docker 容器启动时当然会设置容器内的 rootfs,但普通用户通常通过“换镜像、构建镜像、导入 rootfs tarball”来改变它,而不是直接在 docker run 中指定一个宿主机目录作为 /

bind mount 会改变 rootfs 吗?

不会。bind mount 或 volume 只是把某个目录挂进容器内的某个路径。

例如:

docker run -v /host/data:/data ubuntu

这会让容器内 /data 对应宿主机 /host/data,但容器的 / 仍然来自镜像和可写层组成的 rootfs。

你也可以挂载到更关键的位置:

docker run -v /host/etc:/etc ubuntu

这会覆盖容器内 /etc,但它仍然不是替换整个 rootfs,只是覆盖 rootfs 里面的一个子路径。这样做很容易破坏容器内部依赖,除非你非常清楚后果。

所以:

换镜像 / 导入 rootfs -> 改变容器 rootfs 的基础内容
bind mount / volume -> 在 rootfs 上额外挂载或覆盖局部路径

rootfs、chroot、pivot_root 的关系

rootfs 是“作为 / 的文件系统或目录树”。

chrootpivot_root 是让进程改变它看到的根目录的机制。

chroot 会把某个目录变成进程视角下的 /。例如:

sudo chroot /path/to/rootfs /bin/sh

这个 shell 会把 /path/to/rootfs 当作自己的 /

chroot 不是完整容器隔离。它主要改变路径解析根目录,不自动隔离进程、网络、用户、mount namespace 等。

容器 runtime 通常会结合 mount namespace、pivot_root、bind mount、cgroup、user namespace 等机制,构造出更完整的隔离环境。

可以粗略理解:

rootfs 目标目录树或文件系统
chroot 改变进程看到的 /
pivot_root 在 mount namespace 中切换根挂载
namespace 隔离进程看到的系统视图
cgroup 限制和统计资源

常见误解

误解一:rootfs 是一种文件系统格式

不是。

ext4xfsbtrfssquashfs 是文件系统格式。rootfs 是作为 / 的角色。

一个 ext4 文件系统可以是 rootfs,也可以挂到 /home,也可以挂到 /data。它是不是 rootfs,取决于它挂载在哪里、在当前系统里承担什么职责。

误解二:rootfs 就是根分区

不完全对。

在很多传统 Linux 安装中,rootfs 确实来自某个“根分区”,比如 /dev/sda2。但 rootfs 不一定来自分区。

它也可以来自:

  • 内存中的 initramfs
  • 网络上的 NFS
  • 只读 squashfs 镜像
  • overlayfs 叠加结果
  • 容器运行时准备的目录树
  • LVM、RAID、加密块设备之上的文件系统

所以“根分区”只是 rootfs 的一种常见承载方式,不等于 rootfs 本身。

误解三:容器有自己的 rootfs,所以容器有自己的内核

不是。

容器有自己的文件系统视图,也就是自己的 /,但普通容器和宿主机共享内核。

容器隔离主要依赖 Linux 内核提供的 namespace、cgroup、capability、seccomp、mount 等机制。rootfs 只是其中的文件系统视图部分。

误解四:看不到 rootfs 关键词,就说明系统没有 rootfs

不是。

启动完成后的系统往往显示的是具体来源和具体类型:

/dev/nvme0n1p2 on / type ext4

这行的意思就是:/dev/nvme0n1p2 上的 ext4 文件系统正在承担 rootfs 角色。

rootfs 是角色名,不一定会原样出现在命令输出里。

一张总览图

可以把这些概念放到同一张图里:

物理磁盘 / 虚拟磁盘 / 镜像文件 / 网络存储 / 内存
|
v
分区 / 块设备 / 目录树 / 镜像层
|
v
文件系统格式或叠加机制
ext4 / xfs / btrfs / squashfs / tmpfs / overlayfs / NFS
|
v
挂载到某个路径
/、/boot、/home、/data
|
v
如果挂载到 /,它在当前命名空间里就是 rootfs

再看 Docker:

Docker image layers
|
v
overlay2 合成 merged 目录
|
v
OCI runtime 把它设置为容器进程的 /
|
v
容器内看到的 rootfs

最后总结

rootfs 最核心的含义是:

在某个 Linux 系统或容器的文件系统命名空间里,作为根目录 / 使用的那个文件系统或目录树。

它不是磁盘,不是分区,也不是文件系统格式。

它更像一个系统角色:

  • 从磁盘层看,它可能来自某个分区。
  • 从文件系统格式看,它可能是 ext4、btrfs、squashfs、overlayfs 等。
  • 从挂载层看,它挂载在 /
  • 从系统启动和运行视角看,它承担 rootfs 角色。

理解 rootfs 的关键,是把“底层存储是什么”和“在 Linux 命名空间里扮演什么角色”分开。

一句话记忆:

rootfs 不是“某种格式”,而是“当前这个 Linux 环境里的 / 从哪里来”。

在Linux系统上手动配置iSCSI设备

· 阅读需 5 分钟

翻译自:https://www.ibm.com/docs/en/tsmfve/7.1.8?topic=tasks-manually-configuring-iscsi-device-linux-system

这个程序描述了在iSCSI挂载操作中如何配置一个Linux系统。来自Tivoli® 存储管理器服务器存储的VM快照将被挂载。

在你开始之前

在进行iSCSI挂载时,会在Tivoli存储管理器恢复代理系统上创建一个iSCSI目标。Tivoli存储管理器恢复代理系统上不需要Microsoft iSCSI发起器。

提示: Open-iSCSI Initiator随Red Hat Enterprise Linux和SUSE Linux Enterprise Server一起提供。

在您继续执行此任务之前,请审核以下iSCSI要求:

  • 您可以从任何系统连接到iSCSI目标,以创建包含备份数据的卷。您可以从另一个系统挂载这个卷。
  • 任何需要连接到iSCSI目标的系统都需要一个iSCSI发起器。
  • 必须在需要恢复数据的系统上安装一个iSCSI发起器。
  • 如果一个卷跨越多个磁盘,您必须挂载所有需要的磁盘。当使用镜像卷时,只需挂载镜像磁盘中的一个。挂载一个磁盘可以防止耗时的同步操作。

关于这个任务

完成这些步骤来配置在iSCSI装载操作期间使用的Linux系统:

程序

  1. 记录要恢复数据的系统上的iSCSI发起者名称。

iSCSI发起程序的名称位于

/etc/iscsi/initiatorname.iscsi

文件。如果这

发起者名称=

值为空,请使用以下命令创建一个初始化器名称:

twauslbkpoc01:~ # /sbin/iscsi-iname

以下是一个示例的发起者名称:

iqn.2005-03.org.open-iscsi:3f5058b1d0a0
  1. 将发起方名称添加到 /etc/iscsi/initiatorname.iscsi 文件中。

    1. 使用 vi 命令编辑 /etc/iscsi/initiatorname.iscsi 文件。例如:
twauslbkpoc01:~ # vi /etc/iscsi/initiatorname.iscsi
  1. 更新 InitiatorName= 参数为启动者名称。例如:
InitiatorName=iqn.2005-03.org.open-iscsi:3f5058b1d0a0
  1. 在安装了Tivoli存储管理器恢复代理(或iSCSI目标)的系统上完成以下步骤:

    1. 启动Tivoli存储管理器恢复代理。完成选择TSM服务器和选择快照对话框,然后点击挂载

    2. 在 "选择挂载目的地" 对话框中,选择 "挂载一个 iSCSI 目标"。

    3. 创建一个目标名称。确保它是唯一的,并且你可以从运行iSCSI启动器的系统中识别出它。例如:

iscsi-mount-tsm4ve
  1. 输入在步骤1中记录的iSCSI发起者名称,然后点击确定

  2. 请验证您刚刚挂载的卷是否显示在已挂载的卷字段中。

  3. 在步骤1中选定的发起系统上,定位并启动iSCSI发起器程序。

    1. 通过发出这个命令来验证iSCSI服务是否正在运行:

Red Hat Enterprise Linux:

service iscsi status

SUSE Linux Enterprise Server:

service open-iscsi status

如果服务没有运行,执行此命令以启动服务:

Red Hat Enterprise Linux:

service iscsi start

SUSE Linux Enterprise Server:

service open-iscsi start
  1. 通过执行这个命令来连接到iSCSI目标:
iscsiadm -m discovery -t sendtargets -p <IP/hostname of Tivoli Storage Manager recovery agent system> --login
  1. 通过执行以下命令来验证一个新的原始设备是否可用:
fdisk -l
  1. 挂载文件系统:

对于非LVM卷,请执行以下命令。在这个例子中,新设备是

/dev/sdb1

冒号

mkdir /mountdir
mount /dev/sdb1 /mountdir

对于LVM卷,在Linux客户端完成以下任务:

  1. 确保Linux系统上有vgimportclone脚本。这个脚本不包含在基础(默认)的LVM包中。因此,您可能需要将LVM包更新到提供此脚本的版本。

  2. 发行

vgimportclone

命令并包含一个新的基础卷组名称(

VolGroupSnap01

例如:

vgimportclone --basevgname /dev/VolGroupSnap01 /dev/sdb1
  1. 发行

    lvchange

command to mark the logical volume as active. For example:

lvchange -a y /dev/VolGroupSnap01/LogVol00
  1. 执行这些命令来挂载卷:
mkdir /mountdir
mount -o ro /dev/VolGroupSnap01/LogVol00 /mountdir
  1. 在文件恢复操作完成后,执行这些命令:

    • 对于非LVM卷,执行以下命令:

      1. 卸载文件系统:
umount /dev/sdb1 /mountdir
  1. 移除该卷。如果该卷是卷组的一部分,首先需要通过以下命令将卷从卷组中移除:
vgreduce <your_volume_group> /dev/sdb1

执行这个命令来移除卷:

pvremove /dev/sdb1
  1. 退出单个目标:
iscsiadm --mode node --targetname <target_name> --logout
  1. 退出所有目标:
iscsiadm --mode node --logout
  • 对于LVM卷,在Linux客户端上完成以下任务:

    1. 卸载文件系统:
unmount /mountdir
  1. 移除逻辑卷:
lvm lvremove LogVol00
  1. 删除卷组:
lvm vgremove VolGroupSnap01
  1. 退出单个目标:
iscsiadm --mode node --targetname <target_name> --logout
  1. 退出所有目标:
iscsiadm --mode node --logout

Wi-Fi扩展器/中继器与RelayD

· 阅读需 23 分钟

翻译自:https://openwrt.org/docs/guide-user/network/wifi/wifiextenders/relay_configuration?s[]=relayd

这篇文章描述了如何将OpenWrt路由器变成Wi-Fi扩展器/中继器。扩展器使用其一个无线电波段与主路由器进行“上行”Wi-Fi连接,并使用其其他无线电波段作为本地设备的AP(接入点)。然后,扩展器依靠relayd包来桥接两个连接。

为了简便起见,本文从现在开始将使用“Wi-Fi扩展器”或“扩展器”一词。

在以下情况下使用此配置:当你无法控制主路由器,或者主路由器没有运行OpenWrt,或者主路由器不支持首选的无线中继/扩展器与WDS802.11s 网络成型

下图展示了正常的配置。图中右侧的是主路由器:它的LAN端口(192.168.1.1/24)为本地客户端服务,而它的WAN端口(未显示)则连接到互联网。左边的是Wi-Fi扩展器。它通过无线上行链路(标记为“W-LAN (客户端)”)与主路由器相连。Wi-Fi扩展器的其他无线电则作为本地设备的接入点。

有一个Youtube视频演示了这个过程:https://www.youtube.com/watch?v=Bfmx5NjIWLQ,该过程已在OpenWrt 23.05.3版本中进行了测试,该版本发布于2024年6月。

img

使用LuCI Web GUI进行设置

配置局域网接口

本文假设主路由器的地址是192.168.1.1(子网192.168.1.0/24),而“Wi-Fi扩展器子网”的地址是192.168.2.1(子网192.168.2.0/24)。这些子网必须是不同的。

  • 移除Wi-Fi扩展器与主路由器之间的所有有线连接。
  • 将一台电脑通过以太网连接到Wi-Fi扩展器的LAN端口,并在192.168.1.1(默认地址)登录LuCI网页界面。
  • (可选)将Wi-Fi扩展器的固件更新至当前版本。
  • 系统 → 备份/刷写固件 页面,点击 执行重置 按钮可恢复至默认的 OpenWrt 设置。
  • 前往网络 → 接口,点击LAN接口旁的编辑
  • 局域网协议设置为静态地址,点击更改协议(下图)
  • 使用“Wi-Fi扩展器子网”(例如192.168.2.1)分配一个IP地址。
  • 点击保存
  • 点击“保存并应用”

img


  • 重新连接到扩展器的新IP地址(例如 192.168.2.1)
  • 网络 → 接口,点击编辑局域网接口
  • 点击 DHCP Server 标签并禁用DHCP、IPv6 RA-Service和DHCP-v6 Service。操作步骤如下:
    • 通用设置标签页(下图所示),勾选“忽略接口”框以禁用该接口的DHCP功能。
    • IPv6设置标签页(如下图),选择“禁用”RA-服务DHCP-v6服务
  • 点击保存
  • 点击“保存并应用”。
  • 最后,将您电脑的以太网端口设置为在Wi-Fi扩展器的子网中使用静态IP(例如,192.168.2.10)以及默认网关(例如,192.168.2.1),然后使用以太网再次连接到扩展器。

img


img

配置Wi-Fi上行连接

中继器通常会有多个无线电频段可作为上行链路。根据您的环境选择最适合您的一个。5GHz(n/ac/ax)频段传输速度更快,但2.4GHz(b/g/n)频段的覆盖范围更远。

  • 将你的电脑通过以太网线连接到Wi-Fi扩展器上。移除任何其它的物理连接。
  • 导航至“网络 → 无线”页面
  • 选择用于连接到主路由器的无线电频率。
  • 点击扫描按钮来使用那个无线电。

img

  • 从扫描结果中的SSID列表选择主路由器的Wi-Fi SSID,然后点击“加入网络”。

img


  • 你将看到“加入网络”面板(下图)。
    • 将“新网络的名称”设置为`wwan
    • 请输入任何 Wi-Fi 凭证,例如 WPA 密码
    • 选择lan防火墙区域。
  • 点击保存
  • 点击“保存并应用”。

img


您将会看到客户端Wi-Fi设置页面(下图)。根据需要进行编辑。最重要的设置位于操作频率一行。

  • 如果您正在连接到Wi-Fi g网络,请将模式设置为传统;如果您正在连接到Wi-Fi n网络(依此类推),请将其设置为N
  • 宽度设置为与主路由器相同的信道宽度。
  • 保持与扫描过程中发现的相同的Wi-Fi信道号。这将与主路由器相匹配。
  • 完成后点击“保存”。
  • 点击“保存并应用”。

img

移除多余的WAN接口和防火墙区域

虽然这是可选的,但建议删除多余的WAN接口和防火墙区域。

  • 转到“网络 → 接口”(如下图)
  • 删除 WANWAN6 这两个接口。
  • 转到 网络 > 防火墙(下图).
  • 删除 wan 规则。
  • 点击 保存 & 应用

**注意:**这些操作也将自动移除任何多余的防火墙流量和端口转发规则。

img

img

在"wwan"上添加静态IP地址

为新创建的wwan接口分配一个与主路由器LAN(例如192.168.1.30)同一子网中的静态IP地址。然后,你可以使用这个地址来管理路由器,该地址稍后在创建中继接口时也将使用。

  • 转到“网络 → 接口”(如下图)
  • 点击 编辑 来修改 wwan 接口设置

img

  • 在“通用设置”选项卡上,将协议更改为“静态地址”(下图)。
  • 输入一个来自主路由器局域网子网的IP地址(例如,192.168.1.30);一个子网掩码(例如,255.255.255.0);以及一个网关IP地址(例如,192.168.1.1)

img

  • 在“高级设置”标签页(下图)
  • 使用自定义DNS服务器设置为主路由器的IP地址(例如,192.168.1.1)。
  • 点击“保存”按钮
  • 按下“保存并应用”

img

测试连接

此时,Wi-Fi扩展器应该已经无线连接到主路由器。要验证连接:

  • 前往 网络 → 诊断(如下图)
  • 通过点击“IPv4 Ping”按钮进行一次ping测试。
  • 几刻钟后,如果主路由器连接到了互联网,你应该能看到ping的结果。
  • 您没有提供任何内容进行翻译,请输入需要翻译的文本。

img

安装relayd包

  • 转到系统 → 软件(如下图)
  • 点击更新列表按钮。如果Wi-Fi扩展器已连接到主路由器,并且主路由器已连接到互联网,那么几分钟后,更新的结果将会显示出来。
  • 在过滤框中输入 luci-proto-relay (下图所示),然后点击 安装
  • 当操作完成后,从系统 → 重启(下图)重启路由器。

img

img

添加中继接口

添加relayd接口,该接口将在扩展器的lanwwan接口之间进行桥接。为此:

  • 转到网络 → 接口
  • 点击 添加新接口(下图)

img

  • 添加新界面窗口中(下图)
    • 输入一个名称(“repeater_bridge”是一个不错的选择)
    • 选择下图所示的中继桥接协议。(如果中继桥接选项没有出现,请重启您的设备。)
  • 点击 创建接口

img

  • 网络 → 接口中,点击新的“repeater_bridge”接口旁的编辑按钮(如下图)
    • 确保协议为“中继桥”。
    • 输入分配给wwan接口的IP地址。(例如:192.168.1.30)
    • 在“网络之间的中继”列表中选择lanwwan
  • 点击保存
  • 点击“保存并应用”。
  • 完成上述步骤后,请重启路由器。

img

启用AP(接入点);

启用并配置Wi-Fi扩展器作为本地设备的接入点。

您可以使用与主路由器相同的Wi-Fi网络名称(SSID)以及相同的加密、密码等设置。这样可以让无线设备在最佳的Wi-Fi网络之间自由漫游。或者,您也可以选择给Wi-Fi扩展器设置与主路由器不同的SSID/加密/密码凭据。

  • 前往 网络 → 无线
  • 点击任何标有“模式:主”的SSID旁边的编辑按钮。(不要编辑连接到主路由器的“模式:客户端”上行链接。)
    • 接口配置部分,配置SSID、安全性和其他参数,以便Wi-Fi扩展器可以像接入点一样工作。
    • 如果您正在配置也用作上行链接的无线电,请确保操作频率保持不变。
    • 点击保存
  • 启用那个无线网络。
  • 您可以编辑/启用其他无线电(例如,同时启用b/g/n和n/ac/ax等无线电)。
  • 点击“保存并应用”。

你已完成!再进行更多测试

你已经完成了!Wi-Fi扩展器应该已经开始扩展你主路由器的网络了。将你的计算机切换回DHCP客户端模式,并连接到新配置的Wi-Fi。你的计算机应该已经完全接入互联网,并且从你主路由器那里获取了一个DHCP IP地址。

状态 → 概览 窗口(下图)显示最终结果。radio1 是主路由器的DHCP客户端。客户端Wi-Fi在 Host 列中有一个问号而不是IP地址,因为它的 wwan IP地址只能在网络接口页面中看到。在下面的图片中,radio0(接入点)还没有被配置/启用。但是它会显示你为扩展器配置的SSID。

img

使用命令行界面设置

在进行任何实际配置之前,必须启用 Wi-Fi 接口以便扫描附近的网络:

uci set wireless.@wifi-device[0].disabled="0"
uci commit wireless
wifi
  • 将禁用选项设置为0(以启用无线)
  • 保存更改后的配置文件
  • 使用 wifi 命令启动无线连接

现在我们可以使用iw dev wlan0 scan列出范围内的网络,如果你的无线接口名称不同,请将wlan0替换为实际的接口名称(ifconfig列出所有可用的接口,以便找出你的无线局域网是如何命名的)。

iw dev wlan0 scan` output example:

# iw dev wlan0 scan
BSS c8:d5:fe:c8:61:b0(on wlan0) -- associated
TSF: 24324848870 usec (0d, 06:45:24)
freq: 2412
beacon interval: 100 TUs
capability: ESS (0x0411)
signal: -72.00 dBm
last seen: 140 ms ago
Information elements from Probe Response frame:
SSID: Violetta
RSN: * Version: 1
* Group cipher: CCMP
* Pairwise ciphers: CCMP
* Authentication suites: PSK
* Capabilities: 1-PTKSA-RC 1-GTKSA-RC (0x0000)
BSS f8:35:dd:eb:20:f8(on wlan0)
TSF: 24225790925 usec (0d, 06:43:45)
freq: 2457
beacon interval: 100 TUs
capability: ESS (0x0431)
signal: -90.00 dBm
last seen: 1450 ms ago
Information elements from Probe Response frame:
SSID: GOinternet_EB20FB
HT capabilities:
Capabilities: 0x11ee
HT20/HT40
SM Power Save disabled
RX HT20 SGI
RX HT40 SGI
TX STBC
RX STBC 1-stream
Max AMSDU length: 3839 bytes
DSSS/CCK HT40
Maximum RX AMPDU length 65535 bytes (exponent: 0x003)
Minimum RX AMPDU time spacing: 4 usec (0x05)
HT RX MCS rate indexes supported: 0-15, 32
HT TX MCS rate indexes are undefined
HT operation:
* primary channel: 10
* secondary channel offset: below
* STA channel width: any
RSN: * Version: 1
* Group cipher: TKIP
* Pairwise ciphers: TKIP CCMP
* Authentication suites: PSK
* Capabilities: 1-PTKSA-RC 1-GTKSA-RC (0x0000)

在这个例子中,有两个网络,一个叫Violetta的Wi-Fi g网络和一个叫GOinternet_EB20FB的Wi-Fi n网络。该设备被配置为连接到名为Violetta的那个网络。

这些是配置过程中添加或更改的uci值。对于SSID、BSSID和加密,您必须使用上面的Wi-Fi扫描得到的信息。关于为什么这些值会被更改的解释,请阅读上面的luci教程。

network.lan.ipaddr='192.168.2.1'
network.repeater_bridge=interface
network.repeater_bridge.proto='relay'
network.repeater_bridge.network='lan wwan'
network.wwan=interface
network.wwan.proto='dhcp'
firewall.@zone[0].network='lan repeater_bridge wwan'
dhcp.lan.ignore='1'
wireless.radio0.hwmode='11g'
wireless.radio0.country='00'
wireless.radio0.channel='1'
wireless.radio0.disabled='0'
wireless.@wifi-iface[0]=wifi-iface
wireless.@wifi-iface[0].device='radio0'
wireless.@wifi-iface[0].mode='ap'
wireless.@wifi-iface[0].encryption='none'
wireless.@wifi-iface[0].ssid='OpenWrt'
wireless.@wifi-iface[0].network='lan'
wireless.@wifi-iface[1]=wifi-iface
wireless.@wifi-iface[1].network='wwan'
wireless.@wifi-iface[1].ssid='Violetta'
wireless.@wifi-iface[1].encryption='psk2'
wireless.@wifi-iface[1].device='radio0'
wireless.@wifi-iface[1].mode='sta'
wireless.@wifi-iface[1].bssid='C8:D5:FE:C8:61:B0'
wireless.@wifi-iface[1].key='myWifiPasswordHere'

请注意,在这个例子中设备生成的Wi-Fi网络(叫做OpenWrt)是没有密码和加密的。之所以这么做是因为本文的重点是搭建中继桥接。您可能会希望按照这里解释的Wi-Fi设置页面中的说明,以更安全的方式设置您的设备Wi-Fi网络。

网络细节

如果不需要桥接网络,你可以考虑使用简单无线客户端作为替代 relayd 的方法。Wi-Fi扩展器可以通过其静态wwan IP 地址(例如,192.168.1.30)进行管理。

即便所有连接在Wi-Fi扩展器上的终端设备都会从主路由器的LAN子网获得DHCP地址,Wi-Fi扩展器的LAN接口必须处于不同的子网中,以便relayd能够工作(由于它需要路由交通,它预期有两个不同的子网)。

由于以太网端口和接入点Wi-Fi网络都在同一个LAN接口上,所有连接到Wi-Fi扩展器设备的以太网端口和接入点Wi-Fi网络的客户端都将通过relayd进行路由,并将连接到您的主网络。

LAN 接口子网将仅用作“管理”接口,因为连接到 Wi-Fi 中继器的设备将位于主网络的子网中。如果 relayd 设备变得无法访问,您将不得不配置一台 PC,为其设置与 LAN 接口同一子网中的静态地址(例如,在我们的例子中是192.168.2.10),以便能够连接并使用 LuCI GUI 或 SSH。

故障排除

访问扩展器

如果你发现Wi-Fi扩展器本身仅能通过直接连接到W-LAN AP的计算机访问,而不能通过连接到OpenWrt W-LAN客户端的计算机访问,并且处于192.168.1.0子网中,请确保Relay bridge接口中的Local IPv4 address(本地IPv4地址)设置与无线上行链路的IP地址匹配。(另一种方法比较繁琐:如果你手动将计算机配置到该子网,可以通过OpenWrt盒子的192.168.2.1地址访问。)

检查防火墙区域

![:!:]配置的以下部分本不应该是必要的。默认操作应该已经自动更改了它们。如果有什么不工作,请也检查这个。

img


img

添加IPv6支持

在你的主路由器上激活 IPv6 支持以获取公共 IPv6 前缀。在我们的 Wi-Fi 扩展器上激活 IPv6,以允许对你的公共 IPv6 地址进行无状态地址自动配置(SLAAC)以及处理 IPv6 流量。

  1. 前往“网络/接口”,创建一个新的接口。将其命名为WWAN6,使用DHCPv6协议,并覆盖WWAN接口。在新接口的“常用配置”中,配置:请求IPv6地址:禁用。在防火墙设置中:检查“lan / repeater bridge…”行是否被选中。保留其他默认设置,特别是,将“自定义委派IPv6前缀”字段留空。在“接口/概览”页面上检查WWAN接口是否获得了公共IPv6地址。

  2. 编辑LAN接口设置,DHCP服务器/IPv6设置:检查/修改以下设置:路由器通告服务:中继模式,DHCPv6服务:禁用,NDP-Proxy:中继模式。

  3. 在你的OpenWrt设备上开启一个SSH会话。执行以下命令:

uci set dhcp.wan.interface=wwan
uci set dhcp.wan.ra=relay
uci set dhcp.wan.ndp=relay
uci set dhcp.wan.master=1
uci commit

我们假设你在之前的指南中建议加入另一个Wi-Fi网络时选择了wwan作为名称。如果不是,请相应地更改dhcp.wan.interface=…这一行。

就是这样。重新启动ophcpd(通过系统 → 启动或者执行service odhcpd restart命令),你的IPv6网络应该会开始自行配置。连接的支持IPv6的设备应该会获取来自你公共IPv6前缀派生的公共IPv6地址,并且IPv6流量应该会通过你的Wi-Fi扩展器传输。

已知问题

以下是一些最近报告的问题清单:

  1. 由接入点引起的DHCP问题。OWrt论坛
  2. 极低的上行传输速度问题影响了一些MT762x设备。OpenWrt论坛 故障报告FS#2816
  3. 连接到relayd设备的设备无法被访问。 参考这个讨论 第二个可能类似的案例 参考这个讨论
  4. 无法在同一无线电频率上启用客户端和接入点(AP)
  5. 路由器后门附加指令,因为一旦在LAN上禁用了dhcp,路由器就变得无法访问。如果无线接入点有更改,可能会发生这种情况。例如,wifi SSID、信道号或安全密码发生变化。
    1. 使用以太网缆线将计算机连接到Wifi桥的LAN端口。
    2. 在计算机上配置一个静态IP地址。例如,如果无线桥接在上述例子中使用的局域网IP地址为192.168.2.1,那么使用静态IP地址:192.168.2.10。
    3. 根据上述示例,在浏览器中访问192.168.2.1以进入LuCI界面。
  6. '''替代的relayd设置指南'''
  7. 替代的详细Relayd设置指令也可以在1-OpenWrt-LEDE安装指南HH5A的第9.10节中找到。
  8. 在macOS 10.15及以上版本上,当在局域网设置ULA前缀时,IPv6无法工作 https://github.com/openwrt/openwrt/issues/7561

使用NAT

**评论:**这看起来像是配置一个简单无线客户端的基本说明。

这种方法基本上是在第一个无线路由器上串接了一个第二个无线路由器;即通常这意味着扩展器的客户端将位于双重NAT之后。

就像用一根电缆将Wi-Fi扩展器的WAN端口与主路由器的LAN端口连接起来,Wi-Fi扩展器为自己和连接到它的设备创建了一个新的网络,这样就可以连接到互联网,并且访问主路由器的LAN网络中的设备。但在这种情况下,我们是通过无线网络来实现的。

前提条件:- 带有两个初始接口(局域网,广域网)的路由器

使用WebUI进行设置:

  • 进入“网络 → 接口”页面,点击编辑局域网接口,
  • 将局域网设置为静态IPv4地址192.168.x.1(其中x与您将通过Wi-Fi连接的网络不同),
  • 进入网络 → Wi-Fi,点击扫描,选择“网络”链接,然后点击“加入网络”。
  • 输入Wi-Fi密码,将“新网络名称”保留为“WWAN”并选择WWAN(或WAN)防火墙区域。点击保存。
  • 进入网络 → 接口页面,点击编辑 wwan 接口,
  • 移动到防火墙标签页。点击保存并应用。
  • 进入网络 → 防火墙,点击wan区域的编辑,然后在“覆盖的网络”中勾选WAN和WWAN,点击保存并应用。

现在你已经正确地将WWAN与WAN进行了绑定,并因此将WWAN与LAN也绑定起来了。

可能过时

这部分收集了本文档前面所有不确定的声明和条款。任何仍然有效的应该移动到相关的部分。

按照本文的指导使用relayd并不能保证与所有兼容Openwrt的设备或wifi网络兼容 - 仅作为最后的手段使用。

如果两个设备都支持,可以考虑使用首选的无线中继器/扩展器与WDS802.11s Mesh 网络

如果需要支持虚拟局域网(VLAN),您可以使用第2层GRE隧道(“gretap”)。

最常见的问题是,客户端路由器无法在主路由器和连接到客户端路由器的客户端之间传递DHCP消息。目前看来似乎是硬件/SOC(系统芯片)的限制(与MAC克隆有关?)。

应该可以使用 kmod-trelay 来代替 relayd,关于如何使用它的唯一信息可以在其源代码中查看,如果你成功地使用了它,请在本文中添加一个相关部分。

在这篇文章中,您将看到如何配置您的设备以使其成为一个Wi-Fi扩展器/中继器/桥接器。

在某些情况下,OpenWrt中使用的无线驱动程序不支持在客户端模式下与特定的“上游”无线系统进行“第二层”桥接。当这种情况发生时,一种方法是将LAN和上游无线系统之间的流量进行路由。像DHCP这样的广播流量和mDNS之类的本地链接发现通常是不可路由的。

当其他选项不起作用时,relayd 包为 IPv4(仅限)实现了类似桥接的行为,包括 DHCP 和广播中继。这种配置可以通过 SSH(远程终端)或通过 Luci GUI 进行。

这张图片展示了一个示例设置。中继设备的LAN接口必须位于不同的子网上,中继功能才能工作(因为它需要路由流量,它预期有两个不同的子网)。

由于以太网端口和接入点Wi-Fi网络都在同一个LAN接口上,所有连接到Wi-Fi扩展器设备的以太网端口和接入点Wi-Fi网络的客户端都将通过relayd进行路由,并将连接到您的主网络。

LAN 接口子网将仅用作“管理”接口,因为连接到 Wi-Fi 中继器的设备将位于主网络的子网中。如果 relayd 设备变得无法访问,您将不得不配置一台 PC,为其设置与 LAN 接口同一子网中的静态地址(例如,在我们的例子中是192.168.2.10),以便能够连接并使用 LuCI GUI 或 SSH。