发掘我为Am29000自制计算机用机器码编写的1996年窗口操作系统
摘要
Oscar Toledo G. 讲述了他于1996-1997年间为自制Am29000计算机用机器码开发窗口操作系统的经历,以及近期重新发现并记录这一系统的历程。
暂无内容
查看缓存全文
缓存时间: 2026/08/06 14:02
# 为自制 Am29000 计算机开发一个窗口操作系统
来源:https://nanochess.org/the_am29000_computer.html
*作者:Oscar Toledo G\. 2026年7月30日*
我的模拟窗口操作系统运行在 G11V1(Am29000 自制计算机)上
很久以前,在墨西哥的神秘土地上,从 1996 年秋天到 1997 年春天,我用 32 位机器码编写了一个窗口操作系统,把它塞进一张软盘,然后在它启动时兴奋得跳了起来。
当然,我知道这是一件了不起的事,因为你无法从软盘启动 Windows 3.1 或 GEM,但我远远不知道它到底有多酷。不幸的是,这不是我能拿出来给所有人看的东西,因为它是为一台基于 Am29000 处理器的自制计算机编写的。
让我们回到我的记忆、一些笔记,以及重新发现我已遗忘之物的旅程。
## 世界上最快的处理器
如果你一直在关注我关于 transputer 的系列文章(https://nanochess.org/pascal.html),我对自己的操作系统感到非常满意,而且我已经触及了该系统的极限。与此同时,我父亲正忙于开发一个基于新型 32 位处理器的新计算机系统。嗯,在 1994 年,它对我们来说只是新东西。AMD Am29000 处理器于 1987 年问世,也被宣布为世界上最快的处理器(你看到这里的梗了吗?),它每个周期可以处理一条指令,所以确实很快。
AMD Am29050 处理器
Am29000 处理器及其后继者 Am29050 被用于多种打印机的 Postscript 语言扩展卡、Quadra Mac 的视频 NuBus 卡、街机游戏如 F-15 Strike Eagle 和 Battle of the Solar System(均由 Microprose 出品),而且我在维基百科上读到,它也在航空电子设备中用于双处理器冗余板。
这种速度基于大量的假设,比如拥有足够快的 RAM 或 ROM,或者使用非常高速的 TTL 芯片。而在 1990 年代初,这三者都仍然非常昂贵。此外,Am29k 系列的一些总线设计决策,例如分离的数据和指令总线,使得它与外设接口非常麻烦。最终,用 Am29k 做任何有用的事情都需要 DRAM 内存,而 DRAM 很慢,于是所有优势都丧失了。
然而,我父亲对静态 RAM 内存充满信心,他采购了数十片 128KB 的静态 RAM 芯片,8 位总线。表面贴装技术开始出现,其中几片是由四个 32K RAM 芯片安装在一个漂亮的微型绿色电路板上构成的。
因此,我父亲制造的第一台 Am29000 计算机以 6 MHz 运行,拥有惊人的 512 KB RAM 容量,以及 128 KB EPROM(使用两片 16 位芯片)。它有 ISA 连接器,显卡是 Cirrus Logic GD5429,还有一块标准的并行/串行/软盘控制器卡(多端口卡),并复用了一台旧 PC 的键盘芯片来连接 AT 键盘。这台计算机被命名为 G11。
这是一项团队工作,他做了一些初始编程来启动处理器,我翻译了我用于初始化 Cirrus Logic VGA 的 Z280 代码,制作了键盘解码器和磁盘例程。一旦这些稳定下来,我就继续编写监控程序,用于列出内存、保存数据,而且由于指令集足够复杂,我还集成了一个微反汇编器和一个微汇编器。这意味着我们可以使用真正的助记符来编码,尽管不能使用标签,而且移动代码完全是一大痛苦。
我们需要回到 Z280 机器来烧写 EPROM,但一切进展顺利。我撞上了黑暗的现实:我需要把*所有东西*从 Z280 机器移植到这台机器上,大约有 200 个程序,包括应用程序、工具和游戏。此外,Z280 的程序基本上已经过时,我无法复制同样的东西。
G11V2 计算机内部。我想要一张 G11V1 的照片,但 1999 年之前没有数码相机,我也不知道那台机器在哪里。所以这是一张 G11V2 计算机的照片,你可以看到四片 512KBx8 内存,而且它已经有 PCI 总线(G11V1 上没有)。
## 幕间休息
当 G11 计算机完成后,我父亲接手了它,开始研究每个处理器特殊寄存器是如何工作的,或者为什么它会有某些值,并为此编写文档。我有点惊讶,因为我相信处理器已经在数据手册中记录得很清楚了,而且我对未记录的行为不感兴趣,但不管怎样,我最终还是把他的所有笔记打出来并打印了。
我现在可以专注于 transputer 了,因为那台机器可以供我使用,而当 G11 计算机空闲时,我也会偶尔切换过去,以增强软盘操作系统。我开始编写一些游戏、一个能处理我们新的 Canon BJC-600 气泡喷墨打印机的文本编辑器,甚至还有一个 MSX 模拟器。那是我第一次看到 Gradius 运行,我沉迷其中。
我在重新创建我的 Z280 程序,而我感觉自己在倒退,因为我在 transputer 上已经有一个先进的操作系统了。与此同时,Windows 3.1 看起来更有用,它有着刺眼颜色的图标,这是从 CGA 一路继承下来的调色板的产物。
我有兴趣了解如何做一个窗口系统,但我无法弄到一本关于 Windows 3.1 编程的书。当地的图书馆里仍然堆满了大量的 Turbo-C、Turbo-Pascal、Quick-BASIC 和 QBasic 书籍。最后,我得到了一本关于 OS/2 编程的折扣书,并开始阅读。作者在介绍 OS/2 方面做得非常出色,而且很简单:打开一个窗口,在窗口中绘图,关闭窗口。
真正的发现是…… *咚咚咚* 消息管道。每个带窗口的进程都有一个消息管道,当你创建一个窗口时,你应该给它分配一种界面处理器(窗口类),这个处理器读取消息,并根据用户的交互执行操作。
用户的交互对初学者来说很简单:一条表示鼠标点击的消息,一条表示鼠标在窗口上移动的消息,然后事情开始变得复杂,出现了双击消息。你可以在窗口内创建窗口,但这些是控件,如按钮、滑块、图标、指示器、状态栏等。这些控件可以发回消息。
其美妙之处在于,你可以非常快速地开发用户界面,而且每个程序看起来都很棒(嗯,在 90 年代,我们确实不太挑剔,数字研究公司的 GEM 可以作证)。
一个 AMD 寄给我们的盒子。在研究这篇文章时,我发现我还保留着一个盒子,里面是 AMD 寄来的两本数据手册。有人于 1990 年 6 月 7 日把它丢在了邮局。
## 窗口系统
首先,我需要多任务。我觉得这令人生畏且复杂,最终我学会了如何做协作式多任务。它保存当前状态,加载新状态,然后跳到新位置。抱歉,操作系统设计师们,我没有考虑那么多。我只是想让它能工作。
其次,我需要窗口。我冥思苦想,想弄清楚如何将一个东西叠加在另一个东西之上。然后,当我阅读我父亲买的一张新 CD-ROM 中的一个个文件时,我发现了 X11R5。如果你不知道,X11 是一个网络化窗口系统,设计用于几乎任何图形终端,而且由于大学有很多不同的硬件,它应该能在所有这些硬件上工作。当然,这意味着它在处理颜色或游戏方面极其笨拙。
这里重要的是,CD 上有 X11R5 的完整源代码。当然,我找不到它的用途,因为我的电脑没有网络,也没有 C 编译器,但有一个源代码文件让我深受启发:XRegion.c。
该算法的基本概念是:每个窗口都有一个矩形显示列表。如果一个窗口没有被重叠,那么矩形覆盖整个窗口。如果一个窗口被重叠了,就剪掉被重叠的矩形,并继续剪掉每一个重叠。这意味着,如果一个矩形在中间被剪开,它会创建四个矩形,而在显示信息时,你应该尝试在这四个矩形中绘制。因此,被大量裁剪的窗口绘制得很慢。顺便说一下,苹果有一个技巧来解决这个问题,它使用裁剪位图,但这是一项专利技术。
第三,我根据从 OS/2 书中学到的东西,在笔记本上创建了一个极其简化的 API 规范,这让编码容易了很多。虽然 OS/2(和 Windows 3.1)使用带有画笔类型和填充图案的图形上下文来绘图,但我认为这太麻烦了,而我直接在窗口中按像素单位绘图。
我的窗口系统最初几个函数的规范。我的窗口系统最初几个函数的规范。
调试器(Depurador)的用户界面设计 调试器(Depurador)的用户界面设计
有了这三块拼图,我从 1996 年秋天到 1997 年春天开始用机器码编写我的窗口系统,那时我 18 岁,而且我真的从没想过我的微汇编器会对我不利。它很容易使用,我可以非常快地编写例程,然后我的微反汇编器向我展示的正是我写的东西。我只是需要在纸上记下一些指针,但不幸的是,这并不完全是源代码。
这篇文章之所以可能,是因为我有两张 1997 年早期机器码版本的磁盘。我知道我有一些好东西,即使很原始,因为没有其他窗口操作系统能从软盘启动,但如果没有 Am29000 计算机,别人是不可能体验它的。那么,让我们编写一个模拟器吧!
## 1997 年的磁盘
可启动磁盘的主目录如下所示:
可启动磁盘的目录。可启动磁盘的目录。ESPEJO.IMG 是转储文件,文件夹有恢复日期。
两张容量为 1.44MB 的软盘,里面装着我的窗口操作系统和程序。两张容量为 1.44MB 的软盘,里面装着我的窗口操作系统和程序。
我的第一个任务是编写一个 Am29000 模拟器。我手头某处有用户手册,但我没有花时间去找它,而是花时间在 Google 上搜索,却什么都找不到。我很惊讶,因为几年前它还能找到。不管怎样,我去了 archive.org (http://archive.org/),在输入芯片型号后很轻松地得到了它。
我认为关系-断言指令是 Am29k 系列处理器中最大的硅片空间浪费。我猜想设计者有一个使用场景,因为军方需要 Ada 语言,但这只是我的猜测。
另一个奇怪的想法是比较运算符的结果值:一个布尔值,为 0 和 0x80000000,而条件跳转只检查高位来决定是否跳转,所以你需要右移 31 位才能得到 C 语言的布尔值。
加法和减法指令也带有额外的包袱。在这种情况下,如果结果超过无符号值或有符号值,处理器会生成一个陷阱。这意味着有
ADD,
还有
ADDU,以及
ADDS。如果我们考虑进位标志,它又会增加 3 条指令:
ADDC、
ADDCU 和
ADDCS。
减法指令有一组操作数颠倒的版本,所以我们有
SUB、
SUBU、
SUBS、
SUBC、
SUBCU、
SUBCS,但还有
SUBR、
SUBRU、
SUBRS、
SUBRC、
SUBRCU、
SUBRCS。也许我不够聪明,看不出如何使用
SUBRCU。
## 一切都在哪里?
我曾经以为我能记住一切,但显然我的大脑不同意。我有这两张软盘镜像,但它加载到哪里去了?幸运的是,我放了一条启动消息,写着“Arrancando”,而指向这条消息的代码是这两条指令:
``
CONST gr68,0x0180
CONSTH gr68,0xbfff
``
这意味着启动数据应该加载到 0xbfff0000。这并不意味着我有那么多内存(RAM 从 0x80000000 开始),它只是利用了 RAM 解码器的镜像特性,总是将启动代码放在 RAM 的最高 64KB 中。我还可以从指令的字节顺序看出,所有代码都是大端序。Am29000 处理器允许通过配置寄存器中的一个位来选择端序。
在模拟器开发过程中,我开始用磁盘镜像运行它。令我沮丧的是,我发现我制作了一个不错的启动画面,但它需要我手头没有的 ROM 例程(处理方框绘制、图标显示、磁盘读取以及处理器寄存器的初步设置),所以我打了一个补丁跳过这些,加载器一直运行完成,然后跳到了 0x80000000 的空虚中,因为什么都没有加载。
我做了一个幻影 ROM,它使用模拟器中编码的一条特殊指令读取磁盘,并在未处理的服务上返回。
不完整的模拟器表现得非常好,它加载了我窗口系统的初始代码,然后在一个未处理的指令 0xe2 处停了下来。我很困惑,因为在 Am29000 用户手册 (https://archive.org/details/bitsavers_amdAm29000ual_16568459) 中没有显示任何指令,直到我拿到了 Am29050 手册 (https://archive.org/details/bitsavers_amdAm29000nual_30055159)。我真傻!它是
MULTIPLU 指令。
Am29000 手册在陷阱指令的定义上已经过时了。当 AMD 创建了带有浮点支持和硬件乘法指令的 Am29050 时,他们重新定义了陷阱指令集。例如,操作码 0xe4 在 Am29000 用户手册中是
CVINTF,但在 Am29050 手册中是
CONVERT,而且 AMD 提供了一个数学仿真库,允许 Am29000 处理器使用这些新操作码运行 Am29050 软件。我认为编写一个 Am29050 仿真器是过度的(也许以后再说),所以我限制自己只做 Am29000,因为我觉得他们用陷阱和 32 条连续的
MUL 指令(一路移位)来实现乘法指令很有趣。
顺便说一下,我们的 Am29050 处理器是直接从 AMD 的墨西哥代表处获得的样品。我在笔记本上记下了日期,1997 年 4 月 8 日。不幸的是,同一年 AMD 陷入困境,放弃了 AM29k 产品线,转而制造 K5 处理器,就这样与 Intel 的处理器战争开始了。
在对我关于向量表的解释产生了一点分歧之后,我终于让
MULTIPLU 指令触发陷阱以进行仿真,然后我跑去实现
MUL 指令,却陷入了乘法陷阱的无限循环。原来,要从陷阱返回,你必须重建两个 PC 寄存器!!!只更新其中一个会让我代码再次运行自己,而且陷阱还设置了错误的返回地址。
我必须修正这些错误,然后再次运行模拟器时,它直接退出乘法例程,进入了一个溢出陷阱 *小丑脸*。确实有一个溢出,因为它在读取内存,而我不记得机器启动时那些内存里有什么。我放了一些猜测的值,直到它到达
IRET 指令,这意味着它可以从被仿真的指令返回。
它工作了,然后在未实现的
DIV 指令处停了下来。我当然对指令位处理有疑虑,但我按照手册中描述的算法实现了它。
我到达了原始代码中一个点,它开始从内存中读取字节,这需要再实现一个位(用于设置内部字节指针),以及
EXBYTE、
EXHW 和
EXHWS 指令来提取字节、无符号半字和有符号半字。
很快
INBYTE 和
INHW 指令也出现了,并且也被实现了。
## 这是什么?
出于某种原因,它在系统初始化深处读取了一个端口 0x90,更糟糕的是,它等待位 7 变为开启……我不知道它是什么!我只是在那个端口放了一个 0x80 字节。顺便说一下,读取端口使用特殊的
LOAD control 0x41。内存的正常加载是 0x04 和 0x14(用于字节或半字)
相似文章
我是如何开发Am29000的C编译器和网络浏览器的
Oscar Toledo G分享了他于1998年至1999年间为Am29000自制电脑开发C编译器和网络浏览器的经历,讨论了技术挑战和当时的技术背景。
Os8088:适用于 IBM XT、286、386 的强大的类 Mac 操作系统
os8088 是一个面向 Intel 8086/8088 PC 的业余爱好者级 Macintosh System 1 风格 GUI 操作系统,完全用实模式汇编编写,具有抢占式多任务能力,仅占用 256K RAM。该项目已完成,源代码可供获取。
微软开源“迄今为止发现的最早的DOS源代码”
微软发布了已知最早的DOS源代码,包括86-DOS 1.00内核和实用程序,以及开发者文档。
在现代 glibc 上剖析 House of Apple 2
本文剖析了在现代 glibc 版本上的 House of Apple 2 利用技术,提供了交互式 GDB 演练,并通过沙箱环境分析了其可行性。
在浏览器中启动 Apple Copland D11E4
本文描述了如何使用 DingusPPC 模拟器在浏览器中启动苹果公司失败的 Copland 操作系统,包括运行说明和必要补丁的链接。