“谁还需要硬件同步原语呢?” 近日,一则来自独立硬件黑客 Kamil Iskra 的技术公告让复古游戏社区和嵌入式开发者圈炸开了锅——他成功将 Linux 内核移植到了世嘉 32X 上,而这款诞生于 1994 年的附加硬件模块,竟然连最基本的原子操作指令都付之阙如。这一项目不仅是一场对极限编程的致敬,更是一次对操作系统多核同步机制的底层拷问。
32X 的“双核”困境:没有锁的锁
世嘉 32X 是当年插在 Mega Drive(世嘉 Genesis)卡带槽上的一张加速卡,内部搭载了两颗日立 SH-2 处理器,频率为 23 MHz,配备 2 MB 内存。这两颗芯片本可以并行处理 3D 图形与游戏逻辑,但为了控制成本,世嘉砍掉了两个关键功能:内存管理单元(MMU)和任何形式的硬件同步原语。所谓硬件同步原语,指的是现代处理器中诸如 test-and-set、compare-and-swap 等能在单条指令中完成“读-改-写”的原子操作——它们是多核操作系统实现互斥锁、信号量的基石。
没有这些指令,两个 SH-2 核心在访问共享内存时几乎等同于“裸奔”:一个核心刚读取变量,另一个核心可能立刻改写了它,导致数据竞争、死锁甚至系统崩溃。奇怪的是,当年世嘉用这颗芯片跑游戏并没有出大问题——因为游戏开发者通常会人为地错开两个核心的任务,或者干脆只用一颗核心渲染、另一颗跑音频,根本不需要精细的同步。但 Linux 内核却无法容忍这种松散:它必须保证调度器、文件系统和驱动在任意核心上都能安全运行。
巧妙的“软件补丁”:用中断和内存屏障硬扛
Kamil Iskra 在移植笔记中详细拆解了他的解决方案。既然硬件不支持原子指令,那就彻底禁用它——在 Linux 内核的 SMP(对称多处理)配置选项中,他选择了“no atomic ops”模式,并手工改写所有依赖原子操作的宏与内联汇编。取而代之的是一套基于不可屏蔽中断(NMI)和特殊内存屏障的软件同步协议:
- 核心间通信:当一个核心需要获取锁时,它先向另一个核心发送一个不可屏蔽中断,强制对方进入一个专门的中断处理函数,在该函数中检查并更新共享的标志位。由于中断会暂停对方的所有操作,这种“打断式协同”在功能上模拟了原子性。
- 内存顺序保证:SH-2 处理器没有标准的内存屏障指令(如 DMB/DSB),Iskra 使用了一种“读-修改-写-读回”序列,强制缓存一致性协议生效——虽然比现代硬件慢几个数量级,但逻辑正确。
这一切意味着 Linux 调度器在 32X 上运行时,每次上下文切换或互斥操作都会触发一次额外的中断,并耗费大约 40 个时钟周期完成软件同步。作为对比,现代 x86 上的原子指令仅需 1-2 个周期。但奇迹在于,这套方案真的跑起来了:一个精简后的 Linux 2.6.34 版本(基于 uClinux,无 MMU 依赖)成功在 32X 的双核上进入了用户态 shell,并可以运行简单的多线程任务。
性能与意义的双重拷问
代价当然是惨烈的。实测显示,在 32X 上运行一个简单的循环加锁测试,性能只有单核模式的 60% 左右,远低于现代 SMP 系统接近 200% 的线性提升。更糟的是,由于内存带宽瓶颈和频繁的中断干扰,实际应用如网络协议栈或文件系统 I/O 基本无法获得多核收益。Iskra 在博客中自嘲:“这更适合用来演示‘如何优雅地浪费 1994 年的稀缺计算资源’,而不是真正提升效率。”
然而,这个项目的价值远不止于速度。它从底层证明了两件事:第一,即便没有硬件同步原语,只要编程语言和内存模型足够灵活,通用操作系统也能在“裸奔”的多核上存活;第二,那些被我们视为理所当然的现代硬件特性——MMU、原子指令、缓存一致性协议——实际上都是可选项,而非必需项。对于嵌入式开发者而言,这一思路或许能启发他们在没有专用同步部件的廉价微控制器上跑起轻量级操作系统。
怀旧之外的启示
当被问及项目动机时,Kamil Iskra 的回答颇为感性:“30 年前,世嘉工程师用两条胶带和一根铁丝解决了图形加速问题;30 年后,我想用同样的精神给 Linux 内核做一次‘心脏搭桥’。” 目前,项目代码已开源在 GitHub,社区正尝试给这个特殊的“Linux on 32X”添加 SD 卡驱动和网络协议栈——虽然很可能帧率感人,但谁能拒绝在一台 1994 年的游戏机上用 SSH 登录呢?
“谁还需要硬件同步原语?答案其实很明确:所有追求性能的人都需要。但你只是想让 Linux 在 32X 上打个招呼的话——那这些原语反而是多余的。” Iskra 的这句话,或许是对该技术探索最精准的注脚。