M4 Mac mini 跑上了主线 Linux:一颗会失忆的 CPU 和它两年移植长跑

一台 M4 Mac mini,正在运行主线 Linux 内核。这件事在 2026 年 10 月成为现实,而且不靠某个下游魔改分支——修复代码直接进入了 Linus Torvalds 的主仓库。Hacker News 上,这篇题为《The Forgetful CPU》的博客两天拿到 150 分和 65 条评论,同期登上 Lobste.rs 热榜。作者 Yureka Lilian 也算不上 Asahi Linux 核心团队成员,只是一个在 2024 年 11 月赌了一把 M4 生态的独立开发者。

M4 Mac mini 上运行的 NixOS,hyfetch 显示内核版本 6.18.5

起点是一笔赌博

2024 年 11 月,Yureka 买下 M4 Mac mini,赌注是:这代芯片会延续 M1 到 M3 的路线,Asahi Linux 团队能快速跟进。机器在桌上放了几个月,坏消息陆续浮出水面。

M4 是第一代强制启用 SPTM(Secure Page Table Monitor)的 Apple Silicon。这个组件的职责是对 XNU 内核(macOS 的内核)漏洞做加固,但它同时改变了启动流程的规则。此前几代芯片上,Linux 移植的核心方法论是把 macOS 关进 m1n1(Asahi 团队的开源引导程序兼虚拟机管理器)里跑,用 hypervisor 捕获 macOS 驱动与硬件之间的 MMIO 交互,从这些痕迹反推寄存器布局。SPTM 让这条路线失灵:让新版 macOS 在 m1n1 里运行需要大改 m1n1 本身,工作量远超一个新手能拿下的范围。

Asahi 核心开发者 Sven Peter 在 2025 年 4 月的判断:M4 支持会相当痛苦,反推新硬件的主路线受阻

Asahi Linux 核心开发者 Sven Peter 在 2025 年 4 月的帖子里写了这个困境:配置 macho boot object 时,系统会进入 SPTM 以 GL2 特权级运行的环境,反推硬件的主路线既跑不了 Linux,也跑不了被 hypervisor 监视的 XNU。

串口、一个字母 a 和逐段二分

官方路线走不通,Yureka 选了笨办法:关闭启动安全策略,把 m1n1 作为自定义引导对象装进 macOS 恢复模式,接上串口线看日志。

最初连 m1n1 自己都起不来。GXF(Apple 的受保护异常级扩展,Sven 曾专门撰文解释过它在 M1 上的机制)在裸机启动模式下被锁死禁用,m1n1 一初始化它就崩溃;把 GXF 初始化改成条件跳过后,第二个问题接踵而至:写 RVBAR(每个 CPU 核心的复位向量基址寄存器,告诉核心从哪里开始执行代码)也会触发崩溃——后来发现 M4 上这些寄存器出厂就写好了正确值,跳过写入即可。

2025 年底cc Chaos Communication Congress 上,Yureka 重新捡起这个项目,手工写了一个只含 CPU 核心和中断控制器的最小设备树,通过 m1n1 加载 Linux 内核,参数带 earlycon。屏幕上什么都没有。

拿不到内核输出,只能用最原始的调试手段:从 m1n1 里抄来 debug_putc 汇编例程,改成打印单个字母 a,插进内核启动最早的汇编代码里,然后对启动过程做二分。字母 a 消失的位置在 MMU 初始化——但 MMU 代码本身没有错,问题在它之后:UART 串口是内存映射 I/O,MMU 一开,所有内存访问走虚拟地址,而 m1n1 建的页表把 MMIO 空间映射在同样的虚拟地址上,Linux 的初始页表没有这层 1:1 映射,于是 MMU 启用后串口寄存器变成了未映射空间。给初始页表补上映射,字母 a 一路推进到中断控制器初始化才再次消失。这次锁定的祸首是写入 SYS_IMP_APL_VM_TMR_FIQ_ENA_EL2(一个与虚拟化定时器相关的苹果私有寄存器)就触发崩溃,注释掉这行写入后,内核启动进了 shell。这个寄存器后来在新版 iBoot 里已解锁,注释不再必要。

Linux 启动后 htop 显示全部核心在线

后续的 secondary core(副核心)启动靠补上 m1n1 里缺失的 smp_start_offset 硬编码偏移解决,M1 到 M3 用的偏移值在 M4 上依然有效。内核能跑了,第二个谜团才开始。

