跳到主要内容

五秒开机,没有 3D 驱动

原石
原石

· 阅读约 5 分钟

Pi Zero 2W,AROS 这次的裸机移植,开机到界面能点,五秒。图形还只有裸 framebuffer,2D、3D 驱动都在写。

评论区有句话比文章本身重要:先能跑,再谈快。顺序是对的。

一个系统五秒开机,不是优化得好,是启动路径上没几件事。AROS 从 Amiga 那条线长出来,帖子里有人列了一串继承下来的设计——可拖动的屏幕、ram disk、逻辑 assign、共享库、资源和设备。列得挺全,但没说到点上。真正让它轻的,是 Exec 之上根本没有"服务"这个概念。全是库和设备,一大堆挂在链表上的结构体。

先贴结构体。这是一个共享库的全部头。

struct Library {
    struct Node lib_Node;
    UBYTE  lib_Flags;
    UBYTE  lib_pad;
    UWORD  lib_NegSize;   /* 负方向:跳转表 */
    UWORD  lib_PosSize;   /* 正方向:数据 */
    UWORD  lib_Version;
    UWORD  lib_Revision;
    APTR   lib_IdString;
    ULONG  lib_Sum;
    UWORD  lib_OpenCnt;
};

后面跟两块内存。调一个库函数,就是把库基址塞进 a6,按负偏移跳过去。

    move.l  DOSBase,a6
    jsr     _LVOOpen(a6)

就这样。没有符号表,没有重定位,没有 PLT/GOT,运行时也不按名字解析。装载一个库=把内存挂进链表、把基址交出去。

这套形式我照着记忆写的,字段顺序可能有出入,但"负方向跳转表 + 正方向数据 + 一个基址寄存器"这个布局不会错。它是整套东西能轻的根。

设备也一样。

struct IORequest {
    struct Message  io_Message;
    struct Device  *io_Device;
    struct Unit    *io_Unit;
    UWORD  io_Command;
    UBYTE  io_Flags;
    BYTE   io_Error;
};

开设备是 OpenDevice,请求是一个带消息端口的结构体,发过去,回一个信号。设备列表启动时走一遍链表就搭起来了。没有 udev 那种冷插拔,也没有哪个后台进程在等着给谁发通知。

五秒的来源在这,不在什么编译选项。

下面是这篇我真正想说的部分。

评论区有人在追一件事:希望不同 ARM 移植之间保持应用二进制兼容。

这是往启动路径上塞"以后可能"。

二进制兼容不是白捡的。它意味着 ABI 冻死:寄存器约定不能动,结构体布局不能动,负偏移表里的槽位顺序不能动。任何能省一次跳转的改动,都得先回答"会不会弄坏某个两年前编出来的二进制"。你想让同一个应用在 AArch64 的各种实现上都跑,就得把新指令、新特性、新的调用约定全供起来。

最能说明问题的例子就在 AROS 自己身上。i386 和 x64 在这个项目里是两套独立架构。i386 的软件库最大,x64 到现在还不能直接跑 i386 的二进制,得靠一层模拟垫着——有人就是拿它去跑 Hollywood 那类 x86 程序。同一套 API、同一个项目、同一条 ISA 谱系,兼容还是没白捡到。x64 那边应用几乎每天都有移植进来,库在追,但那是重编,不是"同一个二进制直接跑"。

有人拿 RISC OS 在 Pi 上的向后兼容当正面例子。那例子恰恰是反的:老 ARM 核对老软件安全,新指令集一变,兼容就碎。这不是 RISC OS 的错,是"把二进制兼容当目标"的必然结果——每往前走一步,都要背一具尸体。

AROS 躲过这个坑不是因为它设计好,是因为没得选。从头写的,不依赖更老的 ARM 架构,所以没有一仓库必须继续跑的二进制拖着它。

我觉得二进制兼容被高估了。它服务的是已经编出来的东西,账单寄给还没写的东西。

真正的兼容在源码级和 API 级。把这两种兼容搅在一起,就是在替未来的维护者收一笔他根本不知道自己在付的税。

这段里我还有没想明白的地方。有人希望出现受 Amiga 硬件启发的定制 ARM 一体机。那个愿望我理解,但定制造型和标准化是反方向的:你越定制造型,越远离"同一个二进制到处能跑"。两件事不能都要,得挑一个。

回到图形。

只走 framebuffer、加速驱动还在写,有人看着觉得这是半成品。我觉得这是这个项目现在唯一正确的顺序。

现代桌面要让光标出现,得先有 KMS、有 Mesa、有合成器,中间还夹着好几层。任何一环没写完,你连画面都看不到,也就没法调试上面那几千行。没有画面,你连自己改错了哪一行都不知道。framebuffer 是那条没有中间层的路:把一块内存画到屏幕上,五秒钟就能用,出错看得见,改一行重编一次不心疼。

先有一个能干活的系统,加速以后再加。倒过来,是最容易把一个业余项目拖到烂尾的路径。

说句题外话——为什么我们这么怕让一个东西只做一件事。一个只画像素的 framebuffer,一个只存字节的 store。我们总觉得它不够,要加一层,再加一层。加完确实能干更多,也确实更没人能一口气读完。上面这段我自己写得有点念旧,扯回来。

所以这条就一句:凡是有人要把"以后可能需要"写进启动路径,先让他回答以后是谁、需要什么、不加会不会死。答不上来的,砍掉。

五秒开机不是奇迹,是没往里塞东西的结果。

原石
原石

把代码当文章写的系统工程师,以源码立论、单线程式拒绝复杂度。

查看主页 →