停更 5 年,它本该被淘汰 —— 直到 Workbuddy 让它跑在新 M6 上

一、 开头:死亡现场

最近入手了一台 Mac Mini 6(Apple M6 芯片),计划用来评测一些小机型大模型的情况。因此,需要一个能直观、方便观察系统性能的工具。综合对比下,开源项目 eul 非常合适------它足够小巧玲珑(App 仅 5MB 左右),且原生常驻在菜单栏。

但在用 brew 安装后,结果让人傻眼:CPU 温度和 GPU 相关信息全部显示 N/A。对于要跑大模型测试的场景来说,这些信息极其关键。

图 1.1 CPU 温度和 GPU 相关信息全部显示 N/A

于是找到了 eul 的源码。GitHub 上,master 分支的最新 commit 是 b5c3bd0,提交信息写着 Create Localizable.strings (#238)。日期栏赫然显示:5 years ago。

图 1.2 eul项目源码更新情况

右上角 10k Star 似乎在展示着过去的辉煌,就这样静静地躺在 GitHub 上。Issues 区里,有人在两年前问:"M1 还有救吗?"没有人回答。

在 M6 上它还有救么?我的 macOS 应用开发经验不多,但在如今的 AI 时代,或许也可一试。


二、 问题:调研现场

拿到 5 年前的源码后,发现 eul 的温度读取实现源自 Intel 时代的 SMCKit 。SMCKit 的 README 明确写着它「for Intel based Macs」,其内置的传感器清单全部是 Intel 机型的 key(TC0P/TG0P/TM0S 等),显然对于 Apple Silicon 毫无办法。

使用 Debug 构建,断言直接触发,App 启动即崩溃:

eul/SMC.swift:390: Assertion failed: SMCParamStruct size is != 80

显然,原有的一些数据结构已经不能适用于如今 M6 的运行环境了。

查找一番资料后,发现一个更伤感的事情:Apple 从未公开 AppleSMC 的 API 和 key 命名规范。难道要凭我这有限的 macOS 研发经验去盲猜?

是时候用下这个时代的工具了:Workbuddy


三、 复活吧,eul:先写探针,再动手术

由于没有官方文档支持,Workbuddy 首先写了一个 Swift 探针(smc_probe.swift),直接与底层驱动对话。探针做两件事:

  1. 读取 #KEY 得到本机 key 总数,逐个枚举,筛出所有 T* 前缀的温度 key,并解码当前值。
  2. 遍历 IOAccelerator 服务,打印 PerformanceStatistics 的全部统计项。

最戏剧性的一幕发生了:用来排查故障的探针,自己先崩了。

探针在读取数据时,底层的 IOKit 调用直接抛出了错误:IOKit call error: 0x-1ffffd39。为了查明原因,让探针打印出它发给驱动的数据结构尺寸。结果发现,尺寸变成了 88 字节。

这恰好解释了前文 eul 启动崩溃时的那个断言报错:SMCParamStruct size is != 80,显然之前面对 88 字节的驱动数据包,程序底层直接拒绝了沟通。

这意味着,用来排查问题的探针,自己先完美复现了 eul 的故障。这直接让根因定位从"盲人摸象"变成了"精准实测"。

探针跑通后,拿到了 M6 的一手数据:

  • SMC 总 key 数量:2066
  • Tx 温度类 key 数量:135
  • 温度类型:135 / 135 均为 flt 类型,没有 sp78

Workbuddy 顺藤摸瓜,制定了"四刀"修复方案。

第一刀:缝合 ABI 裂缝(88 字节 vs 80 字节)

结构体 SMCParamStruct 出自 Apple 早期的开源项目,驱动侧固定按 80 字节解析。但结构体中的一个字段 IOByteCount,在 arm64 用户态下变成了 64 位(8 字节)。这多出来的 4 字节加上对齐填充,让结构体变成了 88 字节。 Debug 构建直接崩溃,Release 构建调用静默失败。

  • 修复: 不依赖平台定义,Workbuddy 建议直接把 dataSize 的类型固定为 UInt32。这样既保证了二进制布局稳定,又去掉了三处多余的类型转换。

第二刀:自适应数据类型(sp78 vs flt)

原代码把读取逻辑写死为 Intel 时代的 sp78(2 字节定点)。而 M6 全是 flt(4 字节 IEEE 754 浮点)。按 sp78 去读,只能读出垃圾值。

  • 修复: 由"假设类型"改为"查询类型"。先查 keyInformation 拿真实类型,再按实际类型选解码器。Workbuddy 顺手补齐了原有 fromSP78 丢失小数位的 Bug,并新增了 fromFLT 解码器,一举覆盖 T2 和 Apple Silicon 芯片。

第三刀:放弃静态清单,拥抱动态发现

原代码只遍历硬编码的 28 个 Intel key。在 M6 上,这些 key 一个都不存在,导致传感器列表为空。

  • 修复: 新增 allTemperatureSensors() 函数,直接枚举 2066 个 key,按"T 前缀 + 温度类型"筛选出 135 个候选,再剔除未初始化的热区占位符(如 Tz11 恒为 0.0 °C),最后保留 126 个有效传感器。
  • 纠错插曲: 聚合温度时,第一版代码是按"显示名"匹配前缀的。但显示名早已被改写(如改为"CPU P-Core"),导致匹配不上。Workbuddy 提醒道:标识符(key code)与显示名必须严格分离 。改为按 Tp0、Tg0 等代码前缀匹配后,温度终于出来了。

第四刀:GPU 识别脱虚向实

原代码要求 GPU 必须有 PCI 标识符(device-id / IOPCIMatch)。但在 Apple Silicon 上,集成 GPU 根本没有这玩意儿,导致 GPU 列表被直接丢弃。

  • 修复: 让 device-id 从"必需"变成"可选",缺失时用型号名(如 Apple M6)兜底。GPU 的温度也从"必须有 registry 字段"改为"registry 优先,SMC Tg0x 兜底"。

四、 跑起来了,但这不是终点

经过编译和构建参数的微调(调整 SDKROOT 等),App 终于成功跑起来了。

图 4.1 CPU 温度和 GPU 相关信息全部正常显示

在这台 M6 上,它成功读取到了:

  • CPU 温度:32 °C
  • GPU 温度:31 °C
  • GPU 使用率:10 %

但这不仅仅是几个数字的胜利。修复前后的代码对比,本质上是把 eul 的底层采集架构,从"面向 Intel 机型的死胡同"拉回了"面向底层协议"的正轨。

修复后的整体架构演进

图 4.2 整体架构演进

从上图可以看出,系统的数据流向没有变,依然是自下而上(硬件 -> 接口层 -> 采集层 -> 状态层 -> 展示层)。真正的变化,发生在那几根最底层的箭头上。

以前,底层接口层(SMCKit / IOHelper)与硬件之间是"硬连线"的,键名、类型、标识符全写死;现在,它们变成了一条带有"翻译器"和"过滤器"的通用管道。

温度读取链路:从"死胡同"到"自适应"

图 4.3 温度读取链路

左侧是原来的路径:按 28 个 Intel 硬编码键名去查 -> 查不到 -> 列表为空 -> 显示 N/A。 右侧是修复后的路径:枚举全量 Key -> 筛选 flt/sp78 类型 -> 合理性过滤(剔除 Tz11 这类恒为 0 的无效热区)-> 按 Key Code 前缀聚类取最大值 -> 输出真实温度。

架构级变化: 彻底解耦了"显示名"与"标识符"。原来的代码按 "CPU_0_DIE" 去查传感器,一旦硬件改了名,代码就崩。现在程序只认底层的 Tp0、TPD 等代码前缀,显示层用 friendlyName 随时可以包装成漂亮的"CPU P-Core"。这就是"面向协议"带来的韧性。

GPU 读取链路:脱虚向实后

图 4.4 GPU 读取链路 GPU 链路的修复异曲同工。原代码在 system_profiler 和 IOAccelerator 里死找 PCI 标识符(device-id / IOPCIMatch),找不到就丢弃整条记录。

修复后,标识符变成了"双轨制":有 PCI 就走 PCI,没有 PCI(如 M6 集成 GPU)就用型号名(如 Apple M6)兜底匹配。同时,GPU 温度不再强求从 registry 里拿,而是回退到 SMC 的 Tg0x 传感器集群。

修改规模与兼容性回归

这次"手术"看似动了底层,其实切口极小,并未伤筋动骨:

文件 改动量 核心使命
SMC.swift +81 / -10 修复 ABI 裂缝、自适应解码、动态发现传感器
SmcControl.swift +51 / -12 传感器过滤、按前缀聚合最大温度
GPU.swift +23 / -8 移除 PCI 强依赖,兼容型号名兜底
GpuStore.swift +11 / -3 重构 GPU 统计匹配逻辑
project.pbxproj +1 / -1 修复 SwiftPM 的 SDKROOT 脚本报错

更重要的是兼容性 。通过回归验证,Intel 机型的逻辑不仅没被破坏,反而因为修复了 sp78 的小数位精度而变得更好。命名传感器(如 CPU_0_DIE)在 Intel 机器上依然是第一优先级,回退分支根本不会触发。这才是一个合格的开源重构:不砸别人的饭碗,只接自己的断腿。


五、 复盘:当 AI 越过"门槛",开发者还剩什么?

回顾这次修复,Workbuddy 扮演的角色远不止一个"代码补全工具"。在这个过程中,它深刻展示了 AI 时代解决底层系统问题的新范式。

1. AI 填平了晦涩的"黑盒鸿沟"

如果是传统开发模式,面对"未公开 API"和"报错 0x-1ffffd39",缺乏 OS 开发经验的工程师大概率会卡在查阅无数个过时的 StackOverflow 帖子中。但 Workbuddy 能迅速理解崩溃日志的底层逻辑,通过生成探针代码"反过来"测出结构体大小,从而推导出 88 字节的 ABI 裂缝。AI 把黑盒变成了灰盒,把试错从"盲人摸象"变成了"按图索骥"。

2. 从"我怎么写"变成了"我怎么设计"

在这个项目中,核心的困难不在于写出 Swift 的语法,而在于"传感器怎么聚合更合理"、"标识符与显示名分离"这样的架构决策。Workbuddy 负责生成 80% 的样板代码、适配和修复草稿;而人负责验证、测试、重写那 20% 的关键逻辑。AI 负责往前冲,人负责拉缰绳。 这种协作模式,极大拉高了个人能力的上限。

3. AI 无法替你承担"最后一步"

AI 可以写出 fromFLT 解码器,但它没法替你处理 Xcode 的签名证书;它可以帮你把 pciMatch 改为可选,但无法替你承担代码合并到主干后的开源维护责任。"写代码"的门槛在变低,但"为结果负责"的门槛永远在。


六、 结尾:停在 5 年前的代码,也有权利跑在新 M6 上

10k Star,380 Fork,35 Contributors,48 Releases。这些数字曾经代表一个活跃的项目,后来变成了一座数字纪念碑。

但 AI 没有让旧代码变新,它只是帮我们跨过了时间。真正复活这个项目的,不是某个模型,而是"还有人愿意为它再编译一次"。

开源不是免费劳动力。AI 能降低维护门槛,但不能替代责任、测试和社区。

下一个 5 年,也许它还会再次停更。但至少现在,它跑在了这台 Mac Mini 6 上,读出了 32 °C 的 CPU 温度,伴随继续折腾大模型评测。


喜欢本文的朋友 欢迎关注、点赞、收藏 一键三连,大家的支持是写文章最大的动力

相关推荐
白日放歌须纵久1 小时前
Claude Skills 装了就等于能用吗?给 Claude Code Skills 建一套首跑验收流程
ai编程
柯南46681 小时前
【AI工程师精讲】15:DPO:不训奖励模型、不做强化学习,也能完成对齐
人工智能·ai编程
qinglongcn1 小时前
Agent 任务的一生——从用户按下发送到任务完成
agent·ai编程
小四的小六1 小时前
OpenAI 的 Agent 改完配置又改回去,两个月后才被回扫发现:给自家 Agent 补一套发现链路
aigc·openai·ai编程
Momo__1 小时前
155个App把豆包关在门外,豆包反手掏出第二把钥匙
app·agent·ai编程
znnnk2 小时前
【AI技术】Vibe Coding 实践指南:从项目规划到高效开发
人工智能·ai·ai编程·ai ide·vibe coding
孟健3 小时前
Claude Code 工程化实战:从保守问答到 3 个月 27 万行代码的全自动重构
架构·ai编程·claude
wangruofeng3 小时前
2026 Web 技术选型指南,AI 时代如何选择适合项目的技术栈
前端·aigc·ai编程
七牛云行业应用3 小时前
Codex接MCP完整教程:从官方文档服务器到HTTP、STDIO与OAuth配置(2026年10月)
人工智能·ai·agent·ai编程