一颗会失忆的 CPU

名字里的「forgetful」指的是这颗芯片最违反规范的行为:执行 WFI(Wait For Interrupt,ARM 架构的等待中断指令)之后,CPU 的 x0 到 x31 全部 31 个通用寄存器内容被清零。

这不是 M4 新引入的毛病。M1 到 M3 就有:一个叫 chicken bit(厂商用来开关某项 CPU 特性的寄存器位)的 ARM64_REG_CYC_OVRD_ok2pwrdn_force_mask 控制着 WFI 的行为模式,触发时寄存器会被清空。macOS 的 XNU 内核早就知道这件事——它在每次 WFI 前把寄存器压栈、醒来后恢复。m1n1 在 M1-M3 上会关掉这个行为,让 CPU 表现得像一颗标准 arm64 处理器;Asahi 内核随后又会刻意把它打开,让核心能进更深的睡眠状态省电,也让单个核心在其余核心深睡时能boost到更高频率。

M4 改变了规则:这个 chicken bit 被锁死或移除,默认行为直接违反 ARM 架构规范。规范原文写明:「如果系统配置允许 WFI 指令完成,则 WFI 指令不得造成架构状态的丢失。」芯片不提供关闭开关,硬件行为摆在那里,软件只能绕。

2026 年 4 月,Yureka 用最粗暴的方式让全部核心跑了起来:把内核里所有 WFI 和 WFIT(带超时的等待中断)指令替换成 NOP 空操作。CPU 不再睡下去,自然也不丢状态——代价是省电特性全废。真正的工作随后开始:先让内核「能跑」,再解决「怎么在主线内核里体面地解决」这个问题。

从 NOP 到主线:一个 erratum 的诞生

Linux 内核处理这类硬件缺陷有成熟机制:erratum framework,在启动早期检测到受影响的 CPU 型号后,把特定指令在内存里动态替换成安全序列。Yureka 最初的思路是把 WFI 清寄存器注册成一个标准 erratum,6 月发出的补丁就是这么写的:给 arm64 增加 APPLE_ERRATUM_WFI_STATE 配置项,用 ALTERNATIVE 宏在检测到受影响 CPU 时把 wfi/wfit 汇编替换成 nop。

这个方案在邮件列表里被 arm64 维护者 Will Deacon 否了一半。问题出在虚拟机:WFI 在虚拟化场景下会被 hypervisor 捕获,用来调度不同 guest。如果把 erratum 逻辑做成无条件检测,运行在 macOS hypervisor 下的虚拟机也会触发替换,而嵌套虚拟化场景下判断「当前是否在 VM 里」非常复杂。讨论的结果是反转责任分配:内核提供 idle= 启动参数支持禁用 WFI 空闲,由 m1n1 在引导时判断机型,给已知 WFI 有问题的裸机 M4 附加参数。

最终进主线的两个提交都能在 Torvalds 的仓库里查到:d97afae6f16a「arch: arm64: add early_param idle=<wfi|yield|nop>」,作者 Yureka Lilian,2026 年 8 月 4 日提交,8 月 6 日由 Will Deacon 签入;baaa9b126b87「arm64: Add override for WFxT」。配合 m1n1 侧的 PR #672「kboot: disable wfi/wfit if wfi loses state」,最新版 m1n1 加主线内核已经能在 M4 上全核心原生启动。

这意味着什么

对普通用户,短期内的答案依然务实:想在 M4 Mac mini 上日常使用 Linux,体验仍不适合主力机——外设反推(摄像头、显示控制器、GPU)在稳步推进但远未完成,Yureka 自己的工作也集中在让启动链路正确这个地基上。但有两件事已经变了:其一,M4/M4 Pro/Max 甚至 M5 上的 WFI 问题在主线内核有了正式解法,后续所有发行版自动受益,不再依赖 Asahi 的下游分支;其二,一次由社区开发者独立完成的移植工作绕开了「必须先反推全部硬件」的老路,把「引导链路正确性」这块地基先打进了主线。

Yureka 把工作直接提交给各自的上游项目,捐赠入口放在 LiberaPay 和 GitHub Sponsors,同时呼吁给 Asahi Open Collective 捐款。文章里有一句不带脏字的抱怨:某些拿到大额融资的项目,对自己如何从上游社区的进展中获益,透明度可以更高。

说明