Mac OS X 的 64 位历程与 UNIX 文化

64 位支持是 Mac OS X Snow Leopard 操作系统最重要的新特性之一。这往往让人以为 Apple 是初次提供 64 位支持。其实 OS X 的 64 位支持从 Tiger 开始就默默存在了。Apple 一贯的风格是做得多说的少 —— 能把 x86 架构的 OS X 研发保密达 5 年之久,它最喜欢在发布会上宣布的消息是『available now』。

64 位支持并非从 Tiger 开始就一步到位。否则的话,Leopard 和 Snow Leopard 有固步不前之嫌,Apple 对 64 位支持保持 4 年的市场宣传沉默也显得毫无必要。下面这幅图说明了 OS X 系统 64 位支持的发展路径。Tiger 仅仅支持没有 GUI 的 64 位应用,Leopard 仅仅在 user space 提供 Cocoa 的 64 位支持,没有 64 位 kernel。而且这两者都仅仅提供系统级的支持,附带的应用仍然都是 32 位。Snow Leopard 的 64 位达到成熟,附带的应用几乎全部为 64 位,并且提供 64 位 kernel(尽管不是缺省设置)。

有意思的是,OS X 系统在 64 位上循序渐进的发展策略,正好有意无意的遵循了 UNIX 的设计文化关于 GUI 的设计的原则。《The Art of UNIX Programming》在第 11.6 章讨论 UI 设计之前就开宗明义地宣布『UNIX 没有发展出本土的 GUI 设计模式』。确实,UNIX 在这点上保持谦逊,向 MacOS(这里指classic)、NeXTStep和其它 GUI 的先行者借鉴了许多经验(以及后来的 OS X 与 UNIX 的相互借鉴)。不过随后在第 11.6.8 节指出了 UNIX 在 GUI 方面还是发展出了一个最基本的准则 —— 『界面和引擎的分离』。UNIX 提倡把尽可能多的代码放到没有可见 GUI 的进程中(即引擎进程),提供 GUI 的进程和引擎进程通过 Application Protocol 通信。引擎进程一般不与用户直接交互,但是很多引擎程序也提供让高级用户直接调用的接口(比如 CLI 命令)。这样做既符合 UNIX 通过协作的多个进程来降低整体复杂度的原则,还让程序能够同时满足初级用户和高级用户的不同使用习惯。甚至程序本身的自动化测试也更为方便。现实中很多程序都在实践这一原则,比如我们每天都用的 P4V,它本身的 GUI 并不实现和 Perforce server 的通信,而是调用 Perforce 的命令行客户端程序。这样即满足了初级用户,也让高级用户了解 GUI 操作对应的都是哪些命令,方便了 trouble shooting 和重复操作的自动化。设计良好的引擎进程还允许第三方开发更好的或者满足特殊用户需求的 GUI。

如果一个系统无法在一个 release 中实现完全的 64 位支持,那么应该如何决定模块 64 位化的先后顺序呢?以上面的 UNIX 原则为依据来决定的话,就正好是 OS X 系统对 64 位支持的实现顺序(尽管也许是无意的,但是我相信 Apple 内部的 UNIX 大牛们有此特意的考虑)。首先提供对 GUI-less 进程的 64 位支持。这样在界面引擎分离的系统中,引擎部分可以首先 64 位化。其实 64 位化带来的性能和可扩展性(scalability)的改进对于界面进程并没有太大影响,而急需此类提升的引擎进程正好可以先一步利用系统的优势。

