一台配备 8GB 内存的树莓派正在运行 OpenClaw,同时有三个 Session 执行任务。系统本身仍能通过 SSH 登录,基础命令也能运行,但三个 Session 突然全部显示 disconnected,网页控制界面几乎失去响应。
过去遇到这种情况,最直接的处理方式往往是强制断电重启。但这一次没有立即重启,而是保留现场、检查进程和内核状态,最终成功让原有 Session 自动恢复,正在执行的任务也没有因为重启而被强制中断。
整个过程展示了一个很典型、也很容易被误判的问题:
系统看起来像“死机”,实际上并没有发生 OOM Kill,而是陷入了由内存软限制、页面回收和低速存储共同造成的内存回收风暴。
一、故障现象
故障出现时,OpenClaw 网页端的三个 Session 同时断线,但操作系统仍然可访问。
系统状态大致如下:
物理内存:约 8GB
可用内存:约 1.2GB
Swap:0
系统负载:快速升高
I/O wait:超过 50%
OpenClaw 服务仍显示:
Active: active (running)
Memory: 约 5.1G
MemoryHigh: 5G
MemoryMax: 6G
进程列表中,大量 openclaw 和 openclaw-hooks 进程处于 D 状态,等待位置包括:
mem_cgroup_handle_over_high
folio_wait_bit_common
wait_on_buffer
这几个信息非常关键。
D 表示不可中断睡眠,通常意味着进程正在等待块设备、文件系统或内核内存回收操作。进程没有退出,也不代表程序逻辑已经崩溃,只是暂时无法获得需要的资源。
二、首先确认:有没有发生 OOM Kill
面对内存不足,首先需要判断的是:
内核有没有真正杀死 OpenClaw 或 Session 对应的进程?
检查内核日志:
journalctl -k --since "最近一段时间" --no-pager \
| grep -Ei 'oom|out of memory|killed process|memory cgroup'
结果没有任何输出。
随后检查 OpenClaw 服务对应 cgroup 的内存事件:
cg="$(systemctl --user show openclaw-gateway.service \
-p ControlGroup --value)"
cat "/sys/fs/cgroup${cg}/memory.events"
结果类似:
low 0
high 1622022
max 0
oom 0
oom_kill 0
oom_group_kill 0
这里最重要的是:
oom 0
oom_kill 0
这意味着没有发生 OOM,也没有任何进程被 OOM Killer 杀死。
但另一方面:
high 1622022
说明 MemoryHigh 已经被触发了一百多万次。
这不是普通的“内存有点紧张”,而是服务长时间超过内存软限制,内核正在持续对该 cgroup 进行直接回收和限流。
三、真正的问题:MemoryHigh 引发的回收风暴
systemd 中常见的两个内存控制参数是:
MemoryHigh=
MemoryMax=
两者作用不同。
MemoryHigh 是软限制。服务超过该数值后,内核不会立即杀死进程,而是通过内存回收、限流和压力控制,迫使进程降低内存占用。
MemoryMax 是硬限制。超过该限制且无法回收时,才可能进入 cgroup OOM 处理。
原配置大致是:
[Service]
MemoryHigh=5G
MemoryMax=6G
对于 8GB 内存的设备,这个配置表面上看较为保守,但当 OpenClaw 同时运行多个大任务时,会出现一个问题:
- 服务实际工作集超过 5GB;
- 内核开始强制回收;
- 系统没有 Swap;
- 被回收的页面稍后又要重新从存储中读取;
- 多个进程同时等待 SD 卡;
- 页面刚读回来,又因超过
MemoryHigh被回收; - 形成反复读取、回收、再次读取的循环。
这就是内存回收风暴。
四、PSI 揭示了系统几乎完全停顿
Linux 的 Pressure Stall Information 可以直接反映 CPU、内存和 I/O 压力。
检查服务的内存压力:
cat "/sys/fs/cgroup${cg}/memory.pressure"
故障高峰时的数据类似:
some avg10=96.20
full avg10=93.60
其中:
some表示至少有部分任务因内存压力而停顿;full表示该 cgroup 中所有可运行任务都因内存压力而停顿。
full avg10 接近 94%,意味着过去十秒内,整个服务几乎有九成以上时间无法正常推进。
此时网页断线并不奇怪。Gateway 本身也被困在同一个 cgroup 中,连维持 WebSocket 响应都变得困难。
五、为什么三个 Session 看起来断了,实际上还活着
进程树检查显示,OpenClaw Gateway、Codex app server,以及多个 Hook 和 Session 相关进程仍然存在。
日志也仍能看到类似状态:
state=processing
activeWorkKind=model_call
lastProgress=...
recovery=none
网页端出现的只是:
webchat disconnected
随后又可以看到:
webchat connected
sessions.list
sessions.usage
因此,真正断开的只是前端 WebSocket 连接,不是 Session 本体。
这也是为什么不应立刻重启服务或强制断电。
只要进程还在,Session 状态仍在,正在执行的任务就可能继续恢复。
六、第一步救援:增加临时 Swap
系统当时完全没有 Swap:
Swap: 0B
于是创建一个临时 4GB Swap 文件:
swap=/swapfile-rescue
fallocate -l 4G "$swap" ||
dd if=/dev/zero of="$swap" bs=16M count=256 status=progress
chmod 600 "$swap"
mkswap "$swap"
swapon -p 100 "$swap"
启用后,Swap 很快开始被使用:
Swap used:
1MB
40MB
100MB
800MB
1.2GB
与此同时,物理内存的 available 从约 1.1GB 上升到 2GB 以上。
这说明内核开始把暂时不活跃的匿名页面移入 Swap,为当前活跃任务腾出物理内存。
此时系统没有立刻完全恢复,但已经避免继续向 OOM 方向恶化。
七、仅有 Swap 仍然不够
加入 Swap 后,进程虽然没有被杀死,但系统一度仍然存在严重 I/O 抖动。
vmstat 显示:
b: 28~36
wa: 90%~98%
b 表示等待 I/O 的阻塞进程数量。
wa 接近 100%,说明 CPU 几乎没有在执行有效计算,而是在等待 SD 卡。
进程等待位置也变成:
rq_qos_wait
folio_wait_bit_common
这说明 Swap 解决了“无处安放内存页”的问题,但没有解决 MemoryHigh=5G 持续强制回收的问题。
内核一边把页面写入 Swap,一边又因为任务继续访问这些页面而将其读回,形成明显的交换抖动。
八、第二步救援:临时放宽 MemoryHigh
为了停止过度回收,将运行时 MemoryHigh 从 5GB 提高到 6GB:
systemctl --user set-property --runtime \
openclaw-gateway.service \
MemoryHigh=6G
此操作不会重启服务,也不会杀死已有进程。
核验结果:
MemoryHigh=6442450944
MemoryMax=6442450944
调整后,系统状态迅速发生变化。
内存 PSI 从:
some avg10 ≈ 95%
full avg10 ≈ 89%
下降到:
some avg10 ≈ 22%
full avg10 ≈ 15%
vmstat 中的 I/O wait 也从长期 90% 以上,大幅降至个位数。
同时可以观察到:
si 较高
so 接近 0
这意味着系统开始把之前换出的页面从 Swap 读回内存,而不再持续向 Swap 写入更多页面。
CPU 使用率也明显上升,说明进程终于从“等待磁盘”恢复为“实际执行计算”。
九、Session 自动恢复
随着压力解除,内存状态逐渐恢复:
Mem used: 约 2.1GB
Mem available: 约 5.5GB
Swap used: 约 700MB
网页重新可以访问,原来的 Session 自动连接回来。
TUI 中显示:
finishing context | connected
Session 名称没有改变,说明恢复的是原来的会话,而不是新建会话。
其中的耗时包含了此前被内存回收和 I/O 阻塞的时间,因此即使显示运行十几分钟,也不代表恢复之后又正常执行了十几分钟。
这是典型的“任务仍然活着,只是曾经长时间无法获得资源”的表现。
十、永久解决方案
确认系统恢复后,将临时救援方案整理为正式配置。
1. 创建标准 4GB Swap
最终使用标准路径:
/swapfile
创建并启用:
fallocate -l 4G /swapfile ||
dd if=/dev/zero of=/swapfile bs=16M count=256 status=progress
chmod 600 /swapfile
mkswap /swapfile
swapon -p 100 /swapfile
写入 /etc/fstab:
/swapfile none swap sw,pri=100 0 0
重新加载 systemd:
systemctl daemon-reload
验证:
findmnt --verify
swapon --show
free -h
findmnt 可能提示 Swap 来源是普通文件:
non-bind mount source /swapfile is a directory or regular file
只要最终结果是:
0 parse errors, 0 errors
就不属于配置错误。
2. 放宽 OpenClaw 内存限制
最终配置调整为:
[Service]
MemoryHigh=6G
MemoryMax=6656M
也就是:
- 6GB 开始进行软限制;
- 6.5GB 作为硬上限;
- 给操作系统、SSH、systemd 和其他服务保留约 1GB 以上空间;
- 4GB Swap 作为极端情况下的缓冲。
配置文件写入后:
systemctl --user daemon-reload
为了不重启当前 Gateway,可同步应用运行时限制:
systemctl --user set-property --runtime \
openclaw-gateway.service \
MemoryHigh=6G \
MemoryMax=6656M
核验:
systemctl --user show openclaw-gateway.service \
-p MemoryCurrent \
-p MemorySwapCurrent \
-p MemoryHigh \
-p MemoryMax
十一、为什么不应直接断电
强制断电确实可以让系统快速回到“表面正常”,但代价很大:
- 所有正在执行的 Session 会立即中断;
- 尚未落盘的结果可能丢失;
- Session 状态文件可能处于不完整状态;
- SD 卡文件系统存在损坏风险;
- 后续很难判断任务究竟执行到了哪里;
- 原本可以恢复的进程会被无条件终止。
这次故障中,没有任何进程被 OOM Killer 杀死。三个 Session 只是被困在内存回收和存储等待中。
如果直接断电,实际上等于主动杀死了本来还有机会恢复的任务。
十二、以后再次遇到类似情况,应检查什么
当 OpenClaw 网页无响应或多个 Session 同时断线,但 SSH 仍能登录时,可以依次检查:
是否发生 OOM
journalctl -k --since "最近一段时间" --no-pager \
| grep -Ei 'oom|out of memory|killed process|memory cgroup'
cgroup 内存事件
cg="$(systemctl --user show openclaw-gateway.service \
-p ControlGroup --value)"
cat "/sys/fs/cgroup${cg}/memory.events"
重点关注:
high
max
oom
oom_kill
内存压力
cat "/sys/fs/cgroup${cg}/memory.pressure"
进程是否仍然存活
pstree -ap "$(systemctl --user show \
openclaw-gateway.service -p MainPID --value)"
是否大量卡在 D 状态
ps -eo pid,ppid,stat,rss,etime,wchan:32,cmd \
--sort=-rss |
grep -E 'PID|openclaw|codex|MainThread'
是否发生 I/O 风暴
vmstat 1 10
如果发现:
oom_kill=0
而大量进程仍存在,只是处于 D 状态,就不应立即重启。
这通常意味着任务还有恢复希望。
结语
这次故障最有价值的结论,不是简单地“增加 Swap 就解决了”,而是厘清了几个容易混淆的状态:
- 网页断线不等于 Session 死亡;
- 内存不足不等于已经发生 OOM;
- 进程处于
D状态不等于进程已经崩溃; - Swap 可以避免 OOM,但无法单独解决过度严格的
MemoryHigh; - 软限制配置不当,也可能制造比直接 OOM 更漫长的系统停顿;
- 只要进程仍在、
oom_kill=0,就值得优先尝试无重启救援。
最终,一台看似已经“卡死”的 8GB 树莓派,在没有重启、没有杀进程、没有丢失 Session 的情况下恢复了正常运行。
相比直接断电,这种处理方式更慢一些,但保住了正在执行的工作,也避免了文件系统和任务状态损坏。