Mac OS X 背后的故事
作者王越,美国宾夕法尼亚大学计算机系研究生,中国著名 TeX 开发者,非著名 OpenFOAM 开发者。
Mac OS X 背后的故事(一)力挽狂澜的Ellen Hancock
Mac OS X 背后的故事(二)Linus Torvalds的短视
Mac OS X 背后的故事(三)Mach之父Avie Tevanian
Mac OS X 背后的故事(四)政客的跨界
Mac OS X 背后的故事(五)Jean-Marie Hullot的Interface Builder神话
Mac OS X 背后的故事(六)上善若水
Mac OS X 背后的故事(七)上善若水下——Cordell Ratzlaff 引发的 Aqua 革命
Mac OS X 背后的故事(八)三好学生Chris Lattner的LLVM编译工具链
Mac OS X 背后的故事(九)半导体的丰收
Mac OS X背后的故事(十)Mac OS X文件系统的来龙去脉
Ellen Hancock曾任苹果公司技术总监
Mac OS X 背后的故事(一)力挽狂澜的 Ellen Hancock
故事还得从 20 世纪 90 年代说起。Ellen Hancock 是本文的主人公,也是一位女英雄。她因在 IBM 的经历而被人们所熟悉。1966-1995 年间,Ellen Hancock 在 IBM 共工作了 29 年。1985 年,她成为 IBM 的副主席。在 1986-1988 年间,Ellen Hancock 担任过 IBM 通信产品的主席,并在 1992 年被选为资深副总裁。1995 年 9 月,她被时任美国国家半导体(National Semiconductor)CEO的 Gil Amelio 忽悠,跳槽来到这个企业,做执行副总裁。她在这里带领团队完成了 CompactRISC 架构,这个架构事后成为 ARM7 系列的前身。很多人早已经把她忘了,也很少有人能够在回忆时将她和 Mac OS X 联系起来。但事实上,她是让苹果放弃 Copland 转而购买 NeXT 的关键人物。
早在 1994 年,Gil Amelio 就找好了下家 Apple,成为 Apple 董事会的成员。1997 年 2 月,Gil Amelio 从 National Semiconductor 辞职,并成为 Apple 的 CEO。为了紧跟老板的召唤,Ellen Hancock 再次被忽悠,来到了当时危机四伏的 Apple。这时是 1996 年 5 月,为什么是危机四伏呢?还得从早先的事情说起。
20 世纪 80 年代,卖可乐的 John Sculley 成为 Apple 的 CEO,随之 Steve Jobs 被轰出Apple。毕竟可乐和计算机不是一回事,因此不管是硬件还是 Mac OS,整个公司的开发项目越来越受阻。而且由于先天的不足,Mac OS 从诞生之初就不具有一个现代操作系统所应有的特性。所以,在 1987 年,开发下一代操作系统的计划呼之欲出。具体的规划是,把新的系统所需要的功能,写在一堆卡片上。短期可实现的目标,比如增加颜色支持(当时计算机仍是黑白的),写在蓝色的卡片上;长期的目标,比如多任务功能,写在粉色的卡片上;而在可预见的未来都无法实现的长期的目标,比如加一个纯物件导向的文件系统,就写在红色的卡片上。在这样的思路下,Mac OS 的开发团队马上就被分成两个组,一个叫蓝组,目标是在 1991 年,发布一个关于 Mac OS 的更新版本;另一个叫粉组,和蓝组同时工作,计划在 1993 年,发布一个全新的操作系统。
1991 年 5 月 13 日,蓝组顺利按时完成开发任务,发布了 Mac OS 7(一般被称为 System 7),而粉组却没做出什么有实际用途的东西来,因此接连跳票。而且,由于 Mac OS 7 的发布缺乏人手,为了保持正常发布,常常需要从粉组抽调人员参加蓝组的开发,再加上 Apple 当时把重心放在了和 IBM 等公司的合作上(Taligent 项目)而不是在粉组上,最终导致了粉组项目夭折。而本来 Apple 指望和 IBM 合作的Taligent 项目能开发出一个可用的新系统,但后来 IBM 不跟 Apple 继续玩了,因而 Taligent 的果子又吃不到,Apple 相当郁闷。这时由于 Mac OS 有先天不足(单任务,没有内存保护),再加上 Apple 以及第三方软件的无限量增加(在这段时期,单 Apple 自己就已经加入了 QuickDraw、PowerTalk、QuickTime 等软件和技术,每一个都比 Mac OS 本身来得大),Mac OS 的问题终于大爆发。上个世纪 90 年代,Mac OS 给人的印象就是很不稳定、经常崩溃,同 Windows 95 留给 PC 用户的印象差不多,甚至更甚。
Taligent 项目挂掉后,Apple 自己尝试过十多个不同的内部项目,但大多没做多久就夭折了。而这时正是 Windows NT 走向成熟的关键时期。眼看着日子逐渐变得不好过了,Apple 开始重新开始考虑建立下一代操作系统的事情。1994年,Mac OS 7.5(Mozart)发布后,Apple 推出新规划,建立一个全新的操作系统,以 Copland 命名(纪念 Aaron Copland,Mac OS 的发布以音乐家名字命名,和 Mac OS X 后使用猫科动物名字很不一样),这个项目将有一个全新的内核,具有类似 Windows NT 内核的所有高级特性,而老的软件都当作独立的进程模拟运行。这个项目时间紧、任务重,1995 年 3 月公布计划,预期 1996 年发布。而 Copland 后的版本 Gershwin(纪念 George Gershwin),预计 1997 年发布,将重写 Mac 的所有系统主要部件,以适合新内核的各种特性。
Copland 将使用微内核技术,只做任务和内存分配。除此之外的所有功能,比如文件系统、硬件驱动等作为微内核上的服务运行。而 Mac OS 的所有用户界面功能将成为一个独立的框架,称为蓝盒(Blue Box,今后介绍 Mac OS X 时,我们还会遇到这个词)。所有的任务相互独立,占用独立内存,也可以用 IPC 相互交流。学过操作系统的人都知道,微内核是当时的一个热词,一个系统只有被称为微内核才可被看作是先进的,当时还有针对 Linux 系统的著名的 Tanenbaum-Torvalds 笔战。但事实证明,所有本来想做成微内核系统的成功项目都放弃了原先的设计(包括 NeXTSTEP、Windows NT),因为这种类似 Mach 微内核的系统往往难产,GNU/Mach + Hurd 之类的项目做到现在经过了20年,仍未成事,一年内搞一个微内核系统谈何容易。
微内核还没搞成,Apple 几乎所有开发组的成员都来添乱。大家都说自己做的东西很重要,一定要加入 Copland 开发组,所以 QuickDraw GX、OpenDoc 之类的开发组产品成为新系统的核心组件,甚至类似用户界面皮肤之类的开发组都来凑热闹,马上就使 Copland 成为一个无法维护的项目。开出的计划越来越长,项目越来越多,但相关进展越来越少,完成速度越来越慢。即便做出了产品,连测试人手都不够。1995 年就有工程师指出,在 1996 年发布 Copland 纯粹是幻想,能 1997 年发布就不错了。
1996年,Gil Amelio 已经掌权。在苹果电脑全球研发者大会上他开心地宣布,传说中的 Copland,也就是 System 8 的开发版会在当年夏天发布,而正式版在秋天就可以送到每位用户手上。时任 TidBITs 编辑的 Matt Neuburg 有幸见到了这个传说中的系统。令他大吃一惊的是,这个系统在当时只能打开或关闭文件,而无法对文本文件进行编辑,甚至所有用户界面的文本框都不能输字。哪怕什么都没做,整个系统也会随机崩溃,而崩溃甚至会造成文件系统损坏。参加演示的苹果员工,则需要不断守在旁边,他们的工作是不断地格式化已崩溃的计算机磁盘,然后重装系统。那年夏天,第零个测试版送到一小簇不明真相的开发者手中,把那些脆弱的没见过世面的人吓得半死。就连 Apple 内部都开玩笑说 Copland 的正式发布日期可能得推迟到 2030 年。
Gil Amelio 心急如焚,希望 Copland 快点走到正道上来。作为 Gil Amelio 永远的好朋友,Ellen Hancock 就在这个乱糟糟的时候来到了 Apple。她的职务,正是担任这个乱糟糟项目的负责人。她亲自下访各小组体察民情,了解情况。毕竟在 IBM 干了近三十年,她依靠自己过人的判断力在 2~3 个月内便得出结论,Copland 这个项目是没有指望的,就按目前 Apple 这样的状态,Copland 永远都不可能发布,还不如早点取消了好。在短期内,先把 Copland 中的一些有用的成果一点点合并到老的 Mac OS 中,并且抓紧从外部购买一个全新系统来满足 Apple 的需要。正是她的这个结论,结束了 Apple 长达五年的纠结,使公司重新走向正轨。整个 PC 的黄金时代已经过去,Apple 想要翻身,还有很长一段路要走。Gil Amelio 支持了 Ellen Hancock 的计划。1996 年 8 月,Apple 取消 Copland 项目。开发预览版的 CD 封套都已制完,每个邮包上的地址都已打印就续,而 CD 却从未曾制出。
1996-1998 年是 Apple 最混乱的几年。在商业上,有一阵曾传出 Apple 要被 Sun 收购的消息。更有意思的是,《连线》杂志在 1997 年的六月还发表了一篇文章,名为《101 种拯救 Apple 的方法》,其中一条说最好的方式是 Apple 让自己被 Motorola 买下,成为 Motorola 的一个部门,做 PowerPC 系列产品。以当时的眼光来看这些建议非常讽刺好笑,以今天的眼光看更为好笑。而 Ellen Hancock 在这段时间内的出色工作,成功地挽救了 Apple。
首先,Ellen Hancock 的对内政策是继续 Mac OS 7.5 的开发工作,一步步把 Copland 中的技术并到 7.5 中。同时,也大量购买第三方的系统增强包,包括插件管理工具、层次化菜单等技术。Apple 把它们买过来,整合到现有的系统中。整个老系统在新系统尚未完成的时候不断更新,至 2000 年已出到 9.0 版,尽可能地留住了老用户。并且,前面提到的蓝盒(Blue Box)也作为后来新 Mac OS X 系统的一部分,支持用户运行经典 Mac OS 的程序。
而对外政策更是一个大手笔。Ellen Hancock 协助 Gil Amelio 在 Apple 之外找寻操作系统技术。在 IBM 和 Microsoft 合作 Big Blue 的经验告诉她,购买一个操作系统的使用权问题多多,最好的计划是整个一并买下来。因此,Gil Amelio 开始和当时看好的 Be 谈,却因价格问题没有成功,最终转而收购了 NeXT。而 Apple 合并 NeXT 后,NeXTSTEP 就演化为 Rhapsody,并最终成为 Mac OS X。这些事情我们今后会详细再谈。
买完 NeXT 后,Steve Jobs 执政,Gil Amelio 因任 CEO 期间 Apple 亏损严重而被炒。Steve Jobs 把信得过的人(很多是前 NeXT 员工)拉拢到周围,开始新政,而同 Gil Amelio 有关的 Ellen Hancock 则在人事变动中被疏远。Steve Jobs 甚至在很多场合称她为“笨蛋”。Ellen Hancock 最终于 1998 年主动辞职。事后同 Gil Amelio 以及 Apple 的创始人之一 Steve Wozniak 一同创业,但始终不景气,她的辉煌时代已经过去。
Gil Amelio 总结他在 Apple 时期的工作时说:“Apple 是一艘底部有洞漏水的船,而我的工作是把这船引向正道。”(Apple is like a ship with a hole in a bottom, leaking water, and my job is to get this ship pointed in the right direction. )Ellen Hancock 虽然同 Gil Amelio 一样,不知如何去堵这个漏水的洞,但正是由于她在 Apple 的出色表现,不但把船引到了正道上,还找来了有能力堵这个洞的人。
Mac OS X 背后的故事(二)——Linus Torvalds的短视
本文主要的故事来源是 Linus Torvalds 的自传《Just for Fun: The Story of an Accidental Revolutionary》。
Steve Jobs对Mac OS X的考虑
1997 年,Steve Jobs 回归,开发下一代操作系统的工作被提上日程。此刻的时代背景是像 Linux 这样的开源软件大行其道。随着网络的发展,使得像 Red Hat、VA Linux 之类的企业成为爆发户,把泡沫越吹越大。Steve Jobs 承认 Linux 的好处,甚至在若干年后介绍 Mac OS X 底层的 Darwin 时还不忘在幻灯片上写道:Darwin 是类似 Linux 的系统。而当时精明的 Steve Jobs 在考虑下面几个问题。
第一,NeXTSTEP 的内核和外围工具中,BSD 代码维护起来需要大量人力,而且各分支的 BSD 发展显然不如 Linux 快。很多功能都没有,需要 Apple 自己做。
第二,像 Apple 这样的小公司,需要借力打力。Apple 的主要竞争对手是 Microsoft,而开源软件的矛头也是 Microsoft,如果联合起来干革命,不但能让自己得到一个好名声(Apple 事后一直自称是最大的开源软件公司),也可以获得可观利益,从而对 Microsoft 造成压力。
第三,也是最重要的,联合各开源组织能够推动 Mac OS 的发展。毕竟开源软件中像 GCC 之类都是很成熟的项目,Apple 用起来省时省力,投点钱就有大效益,多好。
所以,把 Linux 内核作为 Mac OS X 的重要组成部分的想法被这位伟大的智者想了出来。Apple 之前也有开发 Linux 的经验,比如在 Steve Jobs 回归之前,Apple 就和 OSF 合作开始把 Mach 内核移植到 PowerPC 上(Apple 是最大的 PowerPC 玩家,而 OSF 是最大的 Mach 玩家),并把 Linux 作为服务跑在 Mach 上。这个系统就是 MkLinux,我们在后续的连载中还会提到这个系统,因为它不但对 Linux 的移植性作出了重要的贡献,也对后来的 Mac OS X 的 XNU 内核技术起到了相当重要的作用。
如果可以采用 Linux 作为系统重要组成部分,并且这个构想能够取得在开源软件界呼风唤雨的 Linus Torvalds 的认同,就能靠他在社区鼓动一大群开发者皈依 Apple 麾下,这是 Apple 很想看到的给力结局。有了这个指导思想,他便让秘书给 Linux 的开发者 Linus Torvalds 发了一个邮件,问他是不是有一到两小时的时间和 Steve Jobs 会面。不明真相的 Linus Torvalds 收到邮件后相当高兴,因为这是他第一次有机会去硅谷观摩。
无果而终的会面
Apple 总部 Infinity Loop 终于迎来了这位稀客,Steve Jobs 亲自接见,而先前任 NeXT 技术总监的 Avie Tevanian(这人的故事我们今后会提到)也参加了这次会谈。不用多说,这次讨论的内容自然是还处于未知状态的 Mac OS X。讨论算不上正式,但 Linus Torvalds 的愤青个性,却让谈判陷入僵局。
Steve Jobs 自然搬出他 1997 年回归之际在 MacWorld 讲话时的那套理论,Apple 虽然很颓,但骨子里是个牛逼的公司。全世界桌面领域的真正玩家就两个,一个是 Apple,另一个是 Microsoft,两者加起来,构成百分之百的桌面用户群。所以,Linus 同学,你就从了我们吧,如果你从了我们,让我们把 Mac 架在 Linux 上,一大批桌面用户就是 Linux 用户啦,前景可是一片大好!
而 Linus Torvalds 那时候牛啊,诸多大公司如 IBM、Red Hat 都围着他转。他可是企业家中的大红人,像 Apple 这样的企业根本就不在他眼里。作为一个开源软件的革命家,在他的想象中 Linux 的潜在用户应该比 Apple 还多。他始终相信,按照目前开源软件的发展态势,自己很快就能在桌面领域分到一杯羹。而且这个命题在他这种古怪性格下的直接推论是,即使我能占领桌面领域,我也要摆出一副不在乎这个领域的态度来。所以实际上 Steve Jobs 的开场白一开始就失败了。
接着,Avie Tevanian 向 Linus Torvalds 介绍了整个计划。他们想把 Mach 和 Linux 内核合并起来作为 Mac OS X 的基础,我估计 Linus Torvalds 是听错了(因为 Avie Tevanian 很早就意识到相比于微内核,混合内核有明显优势),他以为 Apple 想把 Linux 作为 Mach 的一个服务来跑(当然我个人认为,即使是合并 Mach 和 Linux 成为混合内核,依 Linus Torvalds 的愤青性格,依然是不可能接受的),这正让他回想到先前和 Tanenbaum 教授的笔战。并且,他也知道 Apple 和 IBM 合搞的失败项目 Taligent 正是用 Mach 的。
Linus Torvalds 对于微内核有他自己的看法,之前也曾在不同的地方表述过。若把关于微内核的笔战去掉限制级敏感词的话可概括成两方面。一方面,设计一个微内核和相关的服务,可能造成各种设计上的灾难。GNU/Hurd 早在八十年代末就考虑尝试在 Mach 上写一系列 Unix 的服务层,结果他们始终无法搞明白到底是让这些服务先发消息到另几个服务呢,还是考虑其他方案。所以直到 2011 年我写这篇文章时,Hurd 项目依然处于半死不活的状态。而另一方面,微内核的效率无法和传统内核相比,最简单的系统调用会涉及一系列底层服务的互相通信。所以很多研究者着手研究如何把微内核的效率提上去,结果就导致微内核变得更加复杂。能提高微内核效率的很多研究成果都已在 Mach 项目中实现了。而在 Linus Torvalds 看来这恰使 Mach 成为了一个非常复杂的项目,并且效率也不怎么高。
会谈时坐一旁的 Avie Tevanian 事实上是 Mach 最早的开发者之一,他热情地给 Linus 讲述 Mac OS X 系统蓝图。而 Linus 实际上早就不耐烦了。比如,Mac OS X 中,有一个模拟层,可让用户使用经典的 Mac OS 程序。这个技术极类似于现在跑在 Unix 系统上执行 Windows 程序的 Wine 。Apple 当时的考虑是这样,因为老的 Mac OS 在设计 API 时,就没有考虑到类似内存保护之类的问题,所以这层 API 必须废掉,Mac OS X 中所有的新程序必须采用 NeXT 的那套更先进的 API(根据我的考证,当时还没有 Carbon 这样的想法,而且事实上 Carbon 不管在 API 还是 ABI 上都和经典 Mac OS 不兼容)。而短期内已有的软件又不可能快速重写迁移至 Mac OS X。所以,如果用户需要使用老版 Mac OS 的第三方应用程序,就可以使用 Apple 提供的这个兼容层。但是由于刚才提到的原因,老版程序并不享受新版程序的待遇,因为模拟器本身运行多个老 Mac OS 任务时,和原先老版 Mac OS 一样,实际上只有一个进程,没有内存保护。这样做的好处是明显的,因为一方面老的程序在 Mac OS X 发布之初还能用,另一方面 Apple 又和老技术划清了界限,逼着开发者使用新技术,技术方面的原因是最重要的。但这个看似很正确的技术在 Linus Torvalds 看来是古怪的,他想当然地认为,完全可以运行多个不同的模拟器进程,来执行不同的任务,使得每个任务都可以享受内存保护。这种浪漫主义情调让他无比鄙视 Apple 员工的智商。而事后当笔者使用早期版本的 Mac OS X 时,发现 Linus Torvalds 的想法完全是不切实际的。因为这个模拟层本来就要占用不少的内存和 CPU,在处理器速度不及今日手机、内存无比精贵的 90 年代末,跑一堆模拟器进程无异于是和自己过不去。
Steve Jobs 考虑到 Linus Torvalds 是开源软件的领军人物,便继续以开源为话题,动之以情,晓之以理。他告诉 Linus Torvalds,我们这个系统做出来后呢,所有的 Unix 层(非图形界面层),都会开源,所以事实上你加入我们,也是在给开源做贡献啊!而由于在开源圈子混久了,Linus Torvalds 对此丝亳不领情,他认为,有谁会想用一个底层是开源而图形界面是不开源的系统呢?所以,像笔者这样的用户被“代表”了。
Mac OS X 与 Linux 分道扬镳
总之,这次会面完全谈崩,两人站在不同的角度去看问题,加上 Steve Jobs 和 Linus Torvalds 都是个性鲜明、唯我独尊的人,技术和商业上的考虑都不同,所以会谈中双方简直就是鸡同鸭讲。这次讨论也使得 Apple 放弃 Linux,转而采用 FreeBSD 技术,并在 2001 年任命 FreeBSD 的发起者、领军人物 Jordan Hubbard 为 BSD 技术小组的经理,并在后来升为 Unix 技术总监。至于 Apple 的内核技术后来走向何方,我们下期再讲。
笔者认为,Apple 和 Linus Torvarlds 的商谈破裂,以今天的眼光来看,是因 Linus Torvarlds 的自命清高和短视造成的。他不懂得尊重其他开发者的意见,并且不断抬扛。包括后来关于 C++ 的论战。Mac OS X 发布后,Linus Torvalds 又数次嘲笑 Mac 的技术落后,并说这些他在当年和 Steve Jobs 开会时就预料到了。直到最近,他终于有些成熟,对 Mac OS X 的观点开始缓合,但还是不忘批评 Mac 的文件系统就是垃圾(事实上,Linux 的也没好到哪去,至少 Apple 还搞过一阵 ZFS)。这种性格最终导致在 Mac OS X 和 iOS 大行其道的时候,Linus Torvalds 连兔子汤都不曾分到。
而事实上这对 Apple 也是件好事。Apple 重要的是利益而不是折腾,即使是开源也是利益驱动。像类似 Linux 开发组那样自以为是但代码又写得差的开源项目,Apple 事后也遇到不少,比如 GCC 编译器项目组。虽然大把钞票扔进去,在先期能够解决一些问题,但时间长了这群人总和 Apple 过不去,并以自己在开源世界的地位恫吓之,最终 Apple 由于受不了这些项目组人员的态度、协议、代码质量,觉得还不如自己造轮子来得方便,因此 Apple 推动了类似 LLVM 这样宏伟的项目,并且在短短几年内,使其成为最领先的开源软件技术。这无异于扇了 Linux 小组、GCC 小组一记响亮的耳光。
Mac OS X 背后的故事(三)Mach 之父 Avie Tevanian
1975 年,美国罗彻斯特大学纽约分校,一组研究员正在做一个名为 RIG(Rochester’s Intelligent Gateway)的项目,它由 Jerry Feldman 主持设计。RIG 的目标是给所有本地以及远端的计算设备(比如磁盘、列印机、磁带、绘图机等)提供一组统一的访问方式,其作业系统称为 Aleph。为了实现所需要的功能,Aleph 的内核主要构建了一个进程交互(Interprocess Communication,IPC)的机制。RIG 的各进程,只要设置了目标端口,就可以彼此间发送信息。RIG 项目没过几年就被判了死刑,主要是缺少很多有用的功能,比如端口没有保护机制,一次最多只能发送 2KB 大小的信息(受硬件限制),也没有很好的网络支持等。不过在 20 世纪 70 年代,这个系统依然代表着当时作业系统设计的先进水平,比如除了进程交互外,每个进程还有内存保护的功能,这足以让 20 世纪 90 年代末都没有做出内存保护技术的 Apple 公司汗颜。
该项目后来失败了,随后在 1979 年,RIG 的 Richard Rashid 博士毕业到卡内基-梅隆大学当教授,开始做 Accent 项目。它是一个网络作业系统,于 1981 年 4 月开始活跃开发。受 RIG 的影响,Accent 系统的亮点也在于可以使用 IPC,而且解决了很多 RIG 的不足。比如每个进程有 4GB 的虚拟内存空间,而且甚至连内核自已都可以被存入缓存页面,内存有先进的更新前拷贝(Copy-on-Write)功能,可以实现进程间大信息的传送等。读者可以把 Accent 理解为支持虚拟内存技术,并且具有网络透明 IPC 功能的 RIG 内核。
但过了几年,开发者们越来越对 Accent 失去兴趣。在 1980 年初,很多人觉得多核计算是计算机未来发展的潮流,但 Accent 内核在设计时并没有考虑到这些问题。而且,随着许多实验室纷纷购置性能更强劲的计算机,这就意味着 Accent 需要移植到新的目标架构上。此外,Unix 正大行其道,不管是在作业系统理论上还是在用户程序上,都成为最为流行的作业系统模式,而 Accent 并不是一个 Unix 系统,所以无法享受 Unix 世界的诸多美好。为了解决这个问题,研究人员决定把所有设计推翻重来,于是就有了一个全新的系统。
在匹兹堡的一个雨天,卡内基-梅隆大学的 Avie Tevanian,此系统的最主要开发者,正打着伞和同学们在去吃午饭的路上。他们一边绕着无数的泥塘,一边构思给这个新系统取什么名字好。灵感突来, Avadis Tevanian 建议把这个系统叫作 Muck,引得同学们哈哈大笑。后来,Richard Rashid 和一位意大利同事 Dario Giuse 说起这玩笑,结果这位同事不经意地把 Muck 发为 Mach,遂把 Richard Rashid 笑翻,伟大的 Mach 系统因此得名。
Mach 是一个受 Accent 启发而搞出的Unix兼容系统。那年,Unix 已经十六岁,而且依然是作业系统理论与实践开发的主要阵地。Unix 内核由于新加入的功能越来越多,变得越来越复杂。而 Mach 的一个主要目标就是尽量缩减 Unix 的各项服务,以使内核变得简单可维护。此项目从 1984 年开始,目标主要是包含完整的多任务支援、良好的硬件移植性,并要把大量服务移出内核作为跑在内核上的服务,以及提供与 Unix 的兼容性。
Mach 使用纯 C 编写,所以在一定程度上保证了可移植性,这事实上为后面的 NeXT 向 PowerPC 移植以及 2005 年的向 Intel 移植提供了很重要的前提。而为了缩减内核该管的任务,Mach 做得很绝,只提供内存和处理器管理。类似于档案系统、网络、输入输出等功能都作为单个的系统进程,独立执行于内核之上。Mach 的开发过程以 4.3 BSD 作为起点,以 RIG 的 Accent 作为参考,采纳 DEC 的虚拟内存设计思路,逐步开发,以新写的代码代替 BSD 的代码。两年后的 1986 年,虽然没能把系统服务完全分离于内核之外,但已颇见成效。Mach 第一版大功告成,组员发表会议论文,成为操作系统史上里程碑式的经典,引发操作系统业界的“微内核”学潮,如今学习作业系统设计的皆需学习此文,二十五年来被引用一千二百余次。
这篇文章主要讲了两方面内容:IPC 和虚拟内存。在 IPC 方面,Mach 把复杂的消息传送机制分为四个独立的清晰概念—任务、线程、端口、信息。任务是拥有一组系统资源的对象,允许线程在其中执行;线程是执行的基本单位,拥有一个任务的上下文,并且共享任务中的资源。
由于该论文的影响力,所以项目得到了 OSF(Open Software Foundation)在内的很多投资。当然了,学术和工程永远存在差距,所以即使是最受欢迎的 Mach 2.5 其实仍然是一个包括大多数 BSD 服务层的单内核。但包括 NeXTSTEP、OSF/1 在内的很多操作系统都采用 Mach 作为其内核技术,原因是广大研究人员依然相信微内核代表着未来。虽然 Mach 2.5 的效率比传统的 Unix 系统稍低一些,但研究者们表示情绪淡定,因为 Mach 支持多处理器系统,可以利用多线程把任务处理得飞快,相比之下其他 Unix 内核并没有多处理器的完善支援,因此 Mach 效率稍低完全可以接受。但随着真正把 Mach 和 BSD 服务完全脱离的 Mach 3 微内核面世,研究人员们的情绪就再也淡定不起来了。因为服务和内核分离后,任务间的 IPC 数量暴涨,一个简单的 Unix 系统调用要涉及到十多个开端口、设权限、发送、收取消息的操作,哪怕是使用数年后的 1997 年的硬件,跑一个系统调用密集的程序,Mach 的效率要比一般的 Unix 系统慢 50%,而且根本没有什么好方法来解决这个问题。
所以 Mach 3 出来后,虽有少数微内核信徒继续执著地改进 Mach,或者开始其他微内核比如 L4 的研究。但学术界对 Mach 的兴趣大减,因而 Mach 3 也成为最后一版。项目解散后,Richard Rashid 去了微软研究院。
再说我们的主角 Avie Tevanian,他 1987 年博士毕业去了 NeXT。这家公司刚刚由 Steve Jobs 成立两年,这两年 Steve Jobs 啥正经事都没干,只是花了十万美元雇 Paul Rand 设计了一个公司商标。直到 Avie Tevanian 加入后,这个公司才开始干实事。1987 年公司确认要开发一个面向研究人员使用的计算机工作站,于是软硬件的开发工作紧锣密鼓地展开。硬件组由领导过 Apple Lisa 的 Rich Page 原班人马负责,而软件则由 Avie Tevanian 负责,计划开发一个有图形界面的操作系统 NeXTSTEP。由于 Avie Tevanian 是 Mach 主要的开发者,自然 NeXTSTEP 就基于 Mach 了。1988 年 10 月 12 日,NeXT 发布预览版(0.8版),并于 1989 年 9 月 18 日发布 1.0 版(注:http://en.wikipedia.org/wiki/NeXTSTEP)。
作为 NeXTSTEP 系统的内核,NeXT 分支的 Mach 经历了不少变化。NeXTSTEP 0.8 主要使用 Mach 2.0 版,而稍后的 NeXTSTEP 1.0 版主要基于 Mach 2.5 版,包含一个自己定制的当时最新的 4.3 BSD 服务层。从 3.1 版开始,NeXT 分支的 Mach 还包括一个全新的设备驱动框架, 名为 Driver Kit,仅供 x86 系列的硬件使用。和 Mach 以及 BSD 代码不同,Driver Kit 是使用 Objective-C 写的。为什么是一个面向对象的语言呢?看 NeXTSTEP 3.3 的 DriverKit 文档。读者大概就会发现,NeXTSTEP 把所有硬件设备理解为对象,而我们知道,对象之间有继承关系,比如,磁盘(IODisk物件)属于输入输出设备(IODevice物件)的子物件,而磁盘(IODisk)本身又是逻辑磁盘(IOLogicalDisk)的父物件。硬件的初始化对应于每个物件的初始化(init方法),硬件又有读、写,所以可以用 getter/setter 的方法。因此,DriverKit 是一个非常有特色的实现。而且由于 Objective-C 的效率很高,依赖很少(Objective-C 程序可以直接被编译器翻译成等价的C语言程序并编译,而 Objective-C 的运行库 libobjc 也以高效著称),所以也是编写驱动的良好选择。几年后的 IOKit 其实就是个 DriverKit 的翻版。
这时,NeXTSTEP 操作系统大获成功,风险投资商们纷纷购买,但硬件却始终卖不出去(注:Aaron Hillegass《Cocoa Programming for Mac OS X》前言),所以 NeXT 砍掉了硬件部门专做软件,更是使 NeXTSTEP 发展到了巅峰时期,同时支持 68K、x86、PA-RISC 和 SPARC 等硬件,但颇有意味的是它就是不支持 PowerPC 架构。它可以同时产生一个包含所有架构可执行码的二进制文件,来使开发的程序在所有平台上执行。这个功能也影响了后来 Mac OS X 的技术。Mac OS X 10.4 时代有两件跨时代意义的事情,一件是 Apple 搞出了 64 位的 Power Mac,开发者可以发布一个包含64位和32位程序的单一可执行文件,而无需让用户去区分;另一件是和 Intel 合作。Apple 正式发表了 Universal Binary 技术,可以一个 Mach-O 文件同时包含 Intel 和 PowerPC 的指令。这非常贴心的设计(要知道,大多数电脑用户根本不知道 Intel、PowerPC、64位、32位等技术)就是来自于 Mach 的技术。
NeXTSTEP 3.3 后,NeXTSTEP 因为 NeXT 和 Sun 的合作改名为 OPENSTEP,1996 年发布 4.0 版,到 1997 年 2 月 4 日,NeXT 被 Apple 收购之前,期间内核改进除源码同步到 Mach 3.0 版外不明,而且出于不知道的原因,我手头的 OPENSTEP 正式版光盘中,居然找不到 DriverKit 的发布说明和编程文档,故不作详述。不过这段时间,Apple 的活动值得好好一说。之前在《Linus Torvalds的短视》中,我们曾提到,1996 年,Apple 和 OSF 曾经合作,把 Mach 移到 PowerPC Mac 上,再把 Linux 作为单一的服务跑在 Mach 上,这个项目叫做 MkLinux。在 1996 年发布基于 Mach 3.0 和 Linux 1.3 的预览版,并更新到 2002 年结束其历史使命,对 Mach 在 PowerPC 的移植性上做出了重要贡献。这个 PowerPC 版的 Mach 被叫作 osfmk 分支,也正是现在 Mac OS X 中用的分支。当然了,NeXT 被合并后做了大量修改。
Apple 收购 NeXT 后,Mach 被确定作为未来的操作系统核心。Avie Tevanian 被选为软件开发部的总裁。合并所有项目的号角吹响后,上层的 OpenStep API 和老版 Mac OS 的部件开始合并,而 Mach 也经历重大变化。主要是一方面,Mach 使用了 osfmk 分支,但依然包含 4.3 BSD 服务;另一方面,DriverKit 被 IOKit 取代。这是 Apple 走得很被动的一步。因为当时外界普遍对 Objective-C 不看好,逼着 Apple 走老版 Mac OS API 的老路。而 Apple 自己对 Objective-C 也很不自信,甚至想索性换用 Java 了事(我们以后会谈及这段不自信的历史)。所以 IOKit 是一个 C++ 的驱动架构,来符合大众口味。这些改变最早在 Rhapsody 中出现(我们以后也会有一期 Rhapsody 的专题)。但由于 C++ 是门很恐怖的语言,所以 Apple 又把 C++ 给阉割了,去掉了多重继承、模板、运行时动态以及异常,让开发者使用这种对于 Objective-C 来说换汤不换药的 Clean C++ 来做驱动。但公正地说,IOKit 对于 Driver Kit 是有不少改进的,比如 IOKit 可以写在用户空间跑的驱动(虽然大多仍是跑在内核空间上的),因而驱动挂了而系统不会挂。另外 IOKit 考虑到了计算机发展的趋势,所以在电源管理、即插即用、动态加载上做得更好。
但各位也知道,C++ 程序得用专门的运行库才能跑,所以 Mach 中又加入了一个叫作 libkern 的库负责 C++ 相关的功能,同时,还有一个 libsa 的库提供一些类似二分查找、排序等基本算法之类的功能。最后和硬件相关的还有一个叫作 pexpert(Platform Expert)的库,负责收集硬件设备列表、检测机器种类(比如处理器速度等)、解析启动参数等杂活。
至此,Mac OS X 的内核完全形成,形成 BSD、IOKit、Mach osfmk 三足鼎立的态势,并有 pexpert、libkern、libsa 作为基础。Apple 称它的内核杰作为 XNU。其代码开源,请读者移步http://www.opensource.apple.com/source/xnu/xnu-123.5/,每个部分的代码都独立存放在一个文件夹中,条理清晰,不妨一读。
由于 4.3 BSD 已是过眼烟云,Apple 后来投入大量资源扶持 FreeBSD 开发。2001 年,Apple 将 FreeBSD 的发起者、领军人物 Jordan Hubbard 收入麾下,并在 Mac OS X 10.3 时基本同步到 FreeBSD 5 的代码(注:http://osxbook.com/book/bonus/ancient/whatismacosx/arch_xnu.html)。
另外,Apple 的开发也同时反馈到 FreeBSD 小组,包括 FreeBSD 6.2 内核引入的 AUDIT (man audit 或参见http://manpages.unixforum.co.uk/man-pages/unix/freebsd-6.2/4/audit-man-page.html),后来 FreeBSD 8引入的 libdispatch (http://wiki.freebsd.org/GCD, 在 Apple 这项技术叫 Grand Central Dispatch,是 Mac OS X 10.6 主推的新功能,FreeBSD 基本在 Mac OS X 10.6 上市的同时就拥有这项最新技术),以及 FreeBSD-CURRENT 中的 LLVM-Clang,全是 Apple 的手笔。从 1999 年开始,FreeBSD 源码仓库可以搜索到 Apple 提供的大量的补丁以及新功能。
Mac OS X 早期版本不太稳定,所以会内核崩溃。10.0 版本会直接像 Linux 或者 BSD 那样打出回溯信息,很不美观,所以 Apple 在 10.2 版本开始设计了一个多国语言的图片告诉用户你的内核崩溃了,以让内核崩得看起来更优雅一点。由于包含四国语言,被国内用户戏称为“四国”(注:优雅的图片见下图,详见 http://support.apple.com/kb/ht1392),这是 XNU 的 Mach osfmk 部分的功能。但从 10.3~10.4 版本开始,系统越发稳定,正常使用已很少见到内核崩溃。而且,内核提供的服务也越来越多,使得 Mac OS X 成为一个完善的系统。
21 世纪 XNU 架构方面的最重大改动是支持了 PPC64(10.4 版本时代)、x86 架构(其实本来也一直支持的,以后讲 Apple 的 Intel 迁移时详谈)、x86_64(64位支持是苹果长年努力逐步展开的。10.4 时代 32 位内核支持载入 64 位的用户程序,10.5 系统提供 64 位的Cocoa框架,但系统大部分程序都是 32 位的,10.6 时代内核支持以 64 位模式启动,但在不少硬件上这是非默认的方式,但系统大量程序已被改写并编译为 64 位的二进制程序,10.7 时代内核默认以 64 位模式启动。)和 ARM 架构(iPhone 和 iPad 使用 XNU 内核)等多个新架构。
而其中 ARM 架构的支持别具意义。但 2006 年 5 月 31 日,功成名就的 Avie Tevanian 离开 Apple 另谋发展,此时,离 Apple 的 iPhone 奇迹发生,只有不到一年时间。
2000 年,美国总统大选,由于选票设计问题,时任美国副总统的 Al Gore 败北。2000 年 12 月 13 日,在一番重新计票的大折腾不起作用后,曾经意气风发的 Al Gore 拖着疲惫的身子,走上讲台,发表了认输讲话(参见 Al Gore《2000 Presidential Concession Speech》),从此退出政坛。一般国家领导人的退政生活其往往松愉快,出出日记,学用哲学,或者像多才多艺的李岚清不但去各地推广古典音乐,更是玩起了篆刻(参见《南方周末》2006 年 5 月 11 日《老常委的卸任生活》),克林顿先生都成立个基金会来帮助社会预防和治疗爱滋。 Al Gore 也没闲着,他找到了让他感兴趣的去处——Apple 总部,并成为董事之一。
Mac OS X 和 Al Gore 的双赢
2003 年 5 月 19 日,Apple 的启示中罕见性地登出了《前总统 Al Gore 加入 Apple 董事会》的快讯。文中提到,Al Gore 总统是一个正宗果粉,他一直用 Mac 计算机,而且还会用 Final Cut Pro 来编辑他的视频。Al Gore 也不掩饰他对 Apple 技术的热爱,他表示对 Mac OS X 的开发极感兴趣,并且也对 Apple 在开放源代码运动中的贡献喜闻乐见。他虚心地说,想在这个让 Apple 起死回生的董事会好好观摩并学习。
Al Gore 的加盟让 Apple 一跃成为电子产品的代言人
苹果公司的 CEO Steve Jobs 表示 Al Gore 曾经管理过世界最大的组织——美国政府,期间显示出的经验和智慧对苹果公司是笔巨大的财富。Al Gore 将成为出色的董事会主席,苹果将以他把苹果公司作为职业生涯的开始为荣。
这之后,Al Gore 在 Apple 内部的决策究竟起了什么作用,和 Mac OS X 的开发有何关联,在正式的渠道很少有史料,但是他后来的各种公开活动,却给 Mac OS X 的技术做足了广告,而且很多证据表明,他正是使 Apple 从被绿色人士攻击的众矢之的的状态,成为业界注重电子产品环保领头羊的主要推手。
Al Gore 重新进入普通人的视野是在 2006 年,他推出了自己参与制作和演出的纪录片《An Inconvenient Truth》(《难以忽视的真相》)和同名书籍。这部长达 94 分钟的影片,在西方国家引起了广大的回响,以 Al Gore 的一场演讲和人生的回忆作为两条主线,详细、科普地向民众介绍了全球变暖问题的科学证据及美国政府掩盖问题的真相。该片以发人深省的立意、详尽的科学数据、平实的讲演风格,加上苹果高超的技术,而获得了广泛的好评并一举获得年度奥斯卡最佳纪录片奖,使得这位美国前副总统摇身一变,成为好莱坞明星。
为什么单单一场简单的讲话,就能做出一部电影,还能得到奥斯卡这样学院艺术奖的亲睐?是因为讲话内容无懈可击么?事后有很多科学家站出来表示,虽然影片内容有积极的意义,但其实也有很多被夸大的科学数据、假设和结论。试思索,该片之所以成功,甚至成为诸多演讲培训机构的重要分析案例,除了数据、观点、论述外,还有以下几个原因。
首先,这场演讲由苹果主导的技术和艺术的设计。Al Gore 向来以说不清想表达的内容而著称。他经常因为讲得过于专业或者缺乏好的表述方法以致于民众完全不懂他在讲些什么。他的早期讲话用现在的眼光看就是个少将体,比如“互联网…网…我…这个…那个…那个…怎么说呢…我想这个…这…这…这…我啊…我啊…就是说…互联网是我发明的!”因此作为苹果展现公司软实力的重要机会,苹果非常重视这场讲话,请公司的图形设计小组带领完成各种所需设计,苹果甚至特地请来了专业的设计公司 Duarte 来进行讲稿和讲话内容的安排。因此,不管是内容安排、图形设计还是技术支持,Al Gore 都有强有力的后盾,他们能够帮助Al Gore 完成任何想达到的目标。不论是 FinalCut 还是 Keynote,一旦缺少任何 Al Gore 想要的功能,Apple 都可以给他开小灶实现。在片末的走马灯字幕中,有大量 Apple 的 Keynote 组、Final Cut 组和图形设计组的员工名字,以示鸣谢。
其次,上面这些资源的相互合作,也使得 Al Gore 的这场讲话的讲稿被精心制作,体现了精心设计的电子稿演讲所能达到的最高成就。苹果公司向来重视演讲,也是各大企业中最会通过演讲来营销产品的公司。每年的 MacWorld 和 WWDC 的 Steve Jobs 讲话都会吸引百万人在计算机前观看。每场讲话都好戏连连,台下的观众的欢呼和掌声不亚于著名歌星的演唱会。这种风格显然给 Al Gore 的讲话风格带来很大的影响。在影片中,观众看不到一个传统的 bulletpoint(PowerPoint 用户常爱使用的表示讲话结构的方法),取而代之的是高清的照片、视频,来展现环境的严峻性。观众不再会为枯燥无味的技术词语而搞得昏昏欲睡,因为屏幕上的一切都是如此真实,各种科学现象由动画效果配合,使其浅显易懂。另外,所有的数据、图表都精心使用软件制作,使其一目了然,表现准确而美观大方,而且 Al Gore 时而还会玩些小噱头,比如讲到现在的温室气体浓度是多么高时,他甚至爬上工作人员为他准备的升降机,升到舞台顶端,来告诉观众,数据已经突破图表的顶端了。现在距笔者观赏完这部影片,已经五年过去了,但影片中的灾难场景、冰川融化的影片段落、海平面上升的计算机模拟、二氧化碳浓度的数据图表,至今都记得一清二楚,足以见得其表现力是何等深入人心。甚至有人在调侃他在 2000 年的竞选演说是怎么回事?难道就是缺少了这些科技元素?
最后,Mac OS X 的各项技术也是这部片子的重要保证。Duarte 公司的 Ted Boda 表示(该幻灯片的设计师之一),Mac OS X 系统本身的反锯齿功能把文字、图片、矢量图标表现得栩栩如生,使得幻灯片充满美感。QuickTime 技术作为 Mac OS X 的一块重要基石,又使得 Keynote 不需任何插件就能引入任何图片和影像,所以类似使用 Illustrator、Photoshop、AfterEffects 等软件做出的图片、影像或动昼,不需要任何转换过程就能直接拖到 Keynote 中。哪怕 1920×1080 的高清视频,都可以轻松插入,流畅播放。他们组根本想象不出在 Windows上 使用 PowerPoint 会成什么样子。
可以说,没有 Mac OS X,就没有这部电影。而实际上这部电影的作用远胜过任何一部 Apple 公司的广告。片中 Al Gore 时时拿着 PowerBook 的笔记本,在办公室用 Safari 查网页,字体渲染真实而美观,甚至在车上都不忘打开笔记本用 Keynote 做几张幻灯片,就更不用说电影中 Keynote 幻灯片曾经迷倒多少 Windows 用户了。向笔者推荐这部电影的好朋友了解到这些全是 Apple 技术的功劳时,拥有一台 Mac 就成为其人生梦想。
环保卫士的 Apple 之路
作为环保人士,Al Gore 对 Apple 的策略的影响也不容忽视。Apple 向来被各环保组织长期批评,即使 Apple 长年不断地改进这方面问题,但绿色人士依然不买帐。哪怕在稍后的 2007 年,也仍有包括 GreenPeace 在内的七十多个组织联名写信给 Al Gore,敦促 Apple 更重视环境问题,信中指责 Apple 仍在大量使用 PVC 和 BFRs 等对环境有害的材料,也不注重对自家产品的回收。由于 Al Gore 是 Apple 董事会成员,使得这个问题受到了 Apple 的广泛关注。Apple 在 2007 年后史无前例地迈开大步,大力推广环保计划(要求全世界的 IT 制造商们逐步弃用 PVC 等有毒的化学用品进行生产),让 Apple 一跃成为注重电子产品环境保护问题的领头羊。
从制造材料上,2007 年 8 月发布的 iMac 成为分水岭。这款产品的设计主要使用可完全被回收的玻璃屏和铝外框,减小了塑料等不环保物质的使用,此后苹果一发不可收拾,把这项革命进行到底,从手机到笔记本,都全番设计。2008 年的 MacBook Air 引出的 Unibody 技术是这场革命的代表产品,不但在外观上还是工程上做到极致,在环保上更是让各绿色组织无可挑剔。
在造势上,Apple 现在每项主要产品的都有“环境”的标签页,从制造、运输、耗电、回收等性能情况分产品详细列出。Apple 甚至在包装上都动足脑筋,尽量减少每个产品的包装,使得同一架飞机可以运输更多的产品,从而在运输相同数量产品的情况下减少飞机温室气体的总排放量。
Mac OS X 的各项节电功能的开发更是不用说了。休眠、调整空闲时的屏幕亮度、硬盘转速等常规功能自然越做越好。而系统的多项技术能使程序更优地分配使用中央处理器和显示卡。甚至系统还能在用户打字时,每两键之间的空隙减少处理器的占用从而节省击键之间的功耗,这使得 Mac OS X 不但更节约能源,笔记本的电池使用时间也不断提高。而这一切的变化,和 Al Gore 似乎都有着千丝万缕的联系。
由于《An Inconvenient Truth》中的讲话让 Al Gore 的观点深入人心,同时也对美国政府在京都议定的决策产生重大的压力,挪威诺贝尔委员会决定把 2007 年的诺贝尔和平奖颁给了 Al Gore,以表彰其在全球环境问题方面的努力,同时苹果的主页上全版刊发新闻,以示祝贺。贺词如下:
Al has put his heart and soul, and much of his life during the past several years, into alerting and educating us all on the climate crisis. We are bursting with pride for Al and this historic recognition of his global contributions. (Al Gore 在过去几年殚心积虑,全身心地投入对公众关于气候危机的警示和教育中。我们为他这次所得的荣誉和他全球性贡献的历史性承认感到无比自豪。)
或许,由于 Al Gore 在计算机领域的一贯低调(他也是 Google 的高级顾问),他在这些企业的工作很少被报道出来,但是他在政界的跨界身份是显而易见的。Al Gore 在他的人生道路将何去何从,我们不得而知,但是从各种媒体信息的披露可以看出,Al Gore 对计算机事业的热衷,对环保问题的投入,可能是美国历任领导人中最突出的。
Mac OS X 背后的故事(五)Jean - Marie Hullot 的 Interface Builder 神话
Interface Builder,是用于苹果公司 Mac OS X 操作系统的软件开发程序,Xcode 套件的一部分,于 1988 年创立。它的创造者 Jean-Marie Hullot 自称是“一个热爱旅行、充满激情的摄影师”,本篇分享 Hullot 热爱技术的那一面——创造 Interface Builder 的过程。
因势而动
1981年, Jean-Marie Hullot 拿到巴黎第十一大学的计算机科学博士资格后,开始了法国国家信息与自动化研究所(INRIA)的研究生活。
Jean-Marie Hullot 的名字似乎不为大众所熟知,但他设计的 Interface Builder 却深入人心,创造了一个个软件神话。
20 世纪 70 年代初,正是面向对象程序设计开始走上历史舞台的时期。许多现代计算机技术的诞生地 Xerox PARC(施乐帕洛阿尔托研究中心)的 Alan Kay、Dan Ingalls、Ted Kaehler 、Adele Goldberg 等人,从 1969 年开始研发一款面向对象的程序语言 Smalltalk,并于 1980 年正式公布。这是一个完整地实现面向对象范型的编程套件,包含了一种面向对象的程序设计语言、一种程序设计库和一个应用开发环境(ADE)。
虽然当时的机器跑得巨慢无比,但 Smalltalk 先进的思想对其他众多的程序设计语言(Objective-C、Actor、Java 和 Ruby)的产生起到了极大的推动作用,对计算机工业界的发展产生了非常深远的影响。我们将会在今后介绍 Objective-C 时,详细介绍 Smalltalk 及其对 Objective-C 的影响,这里先一笔带过。
Smalltalk 的发布在业界一石激起千层浪,也给 Jean-Marie Hullot 幼小的心灵带来了巨大的震撼。他立即明白了面向对象思想所代表的先进生产力,一定会改变今后数十年的程序设计流程,他毫不犹豫地成为面向对象编程模式的早期粉丝。
SOS 的助力
那时,Jean-Marie Hullot 使用早期的 Macintosh 计算机进行开发。不过他很快就和其他开发者一样,发现虽然 Mac 的用户界面做得不错,但开发程序实在是太糟糕了。他说:“当 Macintosh 被发明出来时,计算机和先前就大不一样了,你至少需要花 60%~70% 的时间在用户界面部分的代码上。”在 Macintosh 被发明之前,用户界面是相当简单的,只需要在命令行下面打一串字符,计算机就会回应出一行行的信息。所以在那个时代,开发者完全不需要专注于用户界面。而 Mac 一经发布,随之而来的众多的窗口和菜单,让整个世界都不一样了。虽然对于使用最终产品的用户而言是简单方便的,但对于码工来说简直是个噩梦。每次他们需要一个窗口或者菜单,都要从零开始构建。
聪明的 Hullot 开始动脑筋改进 Mac 编写用户程序难的现状。他开发了一个程序,有点像现在 Windows 系统中的“画板”。一侧的工具条,是类似菜单这样的大量可重用的对象;而另一侧,则是程序员想构建的用户程序界面。只要把工具条上的工具拖放到程序界面中,那么类似“打开”、“打印”等相关的功能,就可以被添加到用户界面中。事实上,这个程序,是最早的一批能通过鼠标把控件拖入界面设计窗口实现相应功能的商业程序,是用户界面设计软件的先驱。
这个跨时代的发明被称作 SOS,用 Lisp 语言编写【注:What are we going to called this thing 中认为此时就是 Interface Builder,但据 The NeXTonian 等多处资料表明,在 Steve Jobs 见到以前,该程序名为 SOS】。当时,ExperTelligence 开发了一种叫做 ExperLisp 的方言,SOS 即用此语言写成【注:http://en.wikipedia.org/wiki/Interface_Builder】。
此时 Hullot 忽然意识到,他设计的东西事实上很强大,其重要性简直可以和 Smalltalk 这样的发明相比——Smalltalk 让开发者尝到了面向对象语言的甜头,而 SOS 则是直接把对象放到了开发者手边。有了这么拽的东西,Hullot 意识到如果他只在研究所窝着,那只能让十几个人享受这一成果,而如果他跳槽,把这个工具公开,那对天下的码工来说可是大福音。
诞生之源
经过不断努力,Hullot 找到了一个值得推销自己发明的好地方——剑桥的苹果大学联盟(Apple University Consortium)。这个苹果和大学合作的组织看到 Hullot 的创作后反响很好,就推荐他去见 Jean-Louis Gassee。 Jean-Louis Gassee 是个法国人,时任苹果开发研究院主任,见到 SOS 后也认为这是个好东西,便说服他去美国闯一闯。经过几次的鼓励和推荐,加上美国对 Hullot 来说又不陌生,于是他就买了机票跳上飞机就奔赴美国。
不过当 Jean-Marie Hullot 来到美国加州苹果总部时,他却认为这不是一个工作的好地方——苹果已经是一个很庞大的企业,很难再有所创新发展。他最终决定不留在那儿,转而在美国寻找一个能把这个产品卖出去的人。四处推销之后,找到他用来写 SOS 的 Lisp 解释器的生产商,就是刚才提到的位于 Santa Barbara 的软件公司 ExperTelligence。
事实上,当时的 ExperTelligence 正在寻找合作商卖自已的 Lisp,而 Hullot 也在找合作商卖自已的 SOS,两者一拍即合,随即打电话给 NeXT,共同推销自家的产品。
NeXT 在 Palo Alto 总部的产品市场部人员接待了 Jean-Marie Hullot 和两位来自 ExperTelligence 的员工,被 SOS 的理念镇住,遂打电话请 Steve Jobs 下来看。Jean-Marie Hullot 像复读机一样又把自己的大作秀了一遍。老谋深算的 Steve Jobs 事实上早就看中了 SOS,但他对 ExperTelligence 的 Lisp 一点兴趣都没有。所以他装作对这场演示毫无兴致【注:这有很多引用该文的翻译译错,原文说 nonplussed,字面意思为惊异,但在美国非正式表述中,此字表毫无兴致】,挥挥手就把这三个人打发走了。
但当他们一行人走到停车场时,Steve Jobs 让他手下把 Hullot 追了回来,当他只身回到 NeXT 总部时,发现 Steve Jobs 正恭敬地等着他。
“我想要你计算机上那个程序”【注:http://rixstep.com/2/0/people/】,Steve Jobs 说道:“你大概什么时候能开始给我们工作?”
Hullot 回答说自己翌日就要离开去度假。
“好吧,我两周后给你打电话,”Steve Jobs 说。
“不行,老乔”,Hullot 表示:“我不游美国,我可要环游欧洲,你七个礼拜后再打给我吧。”
Steve Jobs 虽然一骨子傲气,但他明白一个简单的道理:21世纪最缺的是什么——是人才!即使 Jean-Marie Hullot 玩起了大牌,这电话自然还是要打的。Hullot 刚一度完假回来,Steve Jobs 的电话就如期而至。
如此三顾茅庐般的热情,把 Jean-Marie Hullot 感动得第二天就登上了去美国的飞机。合约签了半年,但实际上他最终在 NeXT 整整待了十年。在 NeXT 工作期间,他使用 Objective-C 和 NeXTSTEP 框架重写了 SOS,命名为 Interface Builder。由此,Interface Builder 成为 NeXT 集成开发环境 Project Builder 标准套件之一。
进阶与探索
Interface Builder 和 SOS 一样,提供了一个工具箱,包含一系列用户控件对象。工具箱并不是官方定死的,而是可以任意扩展的,比如如果用户想使用类似 Safari 中的 toolbar,而这不是官方提供的,则下载第三方的 PSMTabBar 即可实现,甚至连 Cappuccino 这样的网页框架也可以用 Interface Builder 来完成设计。开发者只要把控件比如菜单和文本框拖入项目文件就能完成用户界面设计,节省了几乎所有和控件放置有关的代码。
开发者拖拽鼠标,将控件可提供的动作(IBAction)和另一个对象的接口(IBOutlet)连在一起, 则建立了一个绑定。这样,一旦动作被激发(比如用户点了按钮),那接口中相应的方法则会被执行。所以,大量对象关联的代码也能被省去。
有了这样的模式后,Interface Builder 和 Cocoa 可以比后来出现的 Microsoft Visual Studio 或 Qt Designer 等软件走得更远——只要是对象,Interface Builder 就能够操控它们,不需要一定是一个界面的控件。比如,数据库的数据源、队列等,都可以在 Interface Builder 中连接起来,于是很多原本需要上千行的复杂应用(比如用来显示、修改企业中职工姓名、部门、电话、地址、头像等信息 SQL 数据库的用户界面程序),数分钟内就可以写完,不用一行代码。不信?让 1992 年的 Steve Jobs 亲自做给你看【注:http://www.youtube.com/watch?v=j02b8Fuz73A, 第 23 分钟~第 29 分钟】。
NeXT 被 Apple 收购后,苹果把下一代操作系统建立在 NeXTSTEP 的基础上。Objective-C 和 Cocoa 被作为主要框架,而 Interface Builder 和 Project Builder 也因此受到重用。就官方的工具箱而言,支持 Objective-C/Cocoa、Carbon 的 HIToolbox 和 WebObject。
2008 年 3 月 27 日,苹果发布首个 iPhone SDK,设计 Cocoa Touch 界面的,也正是 Interface Builder。可以说,Interface Builder 一直随着公司产品的发展而不断拓新。
Jean-Marie Hullot 是在 NeXT 被收购时进入苹果的。Steve Jobs 令他率领在法国的一个小团队,秘密为 Mac OS X 10.2 开发一个办公软件。以往这样量级的程序,都是由苹果加州总部的大班人马完成。而这次,为了向世人表明他的 Interface Builder 有多强大,iCal 横空出世,展示复杂的界面元素(日历、可拖拽的任务、五花八门的分类)和诸多功能(网络同步、Apple Script 脚本控制)可以用相当快速的时间内开发出来【注:http://www.appleinsider.com/articles/07/10/17/road_to_mac_os_x_leopard_ical_3_0.html&page=2】。
最后,在 iCal 小组打完酱油的 Jean-Marie Hullot 荣升苹果软件开发部首席技术官。
Project Builder 在 Mac OS X 10.3 时被重命名为现在大家所熟知的 Xcode。Xcode 3 以前,Interface Builder 使用一种名为 nib 格式的二进制文件格式。不过由于 nib 不能用肉眼读,也不方便使用版本管理工具来管理,所以 Xcode 3 开始新加入一种名为 xib 的文本文件格式,最后再在项目编译阶段输出为 nib 格式。和产生静态界面布局代码的工具(如 MSVC、QtDesigner、 Delphi 等类似的软件)很不同,nib 是不被转译成相应 Objective-C 代码的。用户程序执行时,nib 文件被读入,解包,并且唤醒【注:awake,即载入 nib 会自动调用程序中 awakeFromNib 方法】,所以 nib 文件是在运行时动态加载的。
长期以来,Xcode 环境和 Interface Builder 是两个独立但相互工作的程序。而 2010 年释出的 Xcode 4 预览版中,Xcode 和 Interface Builder 合二为一,成为一个一体化的编程环境。所以现在,开发者甚至可以只用鼠标在用户界面和代码间来回拖拽就能完成,这样一来 Interface Builder 对用户代码的解释也比先前更正确。比早期分离的程序使用起来确实方便很多。
当然,一个负面的影响是,这样用一体化集成开发环境写程序,往往会发现屏幕空间是不够的,所以像我这样用 11 寸 Air 或者 13 寸 Macbook Pro 的人,出去打招呼都不好意思说自己是做 Mac 开发的。
下一个海阔天空
在而后的岁月里,Interface Builder 创造了一个又一个应用软件神话,小到官方教程中的汇率计算器,大到苹果所有的家用、专业软件,都由 Interface Builder 完成。
在风起云涌的 1989 年,欧洲核子研究组织(CERN)工作的科学家 Emilio Pagiola 忽悠经费,买来研究所的第一台 NeXT 计算机——当时 NeXT 计算机在 CERN 可是个新鲜事物——那里的科学家们纷纷前来把玩,普通青年发现里面有全本的韦氏词典,并可自动检查用户输入的拼写错误,技术青年发现它跑的是 Unix 系统,还有一个可读写的光驱,文艺青年更是发现里面居然预装了莎翁全集。不过毕竟像 Emilio Pagiola 这样忽悠巨款买 NeXT 机器的青年不多,所以大家围观完了,也就回去该干嘛干嘛了。
但 Tim Berners-Lee 和别人不一样,他不仅围观了那台计算机,还看到了 Jean-Marie Hullot 设计的 Interface Builder,研究了 Objective-C,发现了面向对象编程范式开发环境的最高成就。这情景让他心中漾起了巨大的波澜,最终化为激情澎湃的投入,汇成了一行行面向对象的代码,一泻千里,奔向未来。
一年后,世界首个 HTTP 服务在 CERN 的 NeXT 计算机运行起来,而使用 Objective-C 和 Interface Builder 所编写的超文本语言编辑器兼浏览器同步发行。他给这个主从式架构起了个好听的名字——World Wide Web(万维网)。
Aqua 是 Mac OS X Public Beta 全新用户界面的名字,英文中为水的词根,寓意以水为灵感,精心设计。Steve Jobs 曾介绍说,Aqua 的设计是如此之美好,初次见它甚至有想亲吻的冲动。本篇 Cordell Ratzlaff 引发的 Aqua 革命(上)介绍的是 Aqua 的起源和来历,在下篇中,我们将展示 Aqua 的具体设计过程。
“Mac OS 的图形界面就是你们那么业余的人设计的吗?” Steve Jobs 开门见山地问。
包括 Cordell Ratzlaff 在内的设计师们怯怯地点头称是。“你们就是一群白痴!” Steve Jobs 骂道。
这个场景发生在 Steve Jobs 回归不久的图形界面组组会上,前文提到的骂人的话,是他送给图形界面设计组的见面礼。【注:参见 http://www.cultofmac.com/how-mac-os-x-came-to-be-exclusive-10th-anniversary-story/87889,How Mac OS X Came To Be,Leander Kahney】
不进则退的局面
Mac OS 曾是图形界面设计的先驱。
从 System 1 开始,Mac 就打破了字符终端的模式,使用图形界面和用户交互设计。但自 System 1 到 System 7,10年过去了,界面却始终没有显著的变化。设计组一直认为,为尊重用户的习惯,定下的规矩不要轻易改动。但同时,Microsoft 的变化可以说是天翻地覆,从黑屏的 DOS,到全屏幕的 Windows 1,再到成熟的 Windows 3,最后演变到奠定当今 Windows 界面基础的炫丽多彩的 Windows 95。用当时的眼光来看,这个变化是相当惊人的。由于因循守旧,Mac OS 在界面设计上从领先掉到了最后。旧的界面原语,一成不变的界面风格,让 Mac OS 的图形界面在 Windows 前显得黯然无光。【注:参见 http://vimeo.com/21742166】
于是,在图形界面组的组会上,Steve Jobs 抨击了老 Mac OS 界面的各种不是——几乎所有的地方都被骂了一遍。众矢之的是各种打开窗口和文件夹的方式。在 Mac OS 中有至少 8 种打开窗口和访问文件夹的方式,如弹出菜单、下拉菜单、DragStrip、Launcher、Finder 等不同的程序。
Cordell Ratzlaff 作为主管,他一开始担心是不是会被 Steve Jobs 炒掉(传闻说 Steve Jobs 刚进入苹果时最爱炒人,经常会发生一些“神奇”的情况,比如有员工和他一同进了电梯,等一同出电梯时,该员工已被炒掉)。不过批评大会进行到第 20 分钟时,Cordell Ratzlaff 转为淡定,因为他意识到如果 Steve Jobs 要炒他,不用废那么多话,早就可以动手了。
其实 Cardell Ratzlaff 是 Apple 内部较早意识到小组设计不思进取的人之一。他意识到苹果有三个重要的设计问题【注:参见 Designing Interactions 第二章 My PC 附录访谈】。第一、Apple 的很多界面语言不明确。例如,在老 Mac OS 中,删除文件的动作是把文件图标拖到废纸篓里,但当磁盘和光盘弹出时,居然也是把图标拖到废纸篓里。第二、老 Mac OS 不会对问题进行变通,如果有几个图标同时显示,窗口还容易操作,但如果有几十个图标或窗口,以相同的方式显示出来,那么在繁杂的页面中找寻所需内容,对使用者则是巨大的挑战。第三、Mac OS 的界面过于古板,看上去还是停留在 Windows 3.0 阶段。总之,当时的 Mac OS 已经不能代表先进的生产力,也不能代表科技的前进方向,更不能让广大用户得到更多的利益。在 Cardell Ratzlaff 看来, Mac OS 的界面面临不进则退的重大困局,非改不可。
Cordell Ratzlaff 的试水
收购 NeXT 以后,Apple 开始考虑如何把 NeXTSTEP 作业系统变为下一代的 Apple 操作系统,但界面设计组的倦怠又浮出水面。设计组认为,这是一个浩大的工程,所以他们决定照着 Mac OS 8 的样子改 NeXTSTEP 的代码,把 NeXTSTEP 改成 System 8 的样子。这并不困难,组里只需一个人就能完成这项任务,这人的工作极其无聊——像小孩子描红模,把新界面的样子临摹得和老界面一模一样。事实上,当 Apple 释出 Rhapsody 和 Mac OS X Server 初版时,经典 Mac OS 的界面已经被学得惟妙惟肖了。
Cordell Ratzlaff 认为这种混搭,是一个极其让苹果丢颜面的事情。所以,除了那个搞山寨的人以外,他召集其他人做新界面设计的图样。而由于 NeXTSTEP 具有强大的图形处理和动画能力,因此很多新的图样是在新系统上完成的。
Apple 将“What's not a computer!”(看起来不是电脑的电脑)的概念应用在硬件外观上,设计出具有浪漫主义气质,半透明“果冻” 式且具有艺术美感的 iMac,这成了 Aqua 设计灵感的来源。
20世纪 90 年代初,Apple 和 Microsoft 的操作系统都素面朝天,色调简单,统一的矩形窗口。到 1997~1998 年,Apple 的硬件外观设计取得重大进展:由后来成为金牌设计师的 Jonathan Ive 领衔,设计出具有浪漫主义气质、五彩斑澜的、半透明外壳、具有曲线美感的 iMac,这个设计成为 Cordell Ratzlaff 和他的同事们设计的灵感,他们马上就作出了一个全新的界面图样来。【注:参见 http://en.wikipedia.org/wiki/IMac_G3】
与此同时,Cordell Ratzlaff 着手解决前文提到的三个设计问题。第一、他提出了一个叫“实时状态”的概念。当用户拖动文件时,废纸保持原样,而如果拖动的是磁盘,那废纸篓的图标变成“弹出”的图标。第二、窗口的问题统一采用动画加以解决。比如窗口的最小化和还原都配有动画,告诉用户窗口的来去方向。当 Dock 项目有所增减时,项目长度和元素也会随之改变。第三、Mac OS 一改死板面孔,呈现多彩的、小清新的图形界面,所有尖锐的直角都被打磨成圆弧,并且有像 iMac 外壳一样半透明的菜单。当时有评论指责 Apple 的设计太卡通缺乏权威感,其变化之大可见一斑。【注:参见 http://www.aresluna.org/attached/files/usability/papers/onethousandsquarepixelsofcanvas.pdf,One thousand square pixels of canvas On evolution of icons in graphical interfaces by Marcin Wichary 第五页】
Cocoa 之父 Bertrand Serlet,作为 Cordell Ratzlaff 的上司,对新界面很满意。但当时,他们认为这个新界面实现起来难度很大,既没有时间也没有资源把这个想法在 Mac OS X 中付诸实现。于是先前那位孤独的照葫芦画瓢的设计者只好继续工作。
Aqua 只是个设想(PS 出来的图样+模拟出来的视频),还不是能用的代码。
Steve Jobs 的怒火和 Aqua 的源头
几个月以后,Apple 举办了一个所有开发小组参加的长达两天的汇报大会。Cordell Ratzlaff 汇报的时间被排在两天的最后压轴出场。大多数工程师对这长达两天的大会报告早已疲倦,感叹 Mac OS X 剩下的的工作很艰巨,认为发布遥遥无期。于是,Cordell Ratzlaff 报告成了整个报告会的最大笑场,所有工程师使出咆哮体来评价这个工作——“啊!!!你看这新界面多出位啊!!!有没有有没有!!!居然用的透明通道!!!还搞个实时的动画!!!你难道不知道你这些永远是天方夜谭不可能完成吗???我们工程师伤不起啊伤不起!!!”这个新设计就这样在所有 Apple 顶级工程师的鄙视下被废了。
无奈于此,只好无聊地让那位开发者继续复制全套经典 Mac OS 界面,而当 Steve Jobs 召集所有设计组负责人时,这个山寨版 Mac OS 的展示把 Steve Jobs 看得情绪激动,就发生了文章开头的那一幕。
Cordell Ratzlaff 前来解释压轴报告的尴尬局面,暗示千里马常有而伯乐不常有的处境,还让 Steve Jobs 观摩了他的杰作。果然 Steve Jobs 看了这几张图例后大为惊异,拍着 Cordell Ratzlaff 的肩说:“很好!很强大!”然后让设计组不惜一切代价做成试验品。
在加班奋战的三周后,设计组用 Macromedia Director 完成了一个试验品。Steve Jobs 亲自来 Cordell Ratzlaff 办公室视察了一下午。结果是他激动地握着 Cordell Ratzlaff 的手,吐露心声:“你是苹果里我见到的第一个智商是三位数字的人。”得到了 Steve Jobs 的支持,Apple 的 Mac OS X 开发团队,更加紧密地围绕在以 Cordell Ratzlaff 为核心的界面设计概念周围,开发操作系统。
有缘千里来相会,无缘对面不相识。Steve Jobs 和 Cordell Ratzlaff 算是相见恨晚。这样由 Cordell Ratzlaff 主导的新界面,在 Steve Jobs 的支持下,横扫一切困难,成为新版操作系统界面的最大亮点。
从这时到 Steve Jobs 正式在舞台上秀他的 Mac OS X Public Beta,还有 18 个月。此时,系统界面革命的旅程已经开始,一道神秘的天光射向 Infinity Loop,千古杰作 Aqua 就要在这里诞生,其光辉历程,我们下篇再谈。
Mac OS X 背后的故事(七)上善若水下——Cordell Ratzlaff 引发的 Aqua 革命
在前一节中讲到,Cordell Ratzlaff 新界面方案得到 Steve Jobs 的高度肯定,Steve Jobs 让各开发组紧紧围绕在界面设计组周围,共同建造 Mac OS X。此时,离 Mac OS X 第一个公共测试版的发布,仅有一年半时间。这时苹果的设计构想,还仅仅是个概念,在本篇中我们将展示 Aqua 的具体设计过程。
设计与软件的融合
开发分设计和软件两条路并行走,“两手抓,两手都要硬”。
设计是个有趣的领域。有些人认为,设计就是产品的外观看上去什么样。但其实,如果细想一下,你会发现设计其实是有关产品如何工作的学问。
——Steve Jobs
首先,苹果定下计划,并规划整个界面设计元素的方案,把设想通过可操作性强的材料让工程师来实现。
Cordell Ratzlaff 每周都要和 Steve Jobs 开会,向他展示界面设计小组最新成果。任何大家现在见到的各界面控件,如菜单、按钮、进度条、Steve Jobs 都一一过目,毫不马虎。针对每一个控件,Cordell Ratzlaff 会要求拿出多套方案来,让 Steve Jobs 选出他中意的。Steve Jobs 也会提出各种他自己的见解和改进建议,而 Cordell Ratzlaff 则会根据这些回馈不断修改,直到 Steve Jobs 满意为止。
与此同时,软件工程师也以越来越重的比例加入到这个设计行列中。
图形界面设计小组使用的设计软件是 Macromedia Director。它能做出演示用的动画,可以演示打开、关闭窗口、下拉菜单等模拟效果,但这些并不是可供用户使用的最终软件。软件工程师需要把图形界面设计师的设计,变为一行行代码,运用到 Mac OS X 中。所以每次会议的 Macromedia Director 动画演示机旁,还会有一台计算机,预装了软件工程师转换的代码。当工程师们向 Steve Jobs 展示最新代码如何工作时,Steve Jobs 会身体前倾,鼻子快贴到荧幕上,观察细微到“像素级别”来比较软件的表现和之前的设计是否完全一致。如果他有发现任何细微的差错,一阵类似“你们全是一帮白痴”的腥风血雨就会在办公室中展开。
设计整套方案是一个令人难以置信的漫长过程,尤其是遇到追求完美的 Steve Jobs。Mac OS X 中有一个控件叫滚动条(NSScroller)。当需要显示的内容长于当前控件大小时就会出现滚动条,可上下翻阅内容。这是一个非常不起眼的控件,大多数时间,用户甚至注意不到它的存在,甚至在十年后的今天它都被默认不显示了(关于 Lion 图形界面的改动受 iOS 思潮的影响我们今后会提到)。但哪怕是这种不起眼的细节,Steve Jobs 都偏执地当个大项目来做。Mac OS X 的界面设计是有史以来最复杂的一个,需要考虑诸多因素——比如所在窗口的活动与否,都会影响这个控件的颜色等属性。就滚动条而言,箭头的大小、位置的变化、颜色的启用等全都是活动的属性,牵一发而动全身。一根看似简单得不能再简单的滚动条,设计组花了整整六个月来修改。
当时,Mac OS X 的用户界面有两个重大的设计目标:第一是让老用户没有压力地迁移过来,且倍感新界面的好用;第二是让那些从未摸过 Mac 的人尽快上手,并称赞这界面很好很强大。所以,整个界面设计保留了老 Mac OS 界面元素的设计理念,但同时又对很多有问题的老设计进行了革新。比如,在老版 Mac OS 中,各种系统设置选项是隐藏在不计其数的系统扩展、控制面板,以及很多系统组件中的。用户要想联个网,要去五六个地方设网络、设 IP、设连接设密码,而在 Mac OS X 中,所有这些设置都被分门别类地规类到一个单一的程序——系统首选项(System Preferences),让用户“足不出户”,就能进行一切相关设置。
精简的狂热追求和大胆的设计创新
Apple 偏爱最简化的设计,而往往满屏的窗口让 Steve Jobs 忍无可忍。又酷又炫的 Dock 横空出世,巧妙地解决了这个问题。Dock 的设计源于 Mac OS X 的前身 NeXTSTEP,但在 Mac OS X 中完全被重写,并重定义了它的功能。Dock 提供用户一个放置常用软件图标、闲置窗口、文档的场所,Steve Jobs 说“任何东西都能被拉进 Dock”。但 Dock 真正神奇的,是它犹如多拉A梦的口袋,有无限的承载能力。当放入 Dock 中的东西变多时,它会自动把横向宽度变长、图标变小,可承载几十个窗口。当窗口缩入和还原时,都配有“精灵”一样的动画——在 Dock 的图标多的时候,每个图标很小,用户就很难找到需要的——灵动且放大动画可以让用户能快速地找到所需。
另外,起初版本的 Dock 中每个图标都是正方形的方块,被换成半透明的背景,看得人垂涎欲滴。这些经典的设计,影响了整整一代图形界面设计者,被各山寨界面抄了一遍又一遍,甚至又活在当今的 Ubuntu Linux 的 Unity 和 Windows 7 中。
Apple 追求清爽甚至到了发疯的地步,在最初版的 Mac OS X Public Beta 中,每个窗口有一个按钮,只要按下,除了当前窗口外,其它一切都会飞入 Dock。因此,只要一键,“整个世界都清静了”。而在后来每个版本的 Mac OS X 中,都有大的更新来防止窗口或其他界面元素的堆积。10. 4 时代的 Expose,10. 5 时代的 Stack 和 Spaces,10.6 时代的 Expose 和 Dock 相结合双管齐下,到 10.7 时代的 Mission Control,都是用来解决果面精简这一个问题的。
而很多传统的界面控件也被赋予了新的含义。比如 Steve Jobs 觉得,“最大化”一个窗口没有实际意义,而且把整个窗口最大化,也会挡住后面的窗口(直到 2011 年,Apple 用“全屏”来重新定义传统的“最大化”)。而 Mac OS X 没有所谓的“最大化”,取而代之的是自动计算后调整窗口到所需大小的“最适化按钮”。而关闭一个窗口的含意也不该是关闭一个程序,而只应是结束目前的内容。Apple 的许多设计都格外具有魄力,完全重写了界面设计的教科书。当然,有许多地方 Apple 确实做得矫枉过正,比如 Apple 一直是我见过的只有拖住右下角才能改动窗口大小的唯一系统。这个置用户于不顾的狂妄设计,一直在十年后发布的 Lion 中,才得以改变。
Steve Jobs 一直是界面设计的重要顾问。他有时候会提出一些看似稀奇古怪的意见,但往往最终又被证明是好的。比如,有一次他在会上指出,窗口左上角的“关闭”、“最小化”、“最适化”三个按钮的颜色都是一样的灰色,不容易区分他们。他建议把三个按钮变成交通灯的颜色,并且当鼠标移到附近时,显示出相应的图形指示。当 Cordell Ratzlaff 一群人听到这个主意后面色大变,认为简直是计算机图形设计史上最好笑的段子——谁会把电脑当交通灯使啊。不过改完后,他们对 Steve Jobs 心悦诚服——“红灯给用户一个终止的警示,这个窗口要被关掉;黄灯表示这个窗口要被放入等待队列,以便以后再通行;最适化则是给这个窗口大开绿灯”——这样高明的比喻,使 Cordell Ratzlaff 对 Steve Jobs 崇拜得五体投地。
18个月转瞬即逝,“你们就是一群白痴”的骂声依旧清晰,而此时的 Mac OS X 的图形界面,已今非昔彼。
“语静声息。我走上舞台。依着那打开的门,我试图探测回声中,蕴涵着什么样的未来。”(北岛翻译的帕斯捷尔纳克的《哈姆雷特》)。
18 个月后的 2000 年 1 月,新世纪的钟声刚刚敲响,Steve Jobs 镇定地走上 MacWorld 大会的舞台,独领风骚的新世纪的经典大作 Aqua,此时,就要被他揭开帷幕。
Mac OS X 背后的故事(八)三好学生 Chris Lattner 的 LLVM 编译工具链
2011年 12 月 3 日,LLVM 3.0 正式版发布,完整支持所有 ISO C++ 标准和大部分 C++ 0x 的新特性, 这对于一个短短几年的全新项目来说非常不易。
开发者的惊愕
在 2011 年 WWDC(苹果全球开发者大会)的一场与 Objective-C 相关的讲座上,开发者的人生观被颠覆了。
作为一个开发者,管理好自己程序所使用的内存是天经地义的事,好比人们在溜狗时必须清理狗的排泄物一样(美国随处可见“Clean up after your dogs”的标志)。在本科阶段上 C 语言的课程时,教授们会向学生反复强调:如果使用 malloc 函数申请了一块内存,使用完后必须再使用 free 函数把申请的内存还给系统——如果不还,会造成“内存泄漏”的结果。这对于 Hello World 可能还不算严重,但对于庞大的程序或是长时间运行的服务器程序,泄内存是致命的。如果没记住,自己还清理了两次,造成的结果则严重得多——直接导致程序崩溃。
Objective-C 有类似 malloc/free 的对子,叫 alloc/dealloc,这种原始的方式如同管理C内存一样困难。所以 Objective-C 中的内存管理又增加了“引用计数”的方法,也就是如果一个物件被别的物件引用一次,则引用计数加一;如果不再被该物件引用,则引用计数减一;当引用计数减至零时,则系统自动清掉该物件所占的内存。具体来说,如果我们有一个字符串,当建立时,需要使用 alloc 方法来申请内存,引用计数则变成了一;然后被其他物件引用时,需要用 retain 方法去增加它的引用计数,变成二。当它和刚才引用的物件脱离关联时,需使 release 方法减少引用计数,又变回了一;最后,使用完这个字符串时,再用 release 方法减少其引用计数,这时,运行库发现其引用计数变为零了,则回收走它的内存。这是手动的方式。
这种方式自然很麻烦,所以又设计出一种叫做 autorelease 的机制(不是类似 Java 的自动垃圾回收)。在 Objective-C 中,设计了一个叫做 NSAutoReleasePool 的池,当开发者需要完成一个任务时(比如每开启一个线程,或者开始一个函数),可以手动创立一个这样的池子, 然后通过显式声明把物件扔进自动回收池中。NSAutoReleasePool 内有一个数组来保存声明为 autorelease 的所有对象。如果一个对象声明为 autorelease,则会自动加到池子里。如果完成了一个任务(结束线程了,或者退出那个函数),则开发者需对这个池子发送一个 drain 消息。这时,NSAutoReleasePool 会对池子中所有的物件发送 release 消息,把它们的引用计数都减一 ——这就好比游泳池关门时通知所有客人都“滚蛋”一样。所以开发者无需显式声明 release,所有的物件也会在池子清空时自动呼叫 release 函数,如果引用计数变成零了,系统才回收那块内存。所以这是个半自动、半手动的方式。
Objective-C 的这种方式虽然比起 C 来进了一大步,我刚才花了几分钟就和读者讲明白了。只要遵守上面这两个简单的规则,就可以保证不犯任何错误。但这和后来的 Java 自动垃圾回收相比则是非常繁琐的,哪怕是再熟练的开发者,一不小心就会弄错。而且,哪怕很简单的代码,比如物件的 getter/setter 函数,都需要用户写上一堆的代码来管理接收来的物件的内存。
经典教材《Cocoa Programming for Mac OS X》用了整整一章节的篇幅,来讲解 Objective-C 中内存管理相关的内容,但初学者们看得还是一头雾水。所以,在 2007 年 10.5 发布时,Objective-C 做出了有史以来最大的更新,最大的亮点是它的运行库 libobjc 2.0 正式支持自动垃圾回收,也就是由运行库在运行时随时侦测哪些物件需要被释放。听上去很不错,可惜使用这个技术的项目却少之又少。原因很简单,使用这个特性,会有很大的性能损失,使 Objective-C 的内存管理效率低得和 Java 一样,而且一旦有一个模块启用了这个特性,这个进程中所有的地方都要启用这个特性——因此如果你写了一个使用垃圾回收的库,那所有引用你库的程序就都得被迫使用垃圾回收。所以 Apple 自己也不使用这项技术,大量的第三方库也不使用它。
这个问题随 Apple 在移动市场的一炮走红而变得更加严峻。不过这次,Apple 和与会的开发者讲,他们找到了一个解决问题的终极方法,这个方法把从世界各地专程赶来聆听圣谕的开发者惊得目瞪口呆——你不用写任何内存管理代码,也不需要使用自动垃圾回收。因为我们的编译器已经学会了上面所介绍的内存管理规则,会自动在编译程序时把这些代码插进去。
这个编译器,一直是 Apple 公开的秘密——LLVM。说它公开,是因为它自始至终都是一个开源项目;而秘密,则是因为它从来没公开在 WWDC 的 Keynote 演讲上亮相过 。
一直关注这系列连载的读者一定还记得,在第二篇《Linus Torvalds 的短视》介绍 Apple 和 GPL 社区的不合时,提到过“自以为是但代码又写得差的开源项目,Apple 事后也遇到不少,比如 GCC 编译器项目组。虽然大把钞票扔进去,在先期能够解决一些问题,但时间长了这群人总和 Apple 过不去,并以自己在开源世界的地位恫吓之,最终 Apple 由于受不了这些项目组的态度、协议、代码质量,觉得还不如自己造轮子来得方便。”LLVM 则是 Apple 造的这个轮子,它的目的是完全替代掉 GCC 那条编译链。它的主要作者,则是现在就职于 Apple 的 Chris Lattner。
编译器高材生 Chris Lattner
2000年,本科毕业的 Chris Lattner 像中国多数大学生一样,按部就班地考了 GRE,最终前往 UIUC(伊利诺伊大学厄巴纳香槟分校),开始了艰苦读计算机硕士和博士的生涯。在这阶段,他不仅周游美国各大景点,更是努力学习科学文化知识,翻烂了“龙书”(《Compilers: Principles, Techniques, and Tools》),成了 GPA 牛人【注:最终学分积 4.0 满分】,以及不断地研究探索关于编译器的未知领域,发表了一篇又一篇的论文,是中国传统观念里的“三好学生”。他的硕士毕业论文提出了一套完整的在编译时、链接时、运行时甚至是在闲置时优化程序的编译思想,直接奠定了 LLVM 的基础。
LLVM 在他念博士时更加成熟,使用 GCC 作为前端来对用户程序进行语义分析产生 IF(Intermidiate Format),然后 LLVM 使用分析结果完成代码优化和生成。这项研究让他在 2005 年毕业时,成为小有名气的编译器专家,他也因此早早地被 Apple 相中,成为其编译器项目的骨干。
Apple 相中 Chris Lattner 主要是看中 LLVM 能摆脱 GCC 束缚。Apple(包括中后期的 NeXT) 一直使用 GCC 作为官方的编译器。GCC 作为开源世界的编译器标准一直做得不错,但 Apple 对编译工具会提出更高的要求。
一方面,是 Apple 对 Objective-C 语言(甚至后来对 C 语言)新增很多特性,但 GCC 开发者并不买 Apple 的帐——不给实现,因此索性后来两者分成两条分支分别开发,这也造成 Apple 的编译器版本远落后于 GCC 的官方版本。另一方面,GCC 的代码耦合度太高,不好独立,而且越是后期的版本,代码质量越差,但 Apple 想做的很多功能(比如更好的 IDE 支持)需要模块化的方式来调用 GCC,但 GCC 一直不给做。甚至最近,《GCC 运行环境豁免条款 (英文版)》从根本上限制了 LLVM-GCC 的开发。 所以,这种不和让 Apple 一直在寻找一个高效的、模块化的、协议更放松的开源替代品,Chris Lattner 的 LLVM 显然是一个很棒的选择。
刚进入 Apple,Chris Lattner 就大展身手:首先在 OpenGL 小组做代码优化,把 LLVM 运行时的编译架在 OpenGL 栈上,这样 OpenGL 栈能够产出更高效率的图形代码。如果显卡足够高级,这些代码会直接扔入 GPU 执行。但对于一些不支持全部 OpenGL 特性的显卡(比如当时的 Intel GMA 卡),LLVM 则能够把这些指令优化成高效的 CPU 指令,使程序依然能够正常运行。这个强大的 OpenGL 实现被用在了后来发布的 Mac OS X 10.5 上。同时,LLVM 的链接优化被直接加入到 Apple 的代码链接器上,而 LLVM-GCC 也被同步到使用 GCC 4 代码。
LLVM 真正的发迹,则得等到 Mac OS X 10.6 Snow Leopard 登上舞台。可以说, Snow Leopard 的新功能,完全得益于 LLVM 的技术。而这一个版本,也是将 LLVM 推向真正成熟的重大机遇。
关于 Snow Leopard 的三项主推技术(64位支持、OpenCL,以及 Grand Central Dispatch)的细节,我们会在下一次有整整一期篇幅仔细讨论,这次只是点到为止——我们告诉读者,这些技术,不但需要语言层面的支持(比如 Grand Centrual Dispatch 所用到的“代码块”语法, 这被很多人看作是带 lambda 的 C),也需要底层代码生成和优化(比如 OpenCL 是在运行时编译为 GPU 或 CPU 代码并发执行的)。而这些需求得以实现,归功于 LLVM 自身的新前端——Clang。
优异的答卷——Clang
前文提到,Apple 吸收 Chris Lattner 的目的要比改进 GCC 代码优化宏大得多——GCC 系统庞大而笨重,而 Apple 大量使用的 Objective-C 在 GCC 中优先级很低。此外 GCC 作为一个纯粹的编译系统,与 IDE 配合得很差。加之许可证方面的要求,Apple 无法使用 LLVM 继续改进 GCC 的代码质量。于是,Apple 决定从零开始写 C、C++、Objective-C 语言的前端 Clang,完全替代掉 GCC。
正像名字所写的那样,Clang 只支持 C,C++和 Objective-C 三种C家族语言。2007年开始开发,C 编译器最早完成,而由于 Objective-C 相对简单,只是 C 语言的一个简单扩展,很多情况下甚至可以等价地改写为C语言对 Objective-C 运行库的函数调用,因此在 2009 年时,已经完全可以用于生产环境。C++ 的支持也热火朝天地进行着。
Clang 的加入代表着 LLVM 真正走向成熟和全能,Chris Lattner 以影响他最大的“龙书”封面【注:见 http://en.wikipedia.org/wiki/Dragon_Book_(computer_science)】为灵感,为项目选定了图标——一条张牙舞爪的飞龙。
Clang 一个重要的特性是编译快速,占内存少,而代码质量还比 GCC 来得高。测试结果表明 Clang 编译 Objective-C 代码时速度为 GCC 的 3 倍【注:http://llvm.org/pubs/2007-07-25-LLVM-2.0-and-Beyond.pdf】,而语法树(AST)内存占用则为被编译源码的 1.3 倍,而 GCC 则可以轻易地可以超过 10 倍。Clang 不但编译代码快,对于用户犯下的错误,也能够更准确地给出建议。使用过 GCC 的读者应该熟悉,GCC 给出的错误提示基本都不是给人看的。
比如最简单的:
struct foo { int x; }
typedef int bar;
如果使用 GCC 编译,它将告诉你:
t.c:3: error: two or more data types in declaration specifiers
但是 Clang 给出的出错提示则显得人性化得多:
t.c:1:22: error: expected ‘;’ after struct
甚至,Clang 可以根据语境,像拼写检查程序一样地告诉你可能的替代方案。
比如这个程序:
#include <inttypes.h>
int64 x;
GCC 一样给出乱码似的出错提示:
t.c:2: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘x’
而优雅的 Clang 则用彩色的提示告诉你是不是拼错了,并给出可能的变量名:
t.c:2:1: error: unknown type name ‘int64′; did you mean ‘int64_t’?
int64 x;^~~~~int64_t
更多的例子可以参考 http://blog.llvm.org/2010/04/amazing-feats-of-clang-error-recovery.html。 而同时又因为 Clang 是高度模块化的一个前端,很容易实现代码的高度重用。所以比如 Xcode 4.0 的集成编程环境就使用 Clang 的模块来实现代码的自动加亮、代码出错的提示和自动的代码补全。开发者使用 Xcode 4.0 以后的版本,可以极大地提高编程效率,尽可能地降低编译错误的发生率。
支持 C++ 也是 Clang 的一项重要使命。C++ 是一门非常复杂的语言,大多编译器(如 GCC、MSVC)用了十多年甚至二十多年来完善对 C++ 的支持,但效果依然不很理想。Clang 的 C++ 支持却一直如火如荼地展开着。2010 年 2 月 4 日,Clang 已经成熟到能自举(即使用 Clang 编译 Clang,到我发稿时,LLVM 3.0 发布已完整支持所有 ISO C++ 标准,以及大部分C++ 0x 的新特性。
这对于一个短短几年的全新项目来说是非常不易的。得益于本身健壮的架构和 Apple 的大力支持,Clang 越来越全能,从 FreeBSD 到 Linux Kernel , 从 Boost 到 Java 虚拟机, Clang 支持的项目越来越多。
Apple 的 Mac OS X 以及 iOS 也成了 Clang 和 LLVM 的主要试验场——10.6 时代,很多需要高效运行的程序比如 OpenSSL 和 Hotspot 就由 LLVM-GCC 编译来加速的。而 10.6 时代的 Xcode 3.2 诸多图形界面开发程序如 Xcode、Interface Builder 等,皆由 Clang 编译。到了 Mac OS X 10.7,整个系统的的代码都由 Clang 或 LLVM-GCC 编译【注:http://llvm.org/Users.html】。
LLVM 周边工具
由于受到 Clang 项目的威胁,GCC 也不得不软下来,让自己变得稍微模块化一些,推出插件的支持,而 LLVM 项目则顺水推舟,索性废掉了出道时就一直作为看家本领的 LLVM-GCC,改为一个 GCC 的插件 DragonEgg。 Apple 也于 Xcode 4.2 彻底抛弃了 GCC 工具链。
而 Clang 的一个重要衍生项目,则是静态分析工具,能够通过自动分折程序的逻辑,在编译时就找出程序可能的 bug。在 Mac OS X 10.6 时,静态分析被集成进 Xcode 3.2,帮助用户查找自己犯下的错误。其中一个功能,就是告诉用户内存管理的 Bug,比如 alloc 了一个物件却忘记使用 release 回收。这已经是一项很可怕的技术,而 Apple 自己一定使用它来发现并改正 Mac OS X 整个系统各层面的问题。但许多开发者还不满足——既然你能发现我漏写了 release,你为什么不能帮我自动加上呢?于是 ARC 被集成进 Clang,发生了文章开头开发者们的惊愕——从来没有人觉得这件事是可以做成的。
除 LLVM 核心和 Clang 以外,LLVM 还包括一些重要的子项目,比如一个原生支持调试多线程程序的调试器 LLDB,和一个 C++ 的标准库 libstdc++,这些项目由于是从零重写的,因此要比先前的很多项目站得更高,比如先前 GNU、Apache、STLport 等 C++ 标准库在设计时,C++0x 标准还未公布,所以大多不支持这些新标准或者需要通过一些肮脏的改动才能支持,而 libstdc++ 则原生支持C++0x。而且在现代架构上,这些项目能动用多核把事情处理得更好。
不单单是 Apple,诸多的项目和编程语言都从 LLVM 里取得了关键性的技术。Haskell 语言编译器 GHC 使用 LLVM 作为后端,实现了高质量的代码编译。很多动态语言实现也使用 LLVM 作为运行时的编译工具,较著名的有 Google 的 Unladen Swallow【注:Python 实现,后夭折】、PyPy【注:Python 实现】,以及 MacRuby【注:Ruby 实现】。例如 MacRuby 后端改为 LLVM 后,速度不但有了显著的提高,更是支持 Grand Central Dispatch 来实现高度的并行运行。由于 LLVM 高度的模块化,很方便重用其中的组件来作为一个实现的重要组成部分,因此类似的项目会越来越多。
LLVM 的成熟也给其他痛恨 GCC 的开发项目出了一口恶气。其中最重要的,恐怕是以 FreeBSD 为代表的 BSD 社区。BSD 社区和 Apple 的联系一向很紧密,而且由于代码相似,很多 Apple 的技术如 Grand Central Dispatch 也是最早移植到 FreeBSD 上。BSD 社区很早就在找 GCC 的替代品,无奈大多都很差(如 Portable C Compiler 产生的代码质量和 GCC 不能同日而语)。
一方面是因为不满意 GCC 的代码品质【注:BSD 代码整体要比 GNU 的高一些,GNU 代码永无休止地出现各种严重的安全问题】,更重要的是协议问题。BSD 开发者有洁癖的居多,大多都不喜欢 GPL 代码,尤其是 GPL 协议第三版发布时,和 FreeBSD 的协议甚至是冲突的。这也正是为什么 FreeBSD 中包含的 GNU 的 C++ 运行库还是 2007 年以 GPLv2 发布的老版本,而不是支持C++0x 的但依 GPLv3 协议发布的新版本。因此历时两年的开发后,2012年初发布的 FreeBSD 9.0 中,Clang 被加入到 FreeBSD 的基础系统。 但这只是第一步,因为 FreeBSD 中依然使用 GNU 的 C++ STL 库、C++ 运行库、GDB 调试器、libgcc/libgcc_s 编译库都是和编译相关的重要底层技术,先前全被 GNU 垄断,而现在 LLVM 子项目 lldb、libstdc++、compiler-rt 等项目的出现,使 BSD 社区有机会向 GNU 说“不”,因此一个把 GNU 组件移出 FreeBSD 的计划被构想出来,并完成了很大一部分。编写过《Cocoa Programming Developer’s Handbook》的著名 Objective-C 牛人 David Chisnall 也被吸收入 FreeBSD 开发组完成这个计划的关键部分。 预计在 FreeBSD 10 发布时,将不再包含 GNU 代码。
LLVM 在短短五年内取得的快速发展充分反映了 Apple 对于产品技术的远见和处理争端的决心和手腕,并一跃成为最领先的开源软件技术。而 Chris Lattner 在 2010 年也赢得了他应有的荣誉——Programming Languages Software Award(程序设计语言软件奖)。
半导体的丰收(上)
在美国宾夕法尼亚州的东部,有一个风景秀美的城市叫费城。在这个城市诞生了一系列改变世界的奇迹:第一个三权分立的国家——美立坚合众国,就在第五街的路口诞生;举世闻名的费城交响乐团,1900年在市中心的 Academy of Music 奏响了他们的第一个音符。而写这篇文章时,我正坐在三十四街的宾夕法尼亚大学计算机系的一楼实验室,面前摆放着世界上第一台电子计算机——ENIAC。
1946年 2 月 14 日,ENIAC 问世,每秒可运行 5000 次加法运算或 500 次乘法运算,面积达 170 平方米,重约 30 吨,拉开了计算机处理器革命的序幕。这场革命是各处理器厂商长达数十年的竞赛,而摩尔定律从一开始就准确地预测了这场比赛的走势。根据摩尔定律,同样价格的集成电路上可容纳的晶体管数目,每隔约 18 个月便会增加一倍,性能也将提升一倍。但事实上,并无法用老路子来保持这个增长速度,因为会遇到包括能耗、散热等各种技术瓶颈。所以每隔几年就会有用来绕过这些瓶颈的新一代产品推出。如采用超纯量(superscala)、指令管线化、快取等。这些技术通过一定程度的高效并行来挖掘计算机处理器的速度所能达到的高度,以促使用户更新换代。
世界上第一台计算机 ENIAC,1946年 2 月 14 日诞生于宾夕法尼亚大学
和 66 年前的 ENIAC 相比,今天的处理器已有了质的飞越。而 21 世纪的前十年,我们更是见证了个人计算机处理器的三次重大革命——64位处理器、多核心和高效图形处理器在个人电脑出现。在这样的背景下,乔布斯在 2008 年 WWDC(苹果全球开发者大会)上,宣布下一代 Mac 操作系统 Mac OS X 10.6 将被命名为 Snow Leopard(雪豹)来适应硬件架构的革新。就在那天下午,Bertrand Serlet 在一场开发者内部讲座上透露,和先前两个发行版包含大量的新功能(10.4 Tiger 包含 150 个新功能,10.5 Leopard 包含 300 个新功能)不同,Snow Leopard 不含任何新功能,仅是对 Leopard 中诸多技术的重大更新,以使其在现代架构上更稳定、高效。 在这十年的最后一年,2009 年 8 月 28 日,苹果发布了 Mac OS X 10.6 来有效地支持这三项技术,而本文将为读者介绍其对应的三项软件技术——64位架构、Grand Central Dispatch,以及 OpenCL。 其他 Mac OS X 10.6 技术更新,如全新的 QuickTime X 和跳票的 ZFS,有着更复杂的历史背景(以后再为读者介绍)。
64 位架构出现的缘由
前文提到,根据摩尔定律,同样价格的集成电路上可容纳的晶体管数目,约每隔 18 个月便会增加一倍,性能也将提升一倍。事实上,存储器的容量增长可能更快,每过 15 个月就会翻一番。有了更快更强的电脑,可能会让数值计算的科学家们喜出望外,但对普通大众来说,摩尔定律给普通消费者一个假象——如果你觉得 1000 美元的苹果电脑太贵,那等上 18 个月就可以用 500 美元买到同样的电脑。十年前你在用电脑写 Word 文档,十年后你还在用电脑写 Word 文档,反正计算机不是耗材,一台电脑只要不坏,就不用去买新的。计算机产业的巨头们自然知道摩尔定律对他们造成的致命打击,因此,一个阴谋被以 Intel 和 Microsoft 为首的巨头们构想出来——Intel 负责把硬件越做越快,而 Microsoft 则负责把自己的软件越做越臃肿、越做越慢——至于你信不信,反正我是信的。因此,使用软件、服务等,直接促进计算机产业的消费,使得计算机产业走上可持续发展的道路。这在计算机产业被称为 Andy-Bill 定律,分别以 Intel 和 Microsoft 总裁的名字命名。
当然,软件公司未必真心欺骗消费者,故意把软件做大做慢——为了实现一个新功能,软件势必会比原先庞大。但现代软件的速度、大小和其增加的功能并不成比例。比如对最终用户来讲,Windows Vista 到底比 Windows XP 多了多少功能呢?可能只有 20%~30%。Word 2007 对比 Word 2003 多了多少功能呢?可能也只有 20%~30%。但 Windows Vista、Word 2007 占用的 CPU、内存、磁盘空间,却比 Windows XP 和 Word 2003 翻了几番。究其原因,为了能赶快把新功能带给用户,我们不惜使用更方便但低效的编程语言(.NET、Java 等依赖虚拟机的语言就要比 C 慢许多,Python 等动态语言比 C 慢的不是一星半点)、快速开发(我们原先处理一个大文本,先分块,一点一点读到内存中,然后把处理完的部分写回磁盘,清空内存;而现在直接把它全读进来处理,开发方便,执行也快)。而用户必须为这些新功能买不成比例的单。64 位就是在这个背景下迅速走入寻常百姓家的——程序占用越来越多的内存,而 32 位的寻址空间已不能满足软件运行的需要了。
64位 CPU 是指 CPU 内部的通用寄存器的宽度为 64bit,支持整数的 64bit 宽度的算术与逻辑运算。早在 1960 年代,64位架构便已存在于当时的超级电脑,且早在 1990 年代,就有以 RISC 为基础的工作站和服务器。2003 年才以 x86-64 和 64 位元 PowerPC 处理器架构(在此之前是 32 位元)的形式引入到个人电脑领域。从 32 位元到 64 位元架构的改变是一个根本的改变,因为大多数操作系统必须进行全面性修改以取得新架构的优点。
成功的迁移
苹果向 64 位处理器的迁移花了整整 6 年时间,远长于该公司其他技术的迁移——向 Intel 的迁移仅用了一年时间,从经典 Mac OS 到 Mac OS X 也仅用了三年时间。总而言之,这场迁移是非常成功的:一方面,用户基本无痛苦,老的 32 位程序在目前最新版的 Mac OS X Lion 中依然可以完全兼容地执行;另一方面,对开发者而言,基本只需做微小的调整,重新编译程序,而且若干技术如 Universal Binary,使他们发布程序非常方便。当然,对于某些大量使用过时技术的公司,如 Adobe 和 Microsoft,这场迁移则要折腾得多。
这场迁移整整用了四个发行版的时间(10.3 至 10.6),不同于 Windows 或 Linux,Mac OS X 对 64 位的迁移自下而上,再自上而下。先是内核扩展,逐渐上升至 Unix 空间,然后上升至用户界面,再上升至整个应用程序生态,最后完成内核的迁移。要提醒读者的是,Mac OS X 的 32 位和 64 位内核空间与用户空间的分配和实现,和 Windows 存在本质的区别,但在本期介绍中,我们尽可能少地把 Mac OS X 的 64 位迁移和 Windows 进行比较,不拘泥于技术细节,对此区别有兴趣的读者,请移步 AppleInsider 的系列专题。
2003 年,苹果发布了其第一款 64 位计算机工作站 Power Mac G5。同期发布的 Mac OS X 10.3 也因此增加了非常简单的 64 位支持,于是 XNU 内核开始支持 64 位的寄存器和整数计算。但对于用户空间而言,程序可见的地址依然是 32 位的。程序当然可以使用大于 4GB 的内存(Power Mac G5 最高可达 8GB 寻址空间),但这要求程序手动地在两个 32 位内存空间中来回转换。
两年后,苹果发布了当时最成功的 Mac OS X 发行版 Mac OS X 10.4 Tiger。10. 4 的内核是革命性的,除了增加对内核并行多线程的支持,它把用户空间可见的地址空间扩展到了 64 位,因此理论上用户程序可以以 64 位方式执行。当然,在这个时期,几乎系统内的所有程序,哪怕是内核,依然是 32 位的。系统中唯一带的 64 位二进制文件是名为 libSystem.dylib 的系统库。它是 Mac OS X 上对 C 标准和 POSIX 标准的支持库,由 libc、libinfo、libkvm、libm 和 libpthread 五部分组成。但这仅有的 libSystem.dylib 理论上就能让所有仅使用 C 标准库和 POSIX 标准库的程序以 64 位模式运行。当时,用户对 64 位的需求较少,主要限于科学计算或图形处理等需要大数组的领域。因此,10.4 能较好地满足这部分用户的需求。但如果程序需要调用除 BSD Unix 以外的系统调用,比如想用 Cocoa 来画图形界面,那么该程序仅能以 32 位方式运行了。对于一些需要 64 位寻址空间的科学计算程序,比如 Mathematica,就需要采用一些比较麻烦的做法:用一个进程调用 32 位的 Cocoa 画图形界面,用另一个进程调用 64 位的 libSystem 来进行运算和 Unix 系统调用,并用 Unix 管道或进程间通信的方式管理两个进程间的输入/输出。
苹果在 Mac OS X 10.4 发布同期的另一项重要决策是向 Intel 平台 x86 及 x86_64架构的迁移。为了帮助开发者和用户顺利迁移,苹果正式公布了 Universal Binary。Universal Binary 技术是 Mach-O 二进制文件早就具有的特性,只是在这个场合作为一个商业词汇进行宣传。NeXT 时代 NeXTSTEP 操作系统就支持许多种不同的硬件架构,自然可以要求开发者对每个平台发布一个独立的版本,但这样的分发模式很麻烦,消费者也需要搞清到底购买哪种平台的软件。因此 NeXT 的 Mach 内核所支持的 Mach-O 二进制文件格式引入了一种叫 fat binary 的特性,说白了就是在一个平台架构上分别交叉编译所有平台的二进制格式文件,然后把每个文件都打包成一个文件。Universal Binary 就是指同时打包 Intel 平台和 PowerPC 平台的二进制文件。Mac OS X 10.4 最终支持四个平台的 BSD 系统调用——32 位 Power PC、64 位 PowerPC、32 位 x86 和 64 位 x86_64。作为最终用户,无须搞清这些区别,因为使用 Universal Binary 技术,买回来的软件直接会解出相应平台程序的二进制文件并执行。这是苹果很成功的一步——不像 Windows 系统中要用不同的路径(\Windows\System、\Windows\System32、\Windows\System64)分别存放不同架构的二进制库,并且用户还需在 32 位版和 64 位版之间犹豫不决。
Mac OS X 10.5 Leopard 经过一系列跳票终于在 2007 年末发布,跳票主要原因是当时苹果投入了大量人力和物力去做 iPhone,以至于 10.5 跳票了整整一年。10.5 包含了约 300 项新功能,而最重要的一项是苹果把对 64 位的支持带入了 Cocoa 层面。因此,几乎系统中所有的库都有四个平台的版本。在 WWDC 上乔布斯亲自向与会者介绍迁移到 64 位的好处,而能使用更大的内存自然是一项重要优势,程序可以申请更大的内存,把所有数据一并读入内存中操作,而无须分块后来来回回地在内存和磁盘搬运数据。另外,对 Intel 平台来说,x86 架构只有 8 个寄存器,而 x86_64 平台有 16 个寄存器,这也就意味着,对该平台来说,只要重新编译程序,程序就能自由调度比原先翻倍的寄存器数量而无须快取或在内存中来回查找和读写。根据粗略估算,一般涉及大量数值计算的程序会加快一倍。所以他很开心地劝说所有的开发者都迁移到 64 位架构。
历时整整 6 年时间,苹果完成了向 64 位处理器的迁移,同时这也给苹果提供了良好的清理门户的机会——清理过时的技术和 API。
彻底的清理
同时,苹果做出了一个大胆的举动——Carbon 框架并未出现在这次迁移中。Carbon 是 Mac OS X 诞生之初为了帮助 Mac OS 开发者把老程序迁移到新的 Mac OS X 操作系统上所提出的一个兼容 API,这套 API 长得很像经典 Mac OS 的 API,但能够得到 Mac OS X 平台提供的一切新特性,Adobe、Microsoft 等都是通过 Carbon 把它们经典的 Mac OS 程序移植到 Mac OS X 上的。苹果的本意是希望开发者用 Carbon 迁移老程序,用 Cocoa 开发新程序,但在 Carbon 诞生之初,其受关注度远大于 Cocoa,据 TeXShop 开发者 Dick Koch 回忆,在 Mac OS X 刚诞生的开发者大会上,Carbon 讲座的教室挤满了人,而 Cocoa 相关的讲座上听者无几。维护两套雷同的 API 的代价自然很高,所以砍掉一个是大势所趋。Carbon 和 Java 的热度甚至一度让苹果产生索性把 Cocoa 或 Objective-C 砍掉的想法。大量苹果自家的程序如 Finder、iTunes、Final Cut、QuickTime 等也都是用 Carbon 写成的。不过在此后由于大量涌现在 Mac OS X 平台上的新程序都是 Cocoa 写的,导致 Cocoa 技术不断走高。2007年的 iPhone 也完全依赖于 Objective-C 和 Cocoa 的一个裁剪版 Cocoa Touch。因此在 WWDC 2006 上,苹果在 Mas OS X Leopard 10.5 的开发预览版中包含了测试版本的 64 位 Carbon 库,甚至还有讲座教如何开发 64 位的 Carbon 程序。但苹果却在 2007 年告诉 Carbon 开发者,他们的程序将不可能再被编译成 64 位,要做到这点,必需先把程序用 Cocoa 重写。
这个突然的决定激怒了很多开发者,尤其是以 Microsoft 和 Adobe 这些巨头为代表的公司。Adobe 全套的 Creative Suite 和 Microsoft 全套的 Microsoft Office 是很多苹果用户必备的软件,数百万行代码全是用 Carbon 写的。所以直到今天,除了 Adobe Photoshop 等少数程序终于在 2010 年全面移植到 Cocoa 后做出了 64 位版,其他大部分程序依然停留在 Carbon 的 32 位模式。
苹果也花了很长时间来重写 Finder、FinalCut、iTunes、QuickTime 等程序或技术,耗费了大量精力。当 Adobe 发布 64 位的 Lightroom 2.0 时,苹果还在手忙脚乱地重写 Aperture。不过公正地讲,长痛不如短痛,砍掉对 Carbon 的支持能够使苹果把更多精力放在该做的事上,也使得 Mac OS X 的结构更简洁,并且事实上,64 位的迁移为苹果提供一个砍去老 API 的机遇,哪怕对 Cocoa 也是。一方面,Cocoa 框架中很多类不是使用类似 Carbon 的 API,就是依赖于用 Carbon 实现(注意,和传统观念不同,Carbon 和 Cocoa 在早期 Mac OS X 上是相互依赖的,比如菜单 NSMenu 就使用了 Carbon 的菜单管理器),这些 API 在 64 位得到了彻底清理,QuickTime 相关的 C 接口全被砍去。Cocoa 经过很长时间的发展,自然也保留了很多过时的 API 以保证和原先的产品兼容,而这次机会给苹果足够的理由彻底推翻原先的设计。在 Mac OS X 10.5 中, Objective-C 的运行库 libobjc 更新到 2.0,提供了全新的并发、异常处理、自动内存回收、属性(property)等新机制,其中很多新特性只供64位享用。同时,所有 int 都被改为 NSInteger,Core Graphics 中的 float 都改为 CGFloat,以保持 API 统一,这些都是 64 位架构上的改动。因此 64 位迁移给苹果一个很好的清理门户的机会。
作为相反的例子,这次清理也有不彻底的地方。比如从老版 Mac OS 中混进来的 Keychain 库,甚至具有 Pascal 风格的 API,由于没有替代品,它也得到了 64 位的更新。所以类似 keychain 这样的库成了现在 Mac OS X 程序员的噩梦。我每次用到 Keychain 都有痛不欲生的感觉。
而 2009 年发布的 Mac OS X 10.6 Snow Leopard 则是对 64 位真正完整的支持。Unix 层虽然 10.4 就提供了 64 位的 libSystem,但所有的 Unix 用户空间工具包括 ls、Python 等,以及 Xcode 中的 gcc,也都是以 32 位二进制的模式发布的。图形界面层,在 10.5 Leopard 中,虽然整个系统的库都迁移到 64 位,以 32 位和 64 位的混合模式发布,但用户应用程序依然是 32 位的。只有 Chess、Java、Xcode 套件等少数程序以 64 位编译。但在 10.6 中,基本所有的应用程序都被迁移到 64 位,不管是 Safari、Mail、Dock,还是 TextEdit。当然,各种 Unix 工具包括 LLVM、GCC 等也都以 64 位的模式发布。10.6 只有四个 Carbon 程序(Front Row、iTunes、DVD Player 以及 Grapher)未得到 64 位升级【2009 年查阅,现页面已更新至 10.7】。其中, Front Row 在 Mac OS X 10.7 Lion 中被砍掉, iTunes 在 10.7 发布时依然以 32 位模式发布,在 2011 年末的更新中才迁至 64 位。
为了使应用支持 64 位,苹果不遗余力地改写了大量代码,Snow Leopard 中最重要的重写当属 Finder,这个程序自 Mac OS X 发布以来就一直是一个 Carbon 程序,并且苹果一直不停地改进它以展示 Carbon 无所不能。但自从 10.5 时代苹果下决心砍掉 Carbon 后,该程序被完整地重写。新的 Finder 和 Carbon 版的 Finder 看上去并没有太大差别,但 Finder 使用 Cocoa 重写后,不仅速度更快,而且增加了许多 Cocoa 新特性,比如加入了更多的 Core Animation 特效来平滑过渡动画。总之,虽然苹果在 10.6 期间没有提供太多新功能,但这样大规模的重写,为今后代码的可维护性奠定了良好的基础。
Mac OS X 10.6 发行版也完成了 64 位化的最后一步——内核的 64 位化。
半导体的丰收(中)
经过 6 年时间,4 个发行版,苹果终于完成了向 64 位的迁移,并随着 Snow Leopard 的发布推出了解决并行编程问题的 Grand Central Dispatch(简称 GCD)技术,释放了多核系统的潜力。
和 10.5 一样,在 10.6 Snow Leopard 中,苹果继续利用 64 位的迁移砍掉了诸多老技术,很多新技术仅以 64 位的模式被支持。例如重写的 QuickTime X 框架,虽然 QuickTime X 应用程序以 32 位和 64 位的模式发布,但其 API 仅暴露给 64 位。另一个例子是 Objective-C 2.1 的运行库,快速 Vtable 调度,新的和 C++ 统一的异常处理模型,以及彻底解决对象的 FBI 问题等,都仅限 64 位程序使用。
内核的 64 位化
读者应该发现,经过这 4 个发行版,Mac OS X 自下而上地对整个系统向 64 位迁移。10.3 内核空间提供了 64 位整数运算的支持。10.4 允许程序以 64 位模式运行在用户空间,并且提供了 64 位的 libSystem 使得开发者可以开发 64 位的 Unix 程序,而 10.5 中系统所有未废弃的函数库、框架都提供 64 位版本,到了 10.6,所有用户空间的程序,包括 Unix 层和图型界面层,基本都更新到 64 位。细心的读者不禁会问—那内核是 64 位的吗?是的,自下而上支持 64 位后,10.6 又从上往下,迁移了整个系统中最后一个也是最重要的部分—内核。
内核 64 位化的意义
对于 Windows、Linux,以及 FreeBSD 等操作系统,64位实现的第一步是实现 64 位的内核。然而 Mac OS X 却反其道而行。主要原因是,反正 32 位的内核也能以非模拟、非兼容的方式原生地运行 64 位用户空间程序,而内核和与内核动态链接的驱动,很少需要用到 64 位的寻址空间(你什么时候见过内核本身使用 4GB 内存?),所以该问题可以暂缓。
但要记住,用户空间的内存是由内核管理的,虚拟内存、内存分页等机制,都是由内核一一实现的。一旦在不久的将来,随着用户空间的内存占用越来越多,虚拟内存的分页比也会不断膨胀。比方说,一个用户程序使用 4GB 的空间,每个分页包含 4KB 的页面,那么总共有 1M 个页面。因此,假设一个页面需要 64B 的 PTE 来记录该页的位置,那总共也就需要 64MB 的内核空间来记录这个用户空间程序的虚拟内存,不算太多。而在不久的将来,如果一个 64 位用户程序使用 128GB 的空间,则需要 32M 个页面,每个页面 64B 的 PTE 会导致 2GB 的内核地址空间来寻址(暂不考虑大分页)。32 位的内核就显得非常紧张。
另外,上一期我们也提到 64 位的 Intel 架构提供了比 32 位多一倍的寄存器,因此,用户空间程序对 64 位内核的系统调用也会更快。根据苹果的数据,系统调用的响应速度比原先快了 250%,而用户空间和内核空间的数据交换也快了 70%,因此,64位内核要比 32 位内核更快。
内核完成 64 位迁移
虽然在 Mac OS X 10.6 中,苹果提供了 64 位模式运行的内核,但在大部分苹果计算机上,这个特性并不默认启用。其原因是,虽然 64 位程序和 32 位程序可以在计算机上同时运行,但 64 位的程序只可以加载 64 位的库或插件,32位程序只能加载 32 位的库或插件。因此,如果默认使用 64 位模式启动,则诸多第三方的 32 位驱动或内核模块将无法使用。当然,用户可以通过修改 com.apple.Boot.plist、nvram,或开机按住 6 和 4 强制加载 64 位内核,不过苹果并不推荐这样的方式。直到 Mac OS X 10.7 时,第三方内核扩展已趋完善,大部分的 Mac 才默认使用 64 位内核模式启动。
苹果用了整整 6 年的时间完成 64 位的迁移,在 2009 年 WWDC 的一个讲座上,Bertrand Serlet 告诉开发者,我们这个 64 位技术的讲座,只针对 Mac OS X,而 iPhone、iPad 等 iOS 设备,由于使用 ARM 平台,在可预见的未来可能并不会支持 64 位技术。
不过两年之后的 2011 年 10 月 27 日,ARM v8 发布,ARM 正式宣布支持 64 位。未来会不会出现基于 ARM 的 Mac,或是 64 位的 iPad,除了苹果,谁知道呢?
Bertrand Serlet 在 WWDC 2009 上介绍 Snow Leopard 的 64 位和 Grand Central Dispatch 技术
GCD(Grand Central Dispatch) 来临
很长一段时间以来,处理器靠更快的运行时钟来获得更高的效率。软件开发者无需改动或重新编译他们的代码,就能得到摩尔定律许诺他们的好处,因为处理器顺序地执行计算机指令,新一代的处理器就自动会跑得比原先更快。后来每每达到一个技术极限时,总有一些聪明的方法绕过这些极限,比如超纯量、指令管线化、快取等,不是悄无声息地把多条互相独立的指令同时运行,就是隐藏掉数据读写的延时。
GCD 出现的缘由
到了 21 世纪,能想的办法基本都想尽了——现代处理器已经足够并行了,也采取了各项优化来不断提升各种预测器的准确率,而时钟频率却是不能无限提高的——提高时钟频率会极大地增加处理器的产热,使得服务器机房或笔记本的散热成为一个头痛的问题。同时对于便携设备而言,高频也意味着短得多的电池时间,因此摩尔定律正在经受重大的考验。
因此大约在 21 世纪头十年过掉一半时,“多核”处理器,终于开始跃入普通消费者的视线。“多核”顾名思义,就是把原先单核的半导体线路复制多份排于同一裸片上,每个核相互独立,又能彼此通信。多核处理器的出现,有效缓解了计算机处理器生产商的设计和制造压力,从而达到忽悠消费者买更新款产品这一不可告人的目的。
但这一次技术革新,并不如之前那么顺利,因为程序并不会自动在多核系统上跑得更快,甚至有很多程序每一步都有前后依赖,不能高效地并行运行。即使能够高效并行的程序,也需要大规模改写才能充分利用多核所带来的优势。
传统的并发编程模式,就是学习使用线程和锁。这听起来很简单,几句话能说明白:
- 把每个任务独立成一个线程;
- 不允许两个线程同时改动某个变量,因此得把变量“锁”起来;
- 手动管理线程的先后并发顺序和并发数量,让它们均匀地占满系统资源;
- 最好系统中只有这个程序在运行,否则你精心设计好的线程管理算法往往不能达到原来该有的效果;
- 最后祈祷程序在用户那儿不出问题。
但是实际操作起来,多线程程序的编写要比单线程难上不止一个数量级。一方面,调用大量内存和数据反复的加解锁本身效率就非常低下;另一个重要原因在于,由于多线程程序可能以任意的次序交错执行,程序再也无法像顺序执行时那样产生确定的结果。多线程程序看似容易编写,但难分析、难调试,更容易出错。即使是最熟练的开发者,在茫茫线程和锁之间,也会迷失方向。且程序的错误在很多时候甚至是不可重现的。所以,程序员使用线程和锁机制编写并行程序的代价是很高的。
GCD 就是在这种背景下被苹果提出来的。2008年最初提出但未公布细节时,很多人怀疑它是 FreeBSD 的 ULE 调度器在 Mac OS X 上的实现。ULE 是 FreeBSD 当时最新的内核调度器,用来替换掉老一代的 4BSD 调度器,当时使 FreeBSD 上跑多线程程序的效率获得了重大的性能提高,远高于同期 Linux 和 Solaris 的算法效率。但当时我就认为 GCD 依赖 FreeBSD 这项技术的可能性不大,因为 Mac OS X 中管理进程和线程主要用的是 Mach 而不是 BSD。不过后来证实我只猜对了一半,GCD 的实现,实际上是依赖于 FreeBSD 的另一项技术 kqueue。kqueue 是一个由 FreeBSD 4 时代引入的新功能,内核级别地支持消息通信管理。GCD 的队列,其实就是用 kqueue 实现的。
GCD 出现的意义
在 GCD 中,开发者不再管理和创建线程,而是将要实现的运算抽象成一个个任务,一起扔给操作系统,转而让操作系统管理,这在计算机科学中,被称为线程池管理模式。
在 GCD 中,开发者使用很简单的方式就能描述清应用程序所需执行的任务,以及任务之间的相互关联。每一个任务在代码中被描述成块(block),然后开发者把一个一个块显式地按顺序扔到队列(queue)中。使用块和队列两个抽象的表述,开发者无须创建线程,也无须管理线程,更无须考虑数据的加解锁。换之而来的,是更简短可读的代码。剩下的事,全都扔给操作系统去完成。
在操作系统那边,GCD 在程序运行时,管理着一定数量的线程,线程的数量是自动分配的,取决于用户计算机的配置和用户程序运行时的负载。多核工作站每个程序配到的线程,自然就会比单核手机或双核笔记本来得多。而且这个线程的数量是会动态变化的。当程序非常忙时,线程数会相应增多,而当程序闲置时,系统会自动减少其线程数量。然后,GCD 会一一从队列中读入需要执行的块,然后扔到线程上并发执行。
相信读者已经看出 GCD 和传统线程-锁机制的区别来了。传统的方式按劳分配,强调程序自由独立地管理,妄想通过“无形的手”把系统资源平均分配,走的是资本主义市场经济的道路。而 GCD 按需分配,真正实现了社会主义计划经济管理模式。因此在政治上 GCD 就是一个代表先进生产力的计算机技术(我被自己雷了,但事实就是这样)。
GCD 是一个自底向上的技术,它实际上由以下 6 个部分组成。
- 编译器层面,LLVM 为 C、Objective-C 和 C++ 提供了块语法,这个内容等下会介绍。
- 运行库方面,有一个高效分配管理线程的运行库 libdispatch。
- 内核方面,主要基于 XNU 内核 Mach 部分提供的 Mach semaphores 和 BSD 部分提供的 kqueue () 机制。
- dispatch/dispatch.h 提供了丰富的底层编程接口。
- 在 Cocoa 层面,NSOperation 被重写,因为使用 libdispatch,所以先前使用 NSOperation 的程序不需改动,就自动享受 Grand Central Dispatch 的最新特性。
- Instruments 和 GDB 提供了非常完整的分析和调试工具。
GCD 还有一些工程上的优势。首先,程序的响应速度会更快。GCD 让程序员更方便地写多线程程序,因此写一个多线程程序来实现前后台简单多了,极大改善了 Mac OS X 上应用程序的生态环境。而且 GCD 的代码块队列开销很小,比传统线程轻量得多。统计表明,传统的 Mac OS X 上使用的 POSIX 线程需要数百个计算机汇编指令,占用 512KB 的内存,而一个代码块队列才用 256 字节的长度,把块加入队列,只需要 15 个计算机汇编指令,因此开成百上千个也不费什么事。
其次,线程模式是一种静态的模式,一旦程序被执行,其运行模式就被固定下来了。但用户的计算机配置各不相同,运行时别的程序有可能耗用大量的计算资源。这些都会影响该程序的运行效率。而动态分配系统资源则能很好地解决这个问题。苹果自然也是不遗余力地忽悠开发者使用 GCD,因为各个软件共享多核运算的资源,如果 GCD 被更多的开发者采用,整个苹果平台的生态也就更健康。
而最重要的,还是 GCD 采用的线程池模式极大简化了多线程编程,也降低了出错的可能性。著名 FreeBSD 开发者 Robert Watson 还发布了一个他修改过的 Apache,并释出了补丁,声称只需原先 1/3 至 1/2 的代码量,就实现了原先的多线程模块,并比原先的效率更好。
如何应用 GCD
当然,老王卖瓜,自卖自夸,没有实际的例子,是不能让读者信服的。下面我们就来简单讲解 GCD 的技术。
首先是块状语法,是一个对 C、C++ 和 Objective-C 语言的扩展。用来描述一个任务,用^引导的大括号括起来。比如最简单的:
x = ^{ printf (“hello world\n”);}
则 x 就变成了一个块。如果执行:
x ();
那么程序会打印 hello world 出来。当然,blcok 像函数一样,可以跟参数,比如:
int spec = 4;
int (^MyBlock)(int) = ^(int aNum){
return aNum * spec;
};
spec = 0;
printf (“Block value is%d”,
MyBlock (4));
这里 MyBlock 是一个带参数的代码块。
读者看到这里不禁要问,块到底有什么好处?它和 C 的函数指针有什么不同?我们依然用上面的例子来说明问题,虽然后面我们把 spec 变量改为 0,但事实上在 MyBlock 创立时,已经生成了一个闭包,因此它最后输出的结果,仍是 16,不受 spec 值改动的影响。这对于搞函数式编程的人来说再熟悉不过了,因此很多开发者亲切地称呼块语法的 C 扩展为“带 lambda 的C”。
有了闭包功能的 C 顿时牛起来——你可以把函数和数据包装在一起——这就是块的真正功能。因为只要一个闭包包含了代码和数据,它的数据就不会被别的闭包轻易改动,所以在它执行时,你根本不用为数据上锁解锁。
有了一系列的代码块后,接下来的事是把代码块扔到队列里。比如最简单的:
dispatch_queue_t queue = dispatch_get_global_queue (0,0);
来创建一个轻量级的队列,然后
dispatch_async (queue,
^{printf (“hello world\n”);});
那这个代码块就被扔进 queue 这个队列中了。你可以手动依次添加任意多个项目,比如“带着老婆”、“出了城”、“吃着火锅”、“唱着歌”、“突然就被麻匪劫了”等。当然在更多的场合,你会更倾向于使用自动事件源,每当一个事件触发时(比如定时器到点、网络传来包裹,或者用户点击了按钮),相应的代码块被自动添加到队列中。
一旦队列不是空的,GCD 就开始分配任务到线程中。拿上面的例子来说,“老婆”、“城”等变量可是封在闭包里的,所以在运行时,不用考虑它们被某个别的闭包改掉(当然也有方法来实现这个功能)。总体而言,这个模式比线程-锁模型简单太多——它的执行是并行的,但思维却是传统的异步思维,对没有学习过系统多线程编程的开发者来说,依然能很容易地掌握。
读者可能要问,如果闭包之间有复杂的依赖关系,需要申明某两个操作必须同步或异步怎么办?比如“出了城”必须在“吃着火锅”之前。在 GCD 中,可以使用 dispatch_async 和 dispatch_sync 来描述这样的依赖关系,而在 Cocoa 层面,NSOperation 中的队列依赖关系甚至可以被描述成有向图。
GCD 得到广泛应用
GCD 一经推出就得到了广泛的应用。苹果自家的软件 Final Cut Pro X、Mail 等软件,都采用 GCD 来实现任务并发和调度,因此 Mac OS X 10.6 成为了有史以来最快的发行版。从 iOS 4 开始,iPhone 和 iPad 也加入了 GCD 的支持。更别提原来使用 Cocoa 的 NSOperation 相关接口的程序,无需改动即享受 GCD 的优惠。
GCD 在 Mac OS X 10.6 发布后,又以 libdispatch 为名,作为一个独立的开源项目发布。 所需的外围代码,如编译器的块支持、运行库的块支持、内核的支持,也都能在 LLVM 和 XNU 等开源项目代码中找到,所以很快被别的操作系统采用。作为 Mac OS X 的近亲, FreeBSD 在一个月后即完整移植了整套 GCD 技术,并最终在 FreeBSD 9.0 和 8.1 中出现。诸多 Linux 发行版也提供 libdispatch 的包,使用 Linux 内核的 epoll 来模拟 FreeBSD 的 kqueue。2011年 5 月 5 日, Windows 的移植工作也宣告完成。
另外,GCD 也成为拯救动态语言的重要法宝。由于受 GIL(全局解释锁)的限制,动态语言虽然有操作系统原生线程,但不能在多核处理器上并行执行。而 GCD 成功绕开了这个限制,如加入 GCD 支持的 Ruby 实现 MacRuby 就能在多核处理器上高效执行。 因此,在苹果生态圈以外,GCD 也会得到越来越多的应用。
半导体的丰收(下)
随着 CPU 与 GPU 合并成技术发展的趋势,苹果开发出了 OpenCL 框架,能够进行高速并行处理的能力使 OpenCL 成为了业界标准,被广泛应用。
最近几年,GPU 的发展吸引了很多来自科学计算界人士的目光。GPU 有稳定的市场推动力——公众喜闻乐见的电子游戏产生了源源不断的升级 GPU 的需求——因此比 CPU 的更新步伐更快。从技术上讲,GPU 本身就是多核架构,高端显卡往往有五百多个核心,即使低端的集成 GPU 也有二三十个核心,所以能够通过并行来高效处理成千上万的线程。同时,对于科学技算中的浮点计算,GPU 往往通过硬件加速使其效率比传统 CPU 更高,因为图形渲染等工作基本都是浮点计算。
GPGPU 浮出水面
早期的 GPU 只能执行固定的程序,而不开放给程序员编程。随着时代的发展,图像处理有时需要对着色器进行编程以实现一些特效,因此需要程序员可以使用 GPU 的汇编语言写简单的着色程序。这自然对程序员要求过高,所以一些高阶的着色语言又被 GPU 厂商开发出来。比如微软和 NVIDIA 共同开发的 Cg 语言,就能为顶点和像素编写专门的着色程序。这类技术虽然面向图形渲染工作者,却吸引了一小簇科学计算研究者的兴趣。以计算流体力学为例,它是用纳维斯托克斯方程【注:把牛顿第二定律和质量守恒应用到流体后,所得到的偏微分方程】来求解流体力学问题的一种算法,广泛用于天气预报、F1 方程式赛车设计等工程领域。同时,对于电影制片特效,计算流体力学也是最基本的用来模拟流体流动特放的算法,皮克斯动画工作室的《寻找尼莫》中的海洋流动和水花等,都是使用纳维斯托克斯方程来模拟的。
首先,对于一个几何空间进行网格化,每个网格中的流体,都可以列出纳维斯托克斯方程,把这些方程联立起来进行求解,即可得到各点的温度、压力、湿度、速度等流体信息。整个求解过程可以高度并行,因为每个网格的控制方程是完全一样的;同时也牵涉大量的浮点运算。但 Cg 这类语言并非面向普通的计算,其变量都是颜色、顶点、像素等图形学专用变量。来自北卡罗莱那大学教堂山分校的 Mark Harris 突发奇想:可以把流体力学中每个网格的速度、压力等变量,存成 RGBA 颜色后让 Cg 去处理,所以他在《GPU Gems》中著名的一章,公布了使用 Cg 来高速实现计算流体力学运算的成果,吸引了大量计算界的目光。然而,这种编程模式对科技工作者来说很不友好,因为这要求一个学力学的、学生物的、学化学的学生,先要明白复杂的 GPU 渲染原理,了解图形学中材质、顶点、合成、像素、光栅化、光线跟踪等深奥的理论,才能编写他们专业相关的 GPU 程序。
GPU 生产厂商洞察到了 GPU 高速并行浮点数运算的潜力,所以 GPGPU(General Purposed Graphics Processing Unit)概念终于浮出水面。一方面 GPU 设计一代比一代可编程化,另一方面各公司也在加紧研制新一代 GPU 编程语言。新一代的语言对比 Cg,去掉了对于渲染相关的知识要求,独立于图形学之外,是纯粹的普通语言,比如变量不再是像素、顶点、面等类型,而是 C/C++ 语言开发者喜闻乐见的浮点数组、整形数组等。这一时期为代表的语言,主要是 CUDA(Compute Unified Device Architecture)。CUDA 是 NVIDIA 在 2007 年公布的一项面对科学计算工作者的编程框架。通过该技术,使用者可利用 NVIDIA 的 GeForce 8 以后的 GPU 和较新的 Quadro GPU 进行高性能编程。用户先编写一个特殊的 C++ 代码文件,扩展名为 cu,文件中需要申明创建的变量、GPU 计算核心(kernel)以及使用给定的编程接口来实现变量在 CPU 和 GPU 中的传送。然后通过 NVIDIA 自家的编译器编译这个代码,链接到 NVIDIA 自家的库上,即可把该运算核心编译为 GPU 汇编语句扔到特定型号的 GPU 上高度执行。其他厂家也紧随其后,比如 AMD 为 ATI 生产的 GPU 卡提供了一个类似的框架叫 Stream SDK(先前被命名为 CTM, Close to Metal, ATI Stream Computing – Technical Overview, 03/20/2009 http://en.wikipedia.org/wiki/Close_to_Metal)。而微软更是趁 Vista 和 Win7 推出了 DirectCompute,作为旗下 DirectX 技术的一部分。
CUDA 并不完美
对科学工作者来说,CUDA 比 Cg 友好太多。使用 CUDA 加速流体力学运算相关的论文更是雨后春笋般涌现。然而不久后,我发现它存在许多问题。
首先,对初学者来说,CUDA 编程模式很容易学混。因为一个 GPU 数组和一个 CPU 数组在 CUDA 中的表述都是同样的C指针,但对于 GPU 数组和 CPU 数组,CUDA 的处理模式完全不同,CPU 数组使用常规的 malloc 来初始化,而 GPU 数组得使用 CUDA 提供的 malloc。所以程序写着写着,就忘了一个变量到底是给 CPU 用的还是给 GPU 用的,这无疑增加了学习难度。同时,CUDA 对 C/C++ 语言进行了一系列扩展,这不但意味着写的程序不再具有 C/C++ 那样良好的可移植性,而且这种计算核心和传统 C 程序混写的编程语言很不美观。
其次,CUDA 这类语言的实现各自为政。如果你写了一个 CUDA 程序,就意味着这个代码只能运行在 NVIDIA 的显卡上。如果想使用 ATI 的显卡呢?没门,请用 ATI Stream SDK 重写。
再次,CUDA 是在编译时就静态产生 GPU 代码的,所以只能产生特定的 GPU 代码。如果你发布了一个 CUDA 程序,它仅对某几种 NVIDIA 显卡进行特定的代码优化。如果 NVIDIA 自家出了一种新显卡,很抱歉,哪怕新显卡可能兼容老显卡的汇编指令而你的程序恰巧可以在新显卡上跑起来,你也无法发挥新显卡的所有特性。必须用针对新显卡的编译器重新编译源代码,才能够保证程序在新显卡上高效执行。
最后,CUDA 这类语言仅能产生高效的 GPU 代码,而无法产生 CPU 代码,即:写完的代码只能跑在 GPU 上,在 CPU 上只能“模拟执行”,仅供调试用。所以在一台不具备给定 GPU 的机器上,无法高效运行 CUDA 程序。同样,如果你有一个性能很强的工作站,那么你的 CPU 亳无用处——CUDA 不可能分配一部分任务给 CPU 完成。
另外还有未来计算机架构的不确定性。当时,GPU 越来越一般化,可以跑多种数值计算程序,而 CPU 随着多核成为主流也越来越像 GPU。所以很多厂家在考虑 CPU 和 GPU 合并的可能性。
当时轰动一时的热门事件,是 CPU 厂商 AMD 买下了 GPU 厂商 ATI,来开发下一代处理器 AMD Fusion,把 GPU 和 CPU 合并到一起。Intel 自然不甘示弱,做出了 Nehalem 平台,在该平台上,CPU 和集成 GPU 处于同一个包装中,外界一度猜测这样可使合并后的 CPU 具有图形处理工能,从而用户购置计算机就不用再考虑配一块 GPU 了。
更强大的是,当时 Intel 还公布了 Larrabee 计划,让 GPU 支援 x86 指令,使得一个常规的 x86 平台的程序不需要修改和重新编译便可在 GPU 上运行。
虽然事实和这些预期有稍许出入,但当时的技术趋势是:在将来可能出现一种新的合并 GPU/CPU 的技术,能够并行高速地运行一般的计算机程序,而面对这样新的可能的平台,我们如何准备?
OpenCL 诞生
OpenCL 则是苹果为这个新局面画下的蓝图。这项技术初期全称为 Open Computing Library(如果留意苹果早期宣传广告的话),后改名为 Open Computing Language。这项技术从本质上来说,和 CUDA 并没有太多的两样,但由于苹果在借鉴他人技术并把他人技术改得更棒这一点上是出了名的,所以 OpenCL 很好地解决了以上所有问题。
下面简单介绍一下这个框架。OpenCL 技术的结构十分清晰,对程序员来说,它是一个 Mac OS X 的 Framework,定义了两套标准,一套是一个 C 语言的编程界面(API),使得开发者创建、拷贝、回收 GPU 使用的对象,同时也包含检测处理器、为该处理器编译并调用核心程序(kernel)相关的接口;另一套是 OpenCL 核心程序语言的定义,是一套基于 C99 发展而来的语言。
例如我们有两个大数组,1024 维的 a 和 1024 维的 b(当然,1024不算大,OpenCL 往往用来处理十万、百万数量级的任务),我们把两个数组对应的元素加和,结果是一个 1024 维的数组c。C 程序员很容易能写出下面的程序:
for (int i = 0; i < 1024; i++)
c[i]=a[i]+b[i];
OpenCL 的核心程序,则是取每个独立的可并行的循环分支,即上面程序中的 c[i]=a[i]+b[i]。所以核心程序大概是下面这样:
__kernel add (float *a, float *b, float *c){
int i = get_global_id (0);
c[i]=a[i]+b[i];}
其中,get_global_id () 函数可以返回当前函数是全局中的第几个元素。把该程序保存为 add.cl,就是一个 OpenCL 的核心程序,为 C99 语言的一个子集。
使用 OpenCL 的 API 就能调用这个核心程序。每个 OpenCL 程序基本上是模式化地照搬下面流程:
1. 探测硬件(用 clGetDeviceIDs 函数护取计算设备(可以指定使用 GPU 或是 CPU),用 clCreateContext 函数来新建一个上下文(context),用 clCreateCommandQueue 函数针对设备和上下文新建一个命令队列);
2. 编译核心(读入 add.cl,用 clCreateProgram-WithSource 和 clBuildProgram 以及 clCreateKernel 来编译读进来的字符串,产生一个核心程序);
3. 写入数组(用 clCreateBuffer 创建a、b、c三个内存对象,用 clEnqueueWriteBuffer 把 C 数组写到内存对象中);
4. 运行核心(把内存对象作为核心程序函数的输入参数执行这个核心,程序会并发为 1024 个线程,每个线程执行一次相应的加法运算);
5. 读出结果(用 clEnqueueReadBuffer 读取c内存对向,写为C的数组);
6. 回收内存。
OpenCL 之美
让我们逐条来看前面那些问题是如何被解决的。
首先,OpenCL Framework 由 C API 和 OpenCL 语言组成,泾渭分明,所有的 GPU 变量在 C API 中,都是内存对象的形式出现,有别于 C 自建的数组。因此,你永远不会搞混两者。同理,OpenCL 核心程序是独立在 C 源程序之外的,不仅美观,也能保证你的 C 程序能被所有 C 编译器编译,因为调用 OpenCL 库和调用其他 C 的函数库没有任何不同。
其次,苹果开发出 OpenCL 后,觉得该技术甚好,索性联合 AMD、ARM、ATI、TI、Intel、IBM、Nokia 等公司,把它做成一个由 Khronos 组织主持的开放标准。不管电脑上用的显卡是 ATI 的还是 NVIDIA 的,OpenCL 都能像 OpenGL 那样在你的设备上无缝运行。事实上,OpenCL 已同 OpenAL 和 OpenGL 一样,成为 Khronos Group 旗下的三大业界标准。
再次,CUDA 是在编译时就静态产生 GPU 代码的,所以只能产生特定的 GPU 代码。而 OpenCL 的核心程序(kernel)是在运行时被编译成 GPU 指令的。由于 kernel 所用的 OpenCL 语言,仅是 C99 的一个子集,所以负责编译这个程序的是 OpenCL 运行库自带的 LLVM-Clang。这样做的好处是明显的,举例来说,如果用户有一堆 OpenCL 的程序,比如苹果最新的 Final Cut Pro X 就在许多地方采用了 OpenCL,如果某一天硬件厂商发布了一个全新的 GPU 架构,那么用户安装显卡后,只要下载或更新相关的驱动程序和运行库即可,而不需要再求软件厂商发布一个新版本的 Final Cut Pro X。因为 OpenCL 在运行时,会根据显卡厂商提供的驱动和新运行库自动优化程序到特定架构上。所以,程序兼容性问题也被圆满解决。
最后,由于 OpenCL 是个开放标准,也支持 CPU 和其他任何计算设备,比如数字信号处理芯片(DSPs)和各种专门的处理器架构。所以只要有相关的驱动和运行库,OpenCL 程序可以高效地并行运行在任何架构的运算设备上。由于 OpenCL 和 GCD 的编程模式是一样的,因此当 OpenCL 程序在 CPU 上执行时,是跑在 GCD 队列上的。
由于 OpenCL 能高速地进行并行处理(如 http://macresearch.org/opencl_episode1 的演示,OpenCL 编写的 GPU 程序比单核 CPU 能快上数十至数百倍,笔者的论文 Yue Wang, Ali Malkawi, Yun Yi, Implementing CFD (Computational Fluid Dynamics) in OpenCL for Building Simulation, 12th Conference of International Building Performance Simulation Association, 2011 也得出了类似的结论),OpenCL 被广泛地使用在很多产品中,苹果也是 OpenCL 的主要用户之一。如上面提到的 Final Cut Pro X 就是个典范,使用 GCD 和 OpenCL 进行大量并行的流媒体处理。在老版本 Final Cut 中,每当用户执行一次流媒体操作,都会弹出一个进度条来告诉用户剩余的处理时间,而 Final Cut Pro X 优化后的速度是如此实时,以至于这个进度条被去除了。Mac OS X 许多的底层库也使用 OpenCL 重写,如 Core Image,本身也是一个 GPU 加速库,使用 OpenCL 后相比原来,依然获得了可观的性能提升。
Snow Leopard 的发布标志着第一个 OpenCL 框架的完整实现,OpenCL 成为业界标准后,AMD 抛弃了原先的策略,投入开放标准的怀抱,一连放出了几个测试版本的集成 OpenCL 的 ATI Stream SDK,并在 2009 年年底发布了稳定版,2011年 8 月 8 日宣布废除原先的 Close to Metal 相关技术。NVIDIA 也是早早地在 CUDA SDK 中加入了 OpenCL 相关的库。CUDA 越来越不被看好,所以 NVIDIA 索性把 CUDA 发布为一个开源项目,并把 CUDA 架构在 LLVM 之上。这和 OpenCL 近几年的走强有很大关系。
开发者的瓶颈
目前看来,OpenCL 虽然解决了上面的所有问题且速度飞快,但对普通程序员来说,依然是非常底层的技术。而且由于硬件的限制(显卡不支持指针运算),很多 C 的标准并未在 OpenCL 中出现,写链表还需要用整数去模拟地址。程序员需要手动管理内存,处理底层的核心调用以及数据读写。而显卡厂商也大多不愿公开 GPU 的技术细节,因此不像 CPU 程序很容易通过汇编指令分析计算机底层干了什么,显卡对于开发者纯粹是个黑盒,把整个问题分成多少个线程并发也没有一个规律可循,有可能不起眼的改动会使程序运行瞬间变快或变慢数十倍,开发者也不知道其中的原因,只能凭经验操作。而且由于不存在良好的调试工具,所以很难改正程序的错误。
显卡作为系统最为重要的共享资源之一,不像现代操作系统那样提供内存保护机制,因此一个用户 OpenCL 程序的错误很容易导致整个计算机崩溃,所以经常是程序跑一遍后发现操作系统挂了,重启后发现了一个可能的错误,改完后编译运行,操作系统又挂了。我用 OpenCL 编写科学计算程序时,大量时间是在重启电脑而不是写程序。这些问题仍然阻碍着 OpenCL 被广泛采纳,不过,在科学计算界,已经涌现出了越来越多相关的论文和技术,相信在不久的将来,情况会有所改观。
结语
当写完这篇技术长文时,天色已晚,走出教室,和 ENIAC 擦肩而过。ENIAC 的出现激励了之后一次次的处理器革命。2009 年发布的 Snow Leopard 可能在整个 Mac OS X 发行版历史中不算最出彩,却是对于半导体集成电路革命的一次重大收获。
Mac OS X背后的故事(十)Mac OS X 文件系统的来龙去脉
(上)
HFS+ 和 UFS 文件系统同时被引入早期的 Mac OS X,随着若干年的发展,HFS+ 提供的功能已超越 UFS,使其在 Mac OS X 10.5 之后成为成为唯一正式的 Mac OS X 系统,但因为其背负许多的历史包袱,为考虑兼容性,这些陈旧的设计并不能被推翻重来,所以苹果开始秘密研发下一代的文件系统。
著名 BSD 开发者 Marshall Kirk McKusick
UFS:经典的 Unix 文件系统
在 Unix 系统刚诞生的远古时期,文件系统被简单地称为 FS。FS 只包括启动块、超级块(处于硬盘分区开头用来保存文件系统信息)、inodes(索引节点)及数据。FS 文件系统在 Unix 系统刚诞生时还能满足新老客户的需求,但随着科学技术的进步,FS 已不能符合现代文件系统的需求,且会导致抖动等一系列问题。当时还是加州大学伯克利分校研究生,后成为著名 BSD 开发者 Marshall Kirk McKusick 在 BSD 4.1b 上承接传统的 FS 文件系统实现了 FFS(Fast File System),妥善地解决了这一难题,把先前整块的磁盘文件系统分为小块,每块包含自已的索引节点和数据,因而增加了文件的局部性,减少了寻道时间。由于 Marshall Kirk McKusick 的 FFS 文件系统很好很强大,所以立即被各大 Unix 系统所使用。SunOS/Solaris、System V Release 4、HP-UX 及 Tru64 UNIX 都使用它,也成为当今各 BSD 分支(FreeBSD、OpenBSD、NetBSD 及 DragonFlyBSD)的标准文件系统。每个不同的系统,无论开源与否,又会在 FFS 文件系统上增加各种扩展,这些扩展往往不互相兼容,但神奇的是,大家又都使用和原版同样的块大小和数据块宽度。因此在很大程度上,这些山寨版 FFS 文件系统又相互兼容,至少在一个操作系统上能对另一操作系统的文件系统执行只读操作。因此,FFS 事实上已经成为 Unix 系统的标准文件系统,故它有了一个更广泛的称谓——UFS(Unix File System,即 Unix 文件系统)。
UFS 在后来的若干年又取得了长足的发展。Sun 公司在 Solaris 7 系统中,给 UFS 提供了简单的日志功能。日志文件系统指在档案系统发生变化时,先把相关的信息写入一个被称为日志的区域,然后再把变化写入主文件系统的文件系统。在文件系统发生故障(如内核崩溃或突然停电)时,日志文件系统更容易保持一致性,并且可以较快恢复。Marshall Kirk McKusick 又实现了 BSD 一度引以为豪的 Soft Update 功能,来保证计算机掉电或系统崩溃时,通过使元数据按依赖顺序更新来确保磁盘上总的文件系统保持一致的实现机制。Soft Update 的目标和日志类似,但实现代价比日志轻量许多。不过这项功能有所代价,主要是需要引入一个后台 FSCK 检查。
2009 年,Jeff Roberson 正式发表了对 UFS 的一项改进,为 Soft Update 加入了日志功能,并消除了对 FSCK 的依赖,这项改进最终集成进了 FreeBSD 9 中。TrustedBSD 项目又为 BSD 分支的文件系统设计了 ACL 访问控制表功能(Access Control Lists)。先前,Unix 文件系统的访问控制是非常简单的,其权限管理分为三个不同的类别:用户、同组用户以及其他用户,对每个类别,Unix 文件系统提供读、写、执行三种权限的管理。这样的许可管理过于粗糙,无法指定某一用户访问的权限,也无法指定更为细致的权限内容(例如准许对一文件实行删除操作)。为解决这个问题,访问控制表被增加到文件系统中,使用以存取控制矩阵为基础的存取控制方法。存取控制串列描述每一个文件对象各自的存取控制,并记录可对此物件进行存取的所有主体对对象的权限。总之,UFS 与时俱进,不断增加新的功能。
HFS+:更现代的 HFS
作为 Mac OS X 的老祖宗 NeXTSTEP,因为基于 BSD,所以自然也使用 UFS。而老版的 Mac OS 则使用一个叫做 HFS 的文件系统。HFS 是一个比较古老且不思进取的文件系统,因此,在 20 世纪 90 年代末已不能满足当时的需要。在《Mac OS X 背后的故事(一)》中我们提到,为了实现 Mac OS 的现代化,Copland 项目被提出。Copland 项目的子项目 Sequoia 旨在 HFS 的基础上,加入现代文件系统所必需的新功能,如大文件支持、Unicode 文件名支持、长文件名支持、32 位文件映射表支持等。Sequoia 项目即成为后来熟知的 HFS+,由 Don Brady 领导,这个团队先花了 6 个月时间把 HFS 项目原本的 Mac 使用的 68K 处理器汇编码改写成 C 代码,然后逐渐加入新功能。
后来由于 Copland 被力挽狂澜的 Ellen Hancock 给废了,所以一些有用的更新,如 HFS+ 即被集成到 Mac OS 8.1 中。在 Mac OS X 诞生初期,HFS+ 和 UFS 文件系统同时被引入早期的 Mac OS X 中。不过由于 HFS+ 根植 Mac OS,缺乏 Unix 文件系统所必需的功能,如符号链接、硬链接及其他各种 POSIX 兼容性,所以 HFS+ 开发组又花了一些工夫在不影响和 Mac OS 兼容性的情况下引入了这些功能。由于 HFS+ 是对 HFS 的扩展,故 HFS+ 支持 Mac OS 至 Mac OS X 的平滑过渡,所以 Mac OS X 一直默认使用 HFS+。但当时的 UFS 提供比 HFS+ 更先进的功能,因此 Mac OS X 10.0 至 10.4,也都支持把系统安装在 UFS 系统上。
Mac OS X 10.0 发布后,苹果不遗余力地对 HFS+ 进行大规模的扩展和维护,增加了很多 UFS 独有的功能。这些新功能使得文件系统更加安全稳定可靠。例如 Mac OS X 10.2.2 中,HFS+ 支持日志。日志功能在 Mac OS X 10.2 服务器版中可以简单地设定,但在普通桌面版中需要使用命令行进行操作。在 Mac OS X 10.3 中,带日志功能的 HFS+(被称为 HFSJ,即 HFS+ volume with journal)成为默认设置。Mac OS X 10.3 亦增加文件名、目录名区分大小写及 Unicode 3.2 的支持。Mac OS X 10.4 中,HFS+ 更是增加了 ACL 访问控制表功能,提供更复杂的对传统 Unix 文件系统权限的扩展。
文件系统除了让用户供稳定地存放文件这一目标以外,还是各项操作系统功能的基础。Mac OS X 每个大发行版都要增加数百项新功能,许多新功能严重依赖于文件系统的实现。Mac OS X 10.3 提供了 FileVault 来加密用户文件,因此用户主目录被保存在一个 HFS+ 文件系统加密镜像中。Mac OS X 10.4 提供了系统内置的 Spotlight 桌面搜寻搜索功能,能让用户对整个磁盘系统进行快速搜寻、随打即显。这项功能要求文件系统提供任意长度文件元数据(metadata)的支持。Mac OS X 10.4 转向了对 Intel 处理器的支持,因此苹果发布了一个测试版本的 BootCamp 来让用户安装 Mac OS X、Windows 双系统,并在 Mac OS X 10.5 正式集成进系统。
哪怕在 Mac OS X 系统运行,BootCamp 也可以实时调整系统主分区的大小,来空出磁盘空间给 Windows,因此,HFS+ 又需要支持动态分区大小调整。在 Mac OS X 10.5 中集成了 Time Machine,它是苹果公司所推出备份的工具程序,于 2006 年 8 月 7 日在苹果计算机全球研发者大会(WWDC)中首次公开,成为当天观众欢呼声最高的功能。Time Machine 对于修改过的文件会在备份盘上保存一个新拷贝,而对于不变的内容,仅在备份盘上存一个指向先前文件的硬链接。因此每一次快照只保存改动的文件,而别的文件只保存占用空间很少的硬链接。但 Unix 一般只支持文件的硬链接而不支持目录的硬链接。因此 HFS+ 在这点上走得比 Unix 文件系统更远,提供了对于目录的硬链接支持。在 Mac OS X 10.6 中,HFS+ 甚至支持文件系统压缩,使得安装后占用比 Mac OS X 10.5 少得多的空间。Mac OS X 10.7 提出了 FileVault2,能加密整个磁盘而不是一个用户目录。这些功能我们在为读者介绍每个发行版时亦会提到,但总之读者看到,HFS+ 的功能随着 Mac OS X 的商业需求不断被扩展。“我在做了这么多工作后回想才发现,我们为 HFS+ 增加了那么多新功能,”苹果前文件系统开发者 Don Brady 如是说。
由于 HFS+ 经过后来若干年的发展,提供的功能已不逊于 UFS,甚至更多更好,故至 Mac OS X 10.5 砍掉了安装至 UFS 的支持。HFS+ 成为唯一正式的 Mac OS X 系统。
HFS+ 并不完美
HFS+ 自发布以来,几乎每个发行版都有令人欣喜的改动。它也逐渐成为一个非常完善的文件系统。但 HFS+ 立足于 HFS 设计,HFS 已有 27 年的历史,HFS+ 亦有 14 年历史。这个文件系统有太多的历史包袱,为考虑兼容性,这些陈旧的设计并不能被推翻重来。
HFS+ 基于B-树实现,当查找B-树中未使用的节点时,HFS+ 只能每次处理 16 位,原因是老 Mac 使用的 Motorola 的 68K 芯片原生支持 16 位的数据操作。但不管是 PowerPC 还是 Intel,寄存器都支持 256 位宽的寄存器。
HFS+ 的元数据(metadata)都以大字节序保存,原因是 Motorola 的 68k 和后来 Mac 使用的 PowerPC 都使用大字节序。但经过 Intel 迁移后,当今的 Mac 都使用 Intel 芯片,而 Intel 芯片是使用小字节序的。因此每当数据读取或存入时,还要经过小字节序和大字节序的转换。远古时期磁盘很慢,计算机处理器的速度也很低,因此进行一次磁盘操作会占用较多的时间,HFS+ 的时间分辨率为一秒,但当今的磁盘、处理器处理一次文件系统操作的时间远小于一秒,因此所有主流磁盘文件系统的时间分辨率都是一至数百纳秒级别的。
HFS+ 的元数据有全局锁,同一时间只有一个进程可以访问更新文件系统。在单核处理器连手机平板都较少见到的当今,这种设计显得很幼稚。
HFS+ 亦没有稀疏文件的支持。例如我们在 SQL 中建立了一个数据库,SQL 分配了 10GB 的文件给这个数据库,并且在文件头和文件尾写上一些字节的数据。而由于我们还没有给这个数据库添加新的数据,所以这 10GB 的文件除了头尾外其他字节都为0。现代的文件系统基本都支持稀疏文件,也就是说,当处理这个数据库操作时,事实上往磁盘写入的数据只有那文件头和文件尾的若干字节。而 HFS+ 则需要把那些 0 也写上,因此会完整写入 10GB 的数据,耗费长得多的时间。
此外,HFS+ 不具备元数据校验功能、快照功能、写入时复制功能、就地执行功能、逻辑卷管理功能等很多现代磁盘系统所具备的功能,也不能动态调整文件块大小。这些功能的加入并不容易。
其中最要命的是,HFS+ 不像一些先进的文件系统,支持写入时复制事务模型,也没有快照和克隆。这使得用户数据时时处于风险之中。例如由于因为断电、内核崩溃等原因,文件系统上写到一半的数据,小则导致个别文件损坏,大则导致整个文件系统崩溃。在生产领域,这样不可靠的文件系统,很有可能带来致命的灾难。
正是由于上述这些原因,连我们介绍过的短视的 Linus Torvalds 都认为 HFS+ 是个垃圾文件系统。苹果自然受不了这种侮辱,因此,干掉 HFS+ 势在必行。用什么取代 HFS+ 呢?苹果开始秘密研发下一代的文件系统。
(下)
由于各种缺点,干掉 HFS+ 势在必行,然而用什么取代 HFS+ 呢?苹果开始秘密研发下一代的文件系统——ZFS,然而在诸多因素的干扰下,Mac OS X 的 ZFS 支持却只是昙花一现,未来文件系统之路将走向何方?
文件系统的新时代——ZFS
为了代替 HFS+,苹果开始为研发下一代文件系统招兵买马,准备大干一场。但这时 Sun 公司的工作让苹果的员工们为之一振。
2004 年,Sun 公司发表了其杰出的文件系统ZFS。这是一个 128 位的文件系统,本为 Solaris 操作系统开发,于 2005 年 10 月 31 日并入了 Solaris开发的主干原始码。后成为一个使用 CDDL 协议条款授权的开源项目。
ZFS 是一个具有高存储容量、文件系统与卷管理概念整合、崭新的磁碟逻辑结构的轻量级文件系统,同时也是一个便捷的存储池管理系统。
ZFS 的一个重大特点就是拥有大容量。ZFS 是一个 128 位的文件系统,这意味着它能存储 1800 亿亿(18.4×1018)倍于当前 64 位文件系统的数据。ZFS 的设计如此超前以至于这个极限就当前现实而言可能永远无法遇到。项目领导 Bonwick 曾说:“要填满一个 128 位的文件系统,将耗尽地球上所有存储设备,除非你拥有煮沸整个海洋的能量。”假设每秒钟创建 1000 个新文件,达到 ZFS 文件数的极限需要约 9000 年。
此外,ZFS 的一个重要指导思想是不单单去做一个文件系统,而是实现一套完整的卷管理方案。不同于传统文件系统需要驻留于单独设备或者需要一个卷管理系统去使用一个以上的设备,ZFS 建立在虚拟的被称为“zpools”的存储池之上。每个存储池由若干虚拟设备组成。这些虚拟设备可以是原始磁碟,也可能是一 RAID1 镜像设备,或是非标准 RAID 等级的多磁碟组。于是 zpool 上的文件系统可以使用这些虚拟设备的总存储容量。
有了卷管理方案后,ZFS 走得更远,加入了快照和克隆等实用的文件系统功能。当 ZFS 写新数据时,包含旧数据的块被保留,磁盘只写入修改过的那部分数据块。所以快照的建立非常快,只存储两个快照间的数据差异,因此快照也是空间优化的。克隆指两个独立的文件系统共享一些列的块。当任何一个克隆版本的文件系统被改变时,只创建改动的数据块,因此非常快速,也占用少得多的空间。
而 ZFS 最大的贡献在于它是第一个支持写入时复制功能(COW,copy on write)的文件系统。所有文件系统中的块都包括 256 位的校验值。含有活动数据的块从来不被覆盖;而是分配一个新块,并把修改过的数据写在新块上。所有与该块相关的元数据块都被重新读、分配和重写。因此,当一个数据写入时发生了任何意外错误,原先的数据依然可以被访问,且文件系统知道哪个操作出了错误而没有完成。ZFS 的快照和克隆正是因此项技术而得以实现。
ZFS 对于用户而言,界面友好。先前 Unix的卷管理非常烦琐,FreeBSD 因此还建了一套宏伟的框架,给逻辑卷管理做深层次的抽象。而 ZFS 文件系统自带卷管理方案,几乎所有烦琐复杂的操作都能在一两条命令内完成,我用传统的卷管理工具已有近十个年头,第一次使用 ZFS 时,完全被其易用性震撼,所以我毫不犹豫地把手头所有的服务器迁移到了 ZFS。
由于 ZFS 各种美好,加上其开源性质,所有的操作系统都想支持它。Solaris、OpenSolaris 项目一直作为标准实现供其他系统参考。Pawe Jakub Dawidek 把 ZFS 移到 FreeBSD,并在 2009 年进入了 FreeBSD 7,作为 FreeBSD 第七版最耀眼的三项功能之一(另一项功能是我们先前提到的 ULE,以及 Sun DTrace 的移植工作)。NetBSD 在 2009 年正式收纳 ZFS。Linux 则麻烦得多,因为 Linux 内核的协议 GPL 是个和很多协议都水火不容的奇葩协议,ZFS 分发所采用的 CDDL 和 GPL 会产生冲突,所以一方面 FUSE 提供了用户空间层面的支持;另一方面,由 Oracle 牵头,专为 Linux 开发 Btrfs,事实上就是一个 ZFS 的山寨版,可惜折腾了几年,Oracle 自己又把 Sun 收购了,且到我撰写此文时 Btrfs 依然没有正式的稳定版本发布。
昙花一现的 ZFS 梦
刚才提到,苹果在招兵买马,雇员工开发新一代的文件系统,而 Chris Emura(Apple CoreOS 的文件系统开发经理)及 Don Brady(先前提到,此人领导 HFS+ 的开发)两个富有经验的文件系统开发者却被衣服一样晾在了一边无所事事。2006 年,刚刚提到的 Pawe Jakub Dawidek 正在往 FreeBSD 迁移 Sun 的 ZFS,这项工作立刻引起了 Chris Emura 及 Don Brady 的高度兴趣。由于 ZFS 在 Unix 系统高度的可移植性,加上 Mac OS X 本就是 FreeBSD 的近亲,闲得发慌的两人立即打算往 Mac OS X 移植 ZFS。在 2007 年 4 月 6 日,FreeBSD 的移植宣告完成,等待合并进主干。一周后,两位苹果员工亦成功地完成了 Mac OS X 的移植。
苹果一看两人的 ZFS 的移植工作大有前途,立即跟进。2007 年的苹果全球开发者大会上,苹果让 Chris Emura 及 Don Brady 举办了一场小型讲话,介绍 Mac OS X 对 ZFS 的支持。这场讲话先前并没有在官方声明中告示,但讲话的报告厅依然挤满了听众。随后 ZFS 移植的源码在 Mac OS Forge 公布。在最终版的 Mac OS X 10.5 带有试验性的 ZFS 只读支持,以命令行方式提供。用户可以挂载 ZFS 的存储池,并对池中的文件系统进行读取操作。
苹果一直使移植并使用 Sun 的关键技术,除了 Java 以外,Mac OS X 10.5 的 Xcode 套件也加入了 DTrace 的支持,并提供了一个好用的图形界面 Instruments 让开发者更方便地调用 DTrace。ZFS 除了解决 HFS+ 的所有问题,提供安全可靠的文件系统基础外,还可以简化苹果许多软件的实现。例如前文提到的 Mac OS X 10.5 的 Time Machine,实现颇为烦琐,依赖于给 HFS+ 提供新功能,功能层也需要增加很多的和备份相关的代码。而 ZFS 默认就支持快照,将大大简化 Time Machine 的实现,并使该功能更稳定可靠。事实上在 2008 年 11 月 25 日,Sun 发布了 OpenSolaris 2008.11 版,其中给 GNOME 的 Nautilus 增加了一个使用 ZFS 的快照功能的图形界面插件名为 Time Slider,和苹果的 Time Machine 提供了非常相近的功能,我在使用后感觉不错。
因此在 WWDC 2008 上,Snow Leopard 被提出,其中一项很重要的卖点就是对 ZFS 的完整的读写支持。在 Mac OS X 的服务器版,苹果也将提供一套图形界面工具来方便维护人员管理 ZFS 存储池。在当时的 Snow Leopard Server 主页上,苹果声明 ZFS 将作为一项主推功能。
但好景不长,一年后的苹果开发者大会时,ZFS 相关的内容被悄悄从任何公开的文档、网站、发布会中撤下,没有给出任何的理由。Mac OS Forge 上的 ZFS 代码和页面也被苹果移除。外界有很多对此的猜测,但没有任何猜测得到苹果官方的或是哪怕离职员工的证实。
猜测之一是当时 Sun 刚被 Oracle 收购,而 Oracle 长期投资 ZFS 的竞争产品 Btrfs。因此苹果觉得 ZFS 的前途不甚明朗。
猜测之二是 ZFS 的关键技术 Copy On Write 有专利问题,NetApp 声称他们拥有 COW 的专利因此在起诉 Sun,苹果不想在当中冒风险。
猜测之三是 ZFS 和苹果的 XNU 内核有协议冲突。我虽然不学法律,但我认为这个说法不完全对,因为 ZFS 和 DTrace 一样,是以 CDDL 发布的开源软件,既然 DTrace 可以无后顾之忧地加入到 XNU 中,ZFS 也没有理由不可以。事实上,除了 Linux 这种少数使用 GPL 这类奇葩协议的内核,大多数系统的协议都不和 CDDL 冲突。FreeBSD 也好,Mac OS X 10.5 也罢,都把 ZFS 加入内核发布。
但事实上,如果把三种猜测并在一起,我们可以看到一个更全局的可能性:对于猜测之二,苹果可能并非想使用 CDDL,而是想从 Sun 买下一个私有的协议,这样一来,Sun 不但提供更好的技术支持,出了问题(比如猜测二中的专利问题)也可以让 Sun 为自己背黑锅。结果 Sun 可能和苹果价格谈不拢,加上猜测之一提到的 Sun 大势已去,让苹果觉得还不如自己造个轮子来得方便。Sun 公司开发 ZFS 的主力 Jeff Bonwick 虽不能提供详细的信息,但他基本证实了这种说法。
无论如何,Mac OS X的 ZFS 支持,如昙花一现般消失了。
未来文件系统之路走向何方
虽然 Mac OS X的 ZFS 支持被砍了,开源社区依然想继续开发 Mac OS Forge 先前版本的移植。如 MacZFS 项目不遗余力地给 Mac OS X 10.5~10.7 提供 ZFS 读写支持。Don Brady 在苹果将对 ZFS 的支持砍掉之后从工作了 20 多年的苹果离职,开了一家名为 Ten’s Complement 的公司,该公司提供 Z-410,较 MacZFS 提供更新更稳定的移植。
不过,砍了 ZFS 后的苹果目标也变得更清晰——和 Sun 的谈判让苹果觉得与其支付高额的协议费,还不如雇人自己做个新的,再说了,作为比 Sun 大得多的 IT 公司,苹果可以轻而易举地搞个更强大的东西灭了它,因为 ZFS 其实也不如传说中的那样好。
首先,时代在进步。ZFS 之后,又有很多新的和文件系统相关的研究,如 Ohad Rodeh 的论文,即成为后来 BtrFS 实现的基础,可能比 ZFS 做得更好。
其次,ZFS 是十年前开始设计的文件系统,但十年中,存储工具已发生了重大的变化。ZFS 为传统磁盘设计,但传统磁盘的市场空间已不断被 SSD、闪存的吞食。尤其是 MacBook Air 中使用的 Flash 存储器便宜好用又小巧,可能将来会在 MacBook Pro 甚至 iMac 中得到更大的推广。采用为传统磁盘优化的 ZFS 就不显得那么有吸引力。
最后,ZFS 和苹果有不同的用户群。ZFS 目标用户是大企业的工作站和服务器。在那里,大容量的存储空间、高级的卷管理显得非常重要,但苹果面对的基本都是个人用户——先前苹果还卖服务器,但后来 Xserve 都被苹果砍了。有几个个人用户需要使用到 ZFS 这些高级的功能呢?更重要的,苹果的主要利润将移到 iPhone、iPod、iPad、Apple TV 这些小设备上,ZFS 需要占用大量的内存来实现文件系统操作,在这些小设备上,内存很少,ZFS 根本跑不起来。
苹果非常清楚这些问题,工程师们现在一定在紧锣密鼓地开发下一代文件系统。在 10.7 及 10.8 中,这套文件系统并未浮出水面,但一些细节值得留意。在 10.7 中,苹果发布了 Core Storage,但并未声张。这是一套逻辑卷管理工具,类似于前文提到的 FreeBSD 的 GEOM。这个版本的 File Vault 2 亦使用 Core Storage 重写。可以看到虽然苹果在上层不断地淡化文件系统的概念,例如 iCloud 的发布和 iOS 中对于文件这一概念的故意忽略,但苹果在底层文件系统上的动作越来越大,想必在将来,苹果定会让我们感到重大的惊喜。