在Atari Jaguar上运行Linux。没错,真的。

Hacker News Top 新闻

摘要

一位开发者成功地将Linux移植到了Atari Jaguar游戏主机上,利用了uClinux和m68k架构来应对系统内存有限且缺乏MMU的问题。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/07/06 20:05

# 在Atari Jaguar上运行Linux。没错,真的。 来源:https://cakehonolulu.github.io/linux-for-jaguar/ ## 这个Atari Jaguar到底是什么鬼? 1993年11月在北美发售的Atari Jaguar,曾以其(备受争议的)64位纯正性能,信誓旦旦地要成为街区最靓的仔。 Atari Jaguar*Atari Jaguar主机* 这台主机最终在商业上惨败,即使后来发布了CD配件Jaguar CD也不例外——这款配件在试图与索尼PlayStation和世嘉土星竞争时,销量甚至更少。 Atari Jaguar CD*Atari Jaguar CD配件* ## 为何偏偏是Linux这个鬼东西? 有趣的是,时至今日,Linux仍然保留着针对68000系列处理器的架构代码。68040、68030、68010……甚至是最原始的68000处理器。它们都整齐地归类在 `arch/m68k/` 目录下。 复习一下:摩托罗拉68000是一款混合16/32位能力的CISC处理器(通常被描述为内部32位,因为寄存器宽度是32位;数据总线16位,所以每次传输2字节)。 它有24位地址总线,2的24次方,大约最多可寻址16MB内存。 它于1979年发布,与当时即将上市的16位CPU竞争。 M68000*摩托罗拉68000* 总体而言,它在商业上获得了巨大成功;最终被集成到许多流行的商业硬件中。最著名的包括初代Macintosh(和Apple Lisa)、Commodore Amiga系列电脑(不同代际使用68000的不同版本)、世嘉Genesis/Megadrive、Neo-Geo AES、Plexus工作站……以及Jaguar。 现在我们来谈谈房间里的大象:为什么是Linux。 嗯,很简单;"因为我们可以"(*大概*)。 ## jmp _linux? 做一个基于68000的新Linux移植应该很容易……对吧?嗯……那你可有的忙了。 你可能知道(也可能不知道),有一种普遍看法认为Linux需要MMU才能运行(你知道的,如果我们想在没有整天头痛的情况下运行软件,能够使用虚拟内存是件好事)。 从技术上讲,你*某种程度上*是对的,但还有*uClinux*;它恰好让这变得可行。在某个时间点,它从Linux的下游分支变成了内核的一部分;幸运的是,它已经被内置到 `m68k` 中(以及平坦内存模型和你能想到的无MMU系统的其他需求)。 那么,我们在Linux的 `menuconfig` (https://github.com/cakehonolulu/linux_jag/commit/7b769c4bfa748f689463ba4d53eecc811c7d42f2) 中启用所有必需的配置标志(基本上告诉它不要使用MMU,而要使用平坦内存模型 (https://en.wikipedia.org/wiki/Flat_memory_model)),编译之后它应该能运行,对吧?嗯,是的……但不是。 ## 实际上jmp _linux涉及什么 Jaguar有2兆字节的RAM(映射在0x000000处),(最多)6兆字节的ROM(基本上是卡带,映射在0x80000处),以及两个自定义IC(Tom & Jerry,一个"GPU"和一个DSP),它们也是内存映射的。 我们面临的主要障碍是内存占用;虽然确实有不少内存,但毕竟不是无限的(我们说的是兆字节,而不是*吉字节*)。 我们基本上需要找到优化RAM使用的方法。我们可以先做简单的事:从内核中移除功能,禁用调试……这些都是常规操作;但我们可能仍然会遇到一个问题:无法将内核加载到RAM而不导致OOM(更别说我们此时甚至还没考虑initramfs,它也很庞大)。 幸运的是,Linux足够聪明,允许我们将内核"分割"到两个独立的内存区域。我们可以利用这一点,将其只读部分(比如 `.rodata` 或 `.text`)存储在卡带(ROM)上,而动态部分(`.data` 或 `.bss`)存储在RAM中(想想 `XIP`:就地执行 (https://en.wikipedia.org/wiki/Execute_in_place))。 幸运的是,我们只需要告诉Linux RAM和ROM的位置,它就会处理重定位。 酷,我们现在可以把Linux塞进Jaguar,它应该能执行了……对吧?嗯,当然,你可以尝试指定内核的基址(记住,`non-PIC` 代码),将它加载到那里,然后直接执行 `jmp $80000` ……但我们如何知道幕后发生了什么? ## Linux启动需要什么 在启动Linux的上下文中,我们需要两样东西(至少在进行移植时是这样): - 某种输出,以便我们看到内核消息 - 某种给系统提供时钟的方式(即一个定时器) 第一个要求通常涉及"好老旧的"UART。Jaguar的DSP(Jerry)有TXD和RXD引脚(至少在启动Linux时),可以被重新用作串行输出(如果我们忽略与声音相关的任何事情)。编写一个简单的控制台驱动程序,通过位碰撞这些引脚,就足以开始看到一些 `earlyprintk` 消息。 第二个要求有点棘手。但我们可以使用Jerry IC的任意一个定时器;商业游戏和软件将它们用于声音相关任务,但我们将使用它们来让Linux校准并设置调度程序以及同样依赖PIT的系统。它们既能在Jerry上触发中断,也能在68000上触发中断,这很有用。我们可以基本上覆盖Linux中68000的板级初始化,并将其指定为从现在起Linux将使用的PIT。 我们编译,尝试运行…… …… 没反应? 好吧……这很奇怪;我们应该能得到点东西?我是说,一切看起来都正确……内核配置:没问题……地址:没问题……编译器……编译器? 结果发现,Ubuntu仓库中捆绑的 `m68k-linux-` 交叉编译器会发出未对齐的内存访问(即使在传递 `-68000` 参数后),所以它实际上会崩溃(因为基础68000没有实现未对齐内存访问的处理)。 使用一个专门为68000从源代码构建的、目标为 `m68k-elf-` 的编译器似乎能解决这个问题,但这绝对是一件奇怪的事。 这也指出了另一个问题:由于我们的ROM没有映射到0x00000而是0x80000,68000会尝试跳转到VBR(0x0),而那里没有处理程序,所以它基本上会自毁。这需要在Jaguar平台特定的Linux代码中单独进行一次 `memcpy`,将所谓的 `vectors` 复制到RAM的基址。 我还不得不从源代码编译 `gdb`,以使其能与MAME的 `gdbtsub` 配合工作。不知为何,`gdb-multiarch` 错误地编排了存根,导致它在整个地址空间中乱跳;这很有趣,直到我决定用 `m68k` 特定构建进行测试。 酷,现在我们终于能得到*一些*输出了: ``` Linux version 7.2.0-rc1+ (cakehonolulu@jaguar) (m68k-elf-gcc (GCC) 16.1.0, GNU ld (GNU Binutils) 2.46.1) #38 Sun Jul 5 11:56:37 CEST 2026 printk: legacy bootconsole [early_jerry0] enabled uClinux with CPU MC68000 Flat model support (C) 1998,1999 Kenneth Albanowski, D. Jeff Dionne Zone ranges: DMA [mem 0x0000000000000000-0x00000000001fffff] Normal empty Movable zone start for each node Early memory node ranges node 0: [mem 0x0000000000000000-0x00000000001fffff] Initmem setup node 0 [mem 0x0000000000000000-0x00000000001fffff] On node 0, zone DMA: 512 pages in unavailable ranges printk: log buffer data + meta data: 4096 + 12800 = 16896 bytes Dentry cache hash table entries: 1024 (order: 0, 4096 bytes, linear) Inode-cache hash table entries: 1024 (order: 0, 4096 bytes, linear) Built 1 zonelists, mobility grouping off. Total pages: 512 mem auto-init: stack:all(zero), heap alloc:off, heap free:off SLUB: HWalign=16, Order=0-1, MinObjects=0, CPUs=1, Nodes=1 NR_IRQS: 32 clocksource: jiffies: mask: 0xffffffff max_cycles: 0xffffffff, max_idle_ns: 19112604462750000 ns Calibrating delay loop... 1.04 BogoMIPS (lpj=5248) pid_max: default: 32768 minimum: 301 Mount-cache hash table entries: 1024 (order: 0, 4096 bytes, linear) Mountpoint-cache hash table entries: 1024 (order: 0, 4096 bytes, linear) ``` 现在它尝试寻找并执行一个 `init` 进程……而我们没有……所以它又崩溃了。 ## 用户空间 我们面临的众多限制之一是,由于不能使用ELF文件(我们使用FLAT二进制文件,主要是因为 `nommu` 情况),所以这使我们的工具链设置变得有点复杂。 我无法找到独立编译 `elf2flt`(基本上将一种格式转换为另一种格式的工具)的方法;它基本上需要你提供一些 `.a` 和一些 `.h` 文件(与 `libiberty` 等相关),而这些文件似乎已经从 `binutils` + `gcc` 针对68000的默认配置中消失了……但奇怪的是,`buildroot` 似乎最近有一次更新,他们为我们解决了基础68000的所有问题。 Buildroot 向 `linuxmd` (https://github.com/LinuxMD/) 项目致敬;启用 m68k nommu 的补丁 (https://github.com/buildroot/buildroot/commit/e5c7b43494c4d6aa57c6ef314bd4e636aacc14fc) 的日期是2026年5月(所以在我写这篇博客时,才过去两个月!) 所以我基本上让它帮我做了这件事,并决定使用 `busybox`,它是少数(如果不是唯一)支持 `nommu` 目标的此类项目之一。 有一些问题(主要是因为我从未处理过平坦二进制文件),但再次强调,花几分钟调试和记录日志就能解决。我遇到的最明显的问题是在尝试执行init时到处都是OOM。我基本上只做了这个: ``` ❯ cat ~/jagfs/rootfs/init #!/bin/busybox sh /bin/busybox sh ❯ ``` 通常你会让 `busybox` 填充其余实用程序(它只是指向自身的符号链接,具有不同的名称;他们称之为 `applet` 设计,其中基于 `argv[1]` 知道要派发哪个 applet:`echo`、`ls`、`cat`……),但由于某种原因,这导致了内核OOM;所以现在只有 `sh`(`shebang` 部分也会被执行,但似乎不像 `--install` 那样占用所有内存)。 我们现在有了一个rootfs,将其打包为 `cpio` 并嵌入到 `vmlinux` 镜像本身中……瞧! 作为给读者留下的练习,关于FLAT二进制文件,有几个有趣的点你基本上可以在Linux中查看(例如,有不同的执行处理方式:将整个二进制文件加载到RAM,进行GOT/PLT重定位……所有这些都可以用 `flthdr` 检查)。 ``` ❯ flthdr ~/jagfs/rootfs/bin/busybox /home/cakehonolulu/jagfs/rootfs/bin/busybox Magic: bFLT Rev: 4 Build Date: Thu Jul 2 03:08:09 2026 Entry: 0x44 Data Start: 0x1e0a0 Data End: 0x25808 BSS End: 0x2a1c0 Stack Size: 0x10000 Reloc Start: 0x25808 Reloc Count: 0x100e Flags: 0x1 ( Load-to-Ram ) ``` 相当巧妙的东西…… 在工具链的 `libc`(即 `uClibc`)上,我们还必须进行一项调整,即修改malloc策略为 `malloc-simple`;否则更快更智能分配相关联的元数据会很快吃掉我们的内存,导致我们再次panic。 这个内存分配更改是在 `buildroot` 的 `uClibC` menuconfig 中完成的。 M68000*make uclibc-menuconfig* 我还花了一些时间实现了一个简单的Tom(GPU但不是GPU,更像是字/对象处理器和blitter)控制台驱动程序,这样也可以在真实硬件上进行测试。 别忘了,如果你在真实硬件上运行,使用 `objcopy` 转换为二进制的 `vmlinux` 需要在0x80000处有一个固定偏移量,以便可以添加Jaguar卡带头(它跳转到你指定的任何位置)。所以你需要在 `CONFIG_ROM_START`、`CONFIG_ROM_LENGTH`(由于基址增加,会稍微变小)、`CONFIG_ROMVEC` 和 `CONFIG_ROMSTART` 中添加8KB的偏移量。 复制设置的基本思路是:编译目标为68000的buildroot(修改了 `malloc` 策略)。你可以要求它用自定义的 `.config` 文件路径(在文章末尾提供)构建busybox,这样它会为你生成FLAT二进制文件。然后你填充一个简单的initramfs文件结构(网上有资源说明你需要做什么,通常包括创建 `/{dev,bin,usr,sbin...}` 目录,并在 `/dev/` 中添加一些 `mknode` 魔法以创建 `/dev/console/` 节点,再加上一个 `init` 脚本;该脚本也在本文末尾提供)。 然后你告诉Linux你新构建的 `initramfs.cpio` 文件在哪里,就大功告成了! 就是这样! 修改后的Linux仓库地址为:https://github.com/cakehonolulu/linux_jag boot 与 `linuxmd` 不同,我们没有特殊的卡带映射器(`SSF2`,它为MegaDrive提供了额外的4MB RAM),所以我无法真正证明(从内存角度)添加任何引导加载程序(例如 `u-boot`)的合理性;所以我基本上采用了我在博客中记录的方法(基本上执行 `jmp _linux`)。 ## 杂项配置文件,供有兴趣尝试的人使用 Busybox 配置 (https://cakehonolulu.github.io/misc/linux_jaguar/busybox.config) Init 脚本 (https://cakehonolulu.github.io/misc/linux_jaguar/init)

相似文章

Sega MegaDrive上的Linux

Hacker News Top

一个项目将Linux移植到Sega MegaDrive游戏机,使用EverDrive卡带,提供了构建和启动指南,以及一个用于模拟的自定义QEMU分支。

Linux/M68k

Hacker News Top

Linux/m68k 是 Linux 操作系统的移植版本,用于在采用摩托罗拉 68020/030/040/060 微处理器的系统上运行。本页面提供有关该项目、发行版和链接的信息。

用 x86_64 汇编写成的 Linux 桌面

Lobsters Hottest

一位开发者借助 Claude Code,用纯 x86_64 汇编重建了完整的 Linux 桌面栈——从 shell、终端、窗口管理器到各种工具,实现微秒级启动,并延长数小时续航。

单片软盘上的嵌入式Linux

Hacker News Top

FLOPPINUX 是一个完整的 Linux 发行版,可安装在一张 1.44MB 软盘上,支持 Intel 486DX 和 20MB RAM 等最低配置硬件。启动后进入终端,具有持久化存储和基本工具。

Plexus P/20 模拟器

Hacker News Top

一款全新的开源 WebAssembly 模拟器,重现 1980 年代 Plexus P/20 Unix 服务器,让用户可在浏览器中运行 SystemV Unix。