到了今天的全 64 位 Snow Leopard 系统,除了安全和整体复杂度的考虑,界面引擎分离的原则是否在 32/64 位架构方面还有作用?仍然有。比如,Dropbox 是一个保证文件在多台机器上同步的程序。它由一个提供同步功能的进程和一个 Finder plug-in 构成。Finder 由 Cocoa 重写并且升级为 64 位之后,plug-in 停止工作,所以无法使用 Finder 右键菜单中的 Dropbox 功能,但是提供同步功能的进程仍然可以工作。Dropbox 的引擎界面分离让失败降低到最小的程度。Safari 升级到 64 位之后,很多第三方的 plug-in 仍然没有来得及跟进。但是这些 plug-in 的重要性让 Apple 没有办法说服用户以抛弃它们为代价享用 64 位。所以 Safari 采取了一种特殊的界面引擎分离。把 32 位的 plug-in 放置在单独的、32 位的虚拟 Safari hosting 环境的进程中运行。这些 32 位虚拟 Safari hosting 进程和 64 位的 Safari 通过 application protocol 通信 —— 称之为 out-of-process plug-in。即便如 Flash 视频这样的应用,以今天的硬件性能来通过 IPC 在两个进程间传输数据也无损用户体验。这样做在满足了 32/64 位架构兼容的同时也提供了进程协作的一贯好处 —— 安全、稳定(单个 plug-in 崩溃不会影响整个浏览器)和易于维护(容易定位 bug)。即便今后有 64 位 plug-in 出现,这些好处仍然会促使 Safari 和其它应用采用 out-of-process plug-in 技术。

Snow Leopard 引入 64 位 kernel 也让另一个利用普通进程技术的优势体现出来。TOR(洋葱路由)基于 SOCKS Proxy 技术,所以它的 client 端实现是一个监听特定端口(一般是 9050)并提供 SOCKS 服务的普通进程。Cisco VPN 基于虚拟以太网技术,所以它的 client 端实现是一个虚拟的网卡驱动,运行在 kernel space。当 kernel 升级为 64 位而 Cisco VPN client 无法跟进的时候,用户只好放弃使用其一 —— 退回 32 位 kernel 或者不使用 VPN。而基于普通进程的 TOR 则不受影响。同样,基于普通进程的 SSH tunnel 技术也不受 kernel 64 位升级的影响。同样是提供加密的传输功能,基于普通进程的技术显示了灵活性。

计算机系统各个部分的发展永远呈现不同步的状态。OS X 的 64 位化过程清晰而富有趣味性的重申了这一点。软件应对这一永恒趋势的唯一手段就是把自己分割成一个个功能简单,可以独立替换,甚至在某些其它模块缺失的时候仍然可以正常工作的模块。这是 UNIX 的基本设计原则之一,利用 application protocol 进行协作的进程就是 UNIX 实践这个原则的工程传统。那些在 Tiger 上就利用了 GUI-less 64 位支持的程序、那些在 Leopard 上来不及把 GUI 升级为 Cocoa 的程序、还有 Snow Leopard 上的各种成功和失败的例子都在重申这一原则的重要性。UNIX 诞生于资源奇缺的计算环境。因此演化出『沉默是金』这样的传统(尽管今日沉默是金是因为有了其它的优势而被遵守)。但是同时也顽强的保留了多进程协作这样看似『奢侈』的传统。不能不惊叹于 UNIX 文化的预见性和适应性。

3条回应 to “Mac OS X 的 64 位历程与 UNIX 文化”

  1. fuzhou Says:

    这个问题俺基本上没什么可争论的,除了一点:俺并不倾向于夸赞UNIX的设计高明是源自其某种先验的预见性。——别忘了早期的386BSD并不被当时自命清高的UNIX社区看好。

    俺之前一直强调,任何设计的演化都有其历史的原因。俺以为UNIX的设计在如今能体现到灵活性的惟一理由是:UNIX这种设计从一开始就没有单进程的历史包袱。

    对比一下Windows吧。为什么Windows的世界里能发展出COM那样的以进程内复用为特征的技术?俺认为原因在于Windows早期脱胎于DOS那样的单进程系统。在操作系统不支持单进程的大前提下,奢谈进程间的“通信”显然已经毫无意义,尽量在一个进程里完成所有任务才是唯一合理的选择。另外,早期PC相对孱弱的性能也给了开发人员足够的理由去尽量玩弄技巧来榨取计算时间。

    等到Windows NT和Windows 95那样已经基本具备多进程特征的换代系统出现时已经为时已晚,这些已经现存的技术已经形成了体系。而任何已经成型的体系,在没有被证明彻底过时之前,是绝对不可能随便退出历史舞台的。

    而Windows和UNIX直接的另一大差异,即多线程,理由则有所不同。俺认为UNIX不使用多线程很大的理由是多进程的体系早已成型——我已经有一个工作完好的模型了,为什么还要接受另一个在当时还不成熟的技术?或许Windows fans会跳起来争辩多线程在伸缩性和效率上的优势,但不要忘记,UNIX世界可是构筑在小型机的世界的,80年代的时候效率从来不是小型机应用关心的首要问题,至于伸缩性么,东哥的文章应该能够很好地回击所谓“线程比进程伸缩性更好”的说法。

    所以总结陈词:我们可以赞赏UNIX的设计适合当前的需要,但也犯不着迷信UNIX设计者如同先知一般的洞察力。我们的PC正在经历20年前小型机甚至是大型机的应用要求——注意,是应用要求,性能上说PC的能力早已超越了——UNIX的设计正好满足了我们的需要,仅此而已。

    题外话:关于多线程和多进程

    俺现在也同意一种观点,即多线程的本质是把本应属于操作系统一部分管理任务重新转嫁给了用户层,或者换一种说法,多线程是一种比多进程更原始的原语。这里的“原始”不是说它“不先进”——俺知道这很容易触动Windows fans的敏感神经——而是说它拆解了原本“进程即任务”这个抽象,而把它还原成了更具体的计算时间和资源,进而将管理与控制的责任交给了应用开发者。

    Windows下这两者差异不明显,那是因为Windows下进程和线程的进程间的同步模型几乎没有差别,主要靠的是事件和信号量,而且它们都需要程序员来手工控制资源分配和释放。反过来,UNIX则有一个更好的选择,即UNIX domain sockets,它的读写函数直接隐含了同步操作,而且容易理解。而与Windows IPC很相似的System V IPC,我的印象是用得越来越少。

    那么为什么Windows当初会选择多线程?——俺感觉到某些UNIX死忠在等着俺将其解释为Microsoft的愚蠢——不,事实上我不知道理由。要解答这个问题,我们至少需要一个经历过20年前那个时代,并且对Windows的进程模型和其开销有足够了解的人,希望在我回家之前,我能有机会遇到这样的人物吧。

    • sipoint Says:

      如果把讨论的焦点放在UNIX用了多进程而别的OS没用,那么很容易怀疑UNIX能如此适应今天的形势也许只是机缘巧合。不过,我感慨的不是多进程这么具体的东西,而是『软件应对这一永恒趋势的唯一手段就是把自己分割成一个个功能简单,可以独立替换,甚至在某些其它模块缺失的时候仍然可以正常工作的模块。』从UNIX的文献来看,这个准则是大牛们很早有意为之的。而『利用 application protocol 进行协作的进程就是 UNIX 实践这个原则的工程传统。』这一点可能有机缘巧合的成分,不过有了上面的一般准则作为铺垫,UNIX发展出这个具体的传统似乎应该比其它OS可能性更高。

      • fuzhou Says:

        俺不是说UNIX的成功是偶然,俺强调的是我们不该把UNIX的成功归结于少数先哲的高瞻远瞩——俺不赞赏个人英雄主义。

        从俺查到的文献上看(主要是洪峰主编的《自由软件文集》),UNIX的工程传统确实是UNIX早期设计者确立起来的。但俺的理解是这应当与UNIX早期开发者就大量采用分布式协作有关,特别是伯克利和多个大学研究部门介入之后。

        或者应该这样说,UNIX的开发者很早就认识到《人月神话》中反复强调的问题,即在大规模协作的前提下无法保证开发者能维持统一的项目理解水平和开发进度。所以UNIX的软件往往倾向于自身功能保持简单(个人维护方便)和接口清晰(便于同侪理解)。反过来,完全在公司环境下开发的项目往往强调自身功能完备(同事在一地交流开销小),对外接口贫弱(减少维护成本)。

        一开始显然是公司风格比较占优,因为风格统一且用户要学的东西较少。但随着软件越做越大,软件结构就开始指数级别上升,此时公司风格带来的质量控制上的问题也会越来越多。而UNIX设计中部件可替换的优势在这个时候就能体现出来。从这一点上看,俺同意如今UNIX风格在Mac和服务器领域的成功有其必然性。

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

您正在使用您的 WordPress.com 账号评论。 注销 /  更改 )

Facebook photo

您正在使用您的 Facebook 账号评论。 注销 /  更改 )

Connecting to %s


%d 博主赞过: