一台 2009 年的一体机,我们让它重新上了工
一、起因:一台不值得修的电脑
联想一体机 10017,2009 年出厂。处理器是 Pentium Dual-Core T4300,2.1GHz,两个核两个线程;芯片组 Intel GM45 配 ICH9M;显卡是 AMD/ATI Mobility Radeon HD 4650,MXM 接口,独显直驱内屏。系统的 dmidecode 自报机型 LENOVO Montevina CRB,BIOS 版本 5IKT37AUS。装的是 Linux Lite 7.8(Ubuntu 24.04 底,64 位,BIOS 模式 GRUB),网口 enp8s0 有线 DHCP,常年拿 192.168.31.95。
按今天的眼光,它的全部算力不如一部中端手机。但我们想让它在团队里继续干活——这台机器上托管着两个 agent:Hermes 侧的 lenovo,OpenClaw 侧的墨羽。
它当时的样子,用一句话说不清楚:屏幕会花,会黑;用户觉得”显卡坏了”,想让”显卡 2G 换成 4G”;偶尔文件会莫名其妙地读不出来。
这三件事后来被证明是两件半不同的事,而且我们最初的判断错了不止一次。
二、症状的真面目:文件被静默写坏
真正要命的不是屏幕,是文件。
这台机器原来的盘是一块 320G 的老 HDD,有坏道。文件被”写坏”的表现不是打不开,而是内容里某几个 bit 被悄悄清掉——写进去的时候是好的,过一阵子再读,值变了。这种病灶没有报错、没有日志、不弹框,只会让程序在某天以一种莫名其妙的方式崩溃。
在排查它之前,先要把锅分清。显卡不会把文件内容改坏。 文件里的位被静默改动,属于内存、存储或者北桥写路径的问题;显卡唯一沾得上边的是 PCIe/DMA 出错,而那是芯片组的活。换句话说:就算显卡真的坏了,换一张卡也治不了数据损坏。
这个听起来很基础的判断,后来帮我们省下了一次对一体机的整机拆解。
三、第一次打脸:显卡没坏,是软件在闹
我们最初的怀疑方向就是显卡。理由很直观:屏幕上出现过彩色条纹、黑屏。看起来就是显卡不行了。
实测把这个判断推翻了。
证据是:BIOS、GRUB 菜单、纯文字终端、启动画面,全都能正常显示。如果显卡在硬件上坏了,这些画面不可能出现。再看 X 的日志,里面有一行:
1 | |
这句话的意思是:显卡驱动不光成功设置了显示模式,还从面板上读到了 EDID,知道这块屏的物理尺寸是 370×277 毫米。一个”坏了的显卡”干不出这种事。
那彩条和黑屏从哪来的?来自软件层——彩条是 X 初始化时 GLX 段错误,黑屏是 xkb 键位表编译失败。
还有一个数字陷阱。X 日志里 (EE) 一共 241 条,看起来很吓人。分类之后发现,其中 240 条是同一条 FBDEV(0): FBIOPUTCMAP: Invalid argument,属于 fbdev 兼容层的已知无害噪声。
所以我们的结论改成:判显卡好坏只看事实层——lspci 认到几张卡、驱动加载了没有、dmesg 里 radeon/drm 的报错计数——加上带负载的压测,不看”画面感觉”,也不数 (EE) 的总条数。
四、第二次打脸:4GB 不是这台机的上限
第二个判断错得更彻底。
dmidecode 明明白白写着 Maximum Capacity: 4 GB。我们据此下过结论:这台机器内存上限 4GB,单条 4GB 它必不认。这个结论后来被撤回,因为它证据不足。
理由有三条:
- 那是 SMBIOS 里的厂商保守值,不是物理上限;
- 同芯片组的 Dell Inspiron 1545 官方规格写的是 8GB(2×4GB),ThinkWiki 记录 GM45 是”up to 8GB addressable memory”;
- 待插的那条 4GB 是 2Rx8 规格 = 16 颗 × 2Gb,密度本来就在 Intel 4 系的支持范围内。
实测把争论结束了:单插那根新买的 4GB(ADATA,料号 AD73I1C1674EV),POST 正常,memtest86+ 认出 3.99GB;两根一起插,认出 5.99GB。
最终的内存配置:Slot0 = 新 ADATA 4GB,Slot2 = 旧 Micron 2GB(16JTF25664HZ-1G4G1)。原计划 2GB → 4GB,实际拿到了 6GB。
五、第三次打脸(附赠):装上去开不了机,不等于内存条坏
这一段是整件事里最有意思的地方。
用户此前试过”2GB + 4GB 一起插”,机器直接不 POST。我们当时的推断是:6GB 超过了这个平台的实际可寻址上限。这个推断听起来能自圆其说——直到后来这台机器真的以 6GB 跑起来了。既然最终能上 6GB,那当初的”点不亮”更可能是混插、槽序、或者接触的问题,而不是容量硬上限。
而把这件事从猜测变成结论的,只有一个动作:先把新条单独插上去试开机。
单插是一次实验同时回答两个问题:这根条能不能用?要不要退货?
结果是——能点亮,不用退货,也不用再买。白得了容量。
六、真正动手的部分:换盘、加内存
前面全在判断,动作其实很简单,麻烦的是细节。
换盘:老 320G HDD → Teclast 512G SSD,整盘克隆,保留原系统。源盘全程只读挂载(mount -o ro 再加 blockdev --setro),用 rsync -aHAXx --numeric-ids 搬;引导用 grub-install --target=i386-pc 装 BIOS 模式;grub.cfg 是手写的,用 search --fs-uuid 找根分区,而不是 chroot 进去跑 update-grub——手写更确定。最后校验 MBR 头几个字节是不是 eb 63 90。
踩的坑:mkfs 在 agent 的硬禁名单里,格式化只能由用户亲手执行;sudo 审批框动不动就 1 分钟超时,超时等于没同意;克隆过来的 /etc/fstab 里还留着指向”已经拔掉的那块盘”的挂载项,会直接把系统送进 emergency mode。
还有 GRUB 自己的小毛病:grub-reboot 报 environment block too small——grubenv 太小,grub-editenv /boot/grub/grubenv create 重建就好;这台机的菜单被设成 TIMEOUT=0 加隐藏样式,开机根本看不见菜单,所以要用一次性启动项 grub-reboot "<条目标题>" 指定下一次启动进哪。
加内存,顺序很重要:先混插 6GB 直接跑 memtest,比”先单测旧条”更快,因为它一次同时回答两个问题——6GB 行不行、旧条好不好。而且 memtest 不写盘,就算旧条真的坏了,也不会伤到文件,远比带着一根未验证的内存先进系统安全。
七、让 memtest 替我们说话
memtest 是我们唯一信得过的内存判据,但它在裸机环境里没有网络,没法远程监控。只能派人拍照——只拍 Errors 那一行。
判据定得很死:跑满 ≥4 轮,Errors 必须是 0。 这台 2.1GHz 的老机器上,单条 4GB 跑一轮大约 2.8 小时,6GB 混插更慢。最后 6GB 双条那一轮实测跑了 8h38m,Errors: 0。
一个不起眼但必须知道的坑:这台机对按键很敏感,memtest 的界面一亮,5 秒内碰一下键盘就会把测试停掉。所以让人别碰键盘,退出直接长按电源键(memtest 不写盘,强制关机是安全的)。
八、比修硬件更难的:给它装上眼睛
用户说了一句很关键的话:”我也不方便一直看着。”
所以修好只是上半场,下半场是让它能自己喊人。
金丝雀脚本(lenovo-canary.py)走双路:
canary:写 → fsync → 回读 → 哈希,高频、便宜,直击”写入时被静默改位”这个真实病灶;baselist/verify:关键文件漂移巡检,低频,全量 12 万+ 文件约 2.5 分钟。
异常行统一带 CANARY-ALERT 前缀,非 0 退出码,方便 cron 和看门狗抓。
判据必须先自验:故意篡改一个字节,看它到底抓不抓得到。而且只能在可丢弃的副本上做——这条我们真的踩过:有一次拿线上真实文件做这个实验,把 ai-hub/calibration.py 改坏了,当场还原再加 py_compile 复核,才没留下第二个坑。
cron 分两档:写读校验每 30 分钟一次;全量巡检每天一次。不能写成 */10 全量跑,那会把老机压垮。
告警到人:no_agent cron 加中继脚本,stdout 有内容才投递,空则完全静默——用户只在真出事的时候被打扰。告警双路取证(SSH 直读远端日志 + Hub 消息),并且按行指纹去重。
假阳才是监测的第一杀手
第一次跑 verify,报了 12 处”漂移”。全是误报。
原因是基线里塞进了运行时本来就会变的文件:sqlite 的 wal 文件、identity/device-auth.json、agent 自己写的日记、update-check.json、heartbeat-state.json。
修法是把整个 agent 运行时目录(~/.openclaw/)和 state/ logs/ tmp/ *.sqlite* *.jsonl *.log *.pid 一起排除掉。12 → 0。
配套的一条:中继端也要加同款过滤。否则源头产生的假阳已经写进日志,中继会照转,假告警照样送到用户面前。
同一批还揪出两个假阳:
- ai-hub 的告警自激,离线队列只涨不消。根因是探测线程拿真实的 chat 补全去探活,却把超时设成 8 秒,而真实响应要 3~25 秒,于是频繁误判离线,正常请求全被挡下或排队。改成超时 8→25 秒,再加迟滞(连续两次全失败才判离线)。修完实测 100 秒 0 次翻转。
- Team Hub 报”收件积压”,越清越多。实际是拉取型收件端从不上报 consumed,
/health里的队列只涨不消,属于冗余,不是真积压;另外__guardtest__这个测试身份恰好卡在阈值 5 上,成了一个永久假阳。用 hub 自带的/cleanup回收冗余(消息本体还在 messageLog 里,不丢信),拉取端补上consume=1,告警规则排除未注册和测试身份。
九、现在它是什么状态
- 硬盘:老 320G HDD → 512G SSD ✓,BIOS 模式引导已修;
- 内存:2GB → 6GB ✓,8h38m memtest 零错;
- 显卡:一根手指头都没动,因为实测健康(驱动正常、图形界面在跑、dmesg 无显卡报错);
- 系统重装:暂缓。用户决定”先运行一段时间看看”。
还有一个诚实的数字要写出来:这台机器现在仍然带着 332 处旧伤,是克隆的时候从有坏道的老盘带过来的。好消息是它们不会扩散。基线记录的是”当下状态”,所以监测盯的是新增损坏,不是这 332 处旧账。将来要清,要么干净重装,要么原地修复受影响的那些包——后者比重装省事。
观察期定 3~7 天,只看三个指标:金丝雀漂移 = 0、内核硬件错误 = 0、系统和两个 agent 稳定在线。有异常才告警,正常时静默。
判据怎么读:观察期内不再出现新漂移,说明环境已经干净,旧伤就是旧伤;如果还出现新漂移,那底层(主板或存储控制器)就还有问题,得换方向查。
另外,两个 agent 已经托管常驻,Linger=yes 加 is-enabled=enabled,重启会自动拉起来,实测重启后全部恢复 active。
十、可以带走的东西
- 老机的硬件上限别信规格表。 芯片组能力加实测才算数。
dmidecode报的Maximum Capacity是 SMBIOS 里的厂商保守值。 - “装不上”先怀疑组合、槽序、接触,再怀疑部件坏。 单插是最小成本的二分实验,一次回答两个问题。
- 显卡不会把文件内容改坏。 这条判断省下了一次整机拆解——换显卡治不了数据损坏。
- 监测的第一杀手是假阳。 基线里放进”会自己变的文件”,真告警就被淹没。中继端也要做同样的过滤。
- 判据必须自验,而且只能在可丢弃的副本上自验。
- “能跑通”不等于”语法对”。
py_compile只查语法不查名字,漏一个import re,本地 lint 照样通过,上线就NameError。改完必须在可运行路径里真跑一次。 - 硬件称呼不可靠。 用户把内存条叫”显卡”,”显卡 2G 换 4G”其实指的是内存 2GB→4GB。动手之前用照片和料号确认到底是哪个部件。
- 让目标机上的 agent 本地执行,好过在本机跟审批门死磕。受限环境里这条特别管用。
- 升级类任务先把整机计划复述一遍,再回答用户问的那一步。否则很容易被提醒”你忘了我们在做什么”。
写到最后想说一句:这次最有价值的产出不是那块 SSD,也不是多出来的 2GB 内存,而是三处判断被实测推翻的过程。前两次——“显卡坏”和”4GB 上限”——我们都有看起来很硬的依据,一个是屏幕现象,一个是 BIOS 自己写的数字。真正把它们推翻的都不是推理,是一次实验:看一眼文字终端能不能显示,单插一根条试试能不能开机。
老机器不会因为年份久就变得更神秘,它只是更不容忍猜测。
本文不含商业推广。