可以毫不夸张地说,UNIX模型就是现代操作系统的原型!不管是原汁原味的UNIX各大系列比如AIX,Solaris,HP-UX,FreeBSD,NetBSD,...还是类UNIX比如Linux...还是基于Windows NT架构的各种微软操作系统,其基本思想都是来源于UNIX。虽然这些系统一个比一个复杂,但是请记住一句话:所有的基本思想都是也必须是朴素的,简单的!
也许,很多人看到这里就觉得有点不屑一顾,毕竟他们觉得自己是技术狂人,觉得只有摆弄复杂的东西才能证明自己的学识和技术,认为上世纪70年代的被UNIX v6是一个过时的系统,里面的内容早就被岁月无情地抛弃了,但事实上,如果你真的看了那个时代的UNIX源码,或者读了莱昂氏的神书,并且细细琢磨之后,你会发现,那些朴素的观念一点也不过时,我们如今的很多技术所依托的思想早在1975年的时候就已经被朴素地实现了。三岁看老,无视历史就是藐视未来。
如果你想知道下一步我们要做什么,听别人讲是没用的,你必须亲自翻开历史。正如给你一条曲线的一部分,问你该曲线的下一步走势,你肯定可以猜个八九不离十,因为任何事物都是循着历史前进的,这就是读史使人明智。前些日子和朋友一起吃饭,请客的是位芯片专家,但是对除芯片以外的任何事物都是不屑一顾,好一个中国传统的所谓术业有专攻,就好像世界上除了芯片是高科技之外,其它的都是小儿科,好一个排他!但是你有没有通过这件事想到些什么,这位朋友是典型的中国式学究,所谓的术业有专攻的学究,正是这种所谓的专攻,导致我们中国事实上从来没有引领过一个时代,我们从早的说,农业革命发力于美索不达米亚以及尼罗河中下游,青铜时代是西亚和东欧游牧民族引领的,民主观念来自于希腊,帝国主义来自于罗马,工业革命来自于西欧,电力革命来自西欧,计算机革命来自美国,关于最后这一点我会详细说,以上这些我并没有提到文艺复兴,那是我故意的,因为如果我提到了文艺复兴,很多人就会说,我们中国也有很多这类事情,比如周昭共和,秦王扫六合,汉武大帝,光武中兴,贞观之治,...康乾盛世,但是你要知道,这些和文艺复兴一样,都是局域性事件,不管怎样,我们现在住着楼房,玩手机,玩电脑,开汽车,坐高铁,所有这些都是舶来品,没有一样是我们自己的,原因就在于我们的思维不够发散,我们做软件就是做软件,不知道一些思想还可以用于硬件,我们学OO学得很精通,精通了C++,Java,却不知道OO来自AI...知道列维飞行却不知道它和计算机内存访问模型的关联...
说了这么多看似没用的,事实上是想说,弄点别的吧,虽然我们都是搞IT的。很多编程高手可能真的不屑于1975年的UNIX源代码,那就是历史,这位高手知道怎么设计类,知道怎么实现一个排序算法,但是也仅仅如此,说到底,他只是一个高价的雇佣兵,永远都成不了将军,而且随时都可能阵亡。我写这个系列的文章,就是想说明一点,用历史的眼光看操作系统以及任何技术的发展,你会得到更多,往窄的说,UNIX就是一座宝库,凡是你在现代软件中能找到的思想,在UNIX中都是它的影子。
在正式开始之前,简单的说一下背景。我们现在所面对的计算机早就不是40年前的计算机了,由于需要产业化和降低门槛,整个计算机产业和通讯产业经历了多次整合,最终我们把软件和硬件严格区分开来,并且在软件和硬件的各个组成部分之间也严格划分了界限,这样的结果就是有利于社会化分工,人们走上了术业有专攻的专业化道路(其实很多思想家从古希腊梭伦时代开始就开始抨击专业化了),但是其缺点也是显而易见的,那就是“对于geek而言,现代计算机已经不好玩了”。
现代计算机已经不好玩了
看了Linus的自传,认同一点,现在的计算机已经不适合玩了,主要原因是已经不好玩了。Linus始终认为技术为娱乐而生!值得注意的是,Linus的自传《Just for fun》并非要表达“计算机已经不好玩”这样的观点,但是Linus认为,现在的计算机“和你的汽车一样复杂”,已经不适合让你随性折腾了。
事实上,由于计算机已经不再好玩,我们也就无法或者很难就计算机本身再创造所谓的“时代”!现在搞计算机已经沦为了一种职业,一种谋生的手段,其内容也随之沦为了套路和规则!要想创造一个时代,你必须不能术业有专攻,你必须是全才。
时代是玩出来的,我以为!当时的那帮人是无心为之的,之后当所有的玩法标准化时,利益便主宰了一切。能玩得转的东西必须是简单的,太复杂的东西在艺术上没有美感,在工程学上你必须花费巨大的精力去进行复杂性管理,因此本质的东西便被隐藏在复杂性之下了,你很难去挖掘它,欣赏它。近期读了几本书,颇有感受。太复杂的东西,可以向不懂的人大肆炫耀,但事实上,听的人根本不知道你在说什么,你只是自说自话,孤芳自赏罢了。你可曾想过,为何大师都是扎堆出现的,古希腊的哲学家,中国先秦的诸子,文艺复兴时期的画家,20世纪初的物理学家,20世纪50-60年代的计算机天才,20世纪70年代的黑客...
计算机产业也是从全民折腾时代开始的,这就和希腊的梭伦改革一样,但是和希腊的伯里克利终结了一个时代一样,产业化,专业化也终结了计算机的嬉皮时代,现如今,只有Richard Stallman,Eric S Raymond他们还在尽力延续那个geek的时代,而事实上,如今的专业化让很多准geek面对复杂的计算心有余而力不足。那么,这个geek时代是怎么被终结的呢?我从软件和硬件相遇的地方说起。
硬件和软件相遇的地方
最初设计的机器并不是想让每个人都可以操控它的,它是隶属于精英阶层的。但是如果有人发现了这些机器中蕴藏的巨大的商机,他便会推动一种全民化运动,全民化运动发展到一定程度,技术已经足够复杂,就会出现专业化,于是这种技术便逐渐远离了大众,再次被精英所垄断,正如它最初时那样。
对于计算机技术而言,我把前一个精英时代的技术成为硬件,我把后一个精英时代的技术称为软件,如今在硬件领域,拥有硅晶体技术,Verilog,HDL等,在软件领域,有JAVA,Python,PHP,OO,设计模式,敏捷开发等术语,专业性让所有这些东西都具有了排他的性质,而创造计算机时代的地方,正是软件和硬件相遇的地方,那就是汇编语言向C语言进化的地方。
要知道,如果你不懂机器的特性,你是用不好汇编语言的,如果你不懂机器的特性,你也不可能用C写出最高效的代码,反过来,如果你只懂C语言,你也不可能将机器的能力发挥的淋漓尽致,汇编和C,这就是软件和硬件相遇的地方,时代就是它们开创的,历史就是它们写就的。
汇编语言编程让人循着机器指令走,程序员很难构建自己的高层逻辑,因为你要花费大量的精力在指令本身上,C语言出现后,人们的逻辑思维被解放了,你可以写诸如a=b+c这类语句了,而不必“先将立即数放入一个寄存器,再将立即数....两个寄存器的值相加或者一个寄存器和一个立即数直接相加,结果存在...”。C语言的最大共享在于解决了“寻址问题”,这就意味着它解决了所有的问题,因为计算机编程可以归为寻址的艺术,C将所有的寻址问题归结为一个概念,那就是指针!要知道,现代存储式计算机的运行,唯一的问题就是寻址问题,不管是数据还是指令,都存在内存中,你必须有办法可以在正确的地方找到指令或者数据。C指针屏蔽了所有的寻址细节,让轻松构建应用逻辑成了可能,这些应用逻辑包括复杂的条件语句,循环语句,goto语句,甚至缓冲区溢出。
硬件和软件的关系就是怎么做和做什么的关系,编程人员告诉硬件做什么之后就可以放心做别的事了,因为他相信硬件知道怎么做。这个接口就是C语言,而硬件的内部电路逻辑则实现了“怎么做”的逻辑。
水不知冰有多冷,冰不知水的多情。同一个东西,一旦被隔离,那就是隔行如隔山,本来硬件和软件是一家的,可如今变成了排他的两个行业,就算在软件行业内部,还经常有各种排他性的隔离,诸如什么“搞底层的”,“搞协议栈的”,“写类的”,...软硬通吃的全栈程序员现在被人和酷(苦)逼联系在了一起。但是在30-40年前,计算机时代就是这帮全栈程序员开创的!在软件和硬件相遇的地方,那帮人同时属于硬件精英和软件精英,实际上他们引领的是一种全民玩转计算机的嘻哈时代,这个时代被20世纪70-80年代的黑客们所延续,众所周知的业界名人几乎都出自那个由UNIX开启被黑客精神延续的时代,比如乔布斯的Apple,比尔.盖茨的Microsoft,理查德.斯托曼的GNU,比尔.乔伊的BSD,IBM PC,Intel IA32,...不胜枚举。这是一个简单纯真的时代,一个精简浓缩的年代。
PC技术与UNIX技术
软件和硬件相遇的地方就是UNIX诞生的地方,UNIX作为一个营养丰富的根,注定会长成参天大树,这棵树上的果实太多,以至于几乎囊括的所有现如今我们在使用的技术。
PC技术使得计算机小型化,进而出现了微型化的趋势,随着工艺越来越先进,硬件越来越小,越来越便宜,巨大的商机促进了PC技术的发展,但是软件这方面却没有实时跟进。多用户多任务分时系统当时最风靡的就是UNIX,然而UNIX却受制于各种非技术因素的控制,正因为如此,才让微软,苹果等公司占了先机,它们两家你追我赶,一直到今天,导致这个结果的另一个原因是UNIX从来都不属于草根(正因为如此才有了GNU),而不管是苹果还是微软,都和一个叫“家酿俱乐部”的草根组织有关,大家在一个相对平和的环境中展示自己的机器和技术,那个时代是黑客的时代。可以说,UNIX并没有赶上PC时代的潮流,但是这就是UNIX,正如启蒙思想家本人不参加法国大革命一样,UNIX作为一位思想先驱,它的一些古老的观念,甚至Windows 8系统还在使用。SVR4 VM的设计概念, 可以说是颠覆了传统对记忆体的理解;文件抽象使得IO接口变得简单;基于页面交换和按需调页实现的虚拟内存的思想影响了几乎所有的操作系统设计;分级存储思想更绝妙,在CPU内部,CPU缓存是内存的Cache,在VM里面,物理内存是虚拟内存的Cache,在MMU,物理内存是磁盘或者网络的Cache...;最重要的还是UNIX进程模型(线程模型和进程组模型都是进程模型的扩展),它的重要性我真的不知道该怎么才能说得清。
不得不提到的是Intel和微软以及PC技术这三者的关系,但是这个话题过大,也只能在此提示一下,总之,PC技术造就了两大帝国,而Intel和微软配合太默契,以至于成了一个好像罗马帝国那样的存在...
Mac OS X技术
可以说,PC技术所依托的Intel技术和微软的技术是并行发展的,其间出现了太多太多花哨的特性,甚至很多东西都直接固化在了硬件中。如今,苹果也采用了Intel芯片,然而它并没有采用微软的技术,而是采用了UNIX系统Mach/BSD来构建自己的操作系统Mac OS X,可以说正是苹果将PC技术拉进了UNIX,在另一个方向,Android正在和iOS争夺市场,但是不管是Android还是iOS,其底层都是基于直接的UNIX思想构建的系统,一个是Linux,一个是Mach/BSD。返璞归真,UNIX技术历经40年,其基本观念以及核心基本没有变过,这足以说明它的设计是多么的优秀。
UNIX成功的观念是,它从来不关注实现细节,因为细节会将你拉离目标。UNIX只提供基本理念,因此,不管是AIX,Solaris,Mach,xxBSD,它们的实现绝对截然不同,但是都叫做UNIX。如果微软愿意,Windows NT也可以称为UNIX,因为NT系统在70%的程度上实现了UNIX的基本理念。
在UNIX之外,有另一条演进道路,那就是复杂性演化。它是严格遵循硬件的最新特性,软件迎合硬件,硬件惯坏软件,大家彼此阿谀奉承,实际上大家都忘了,其实原本大家都是一家。有些时候,仅仅为了一些单一的商业利益,就会把一些复杂但不普适的机制固化在硬件中,这一点的反例就是RISC架构,它就是UNIX在硬件领域的直接体现。
复杂性演化举例
数据的地址按照其类型的长度倍数自然对齐是一个不成文的编程约定。可是为何作如此的约定呢?难道不对齐就不行吗?在某些架构的处理器上,真的就是不行,比如很多的RISC处理器。然而在CISC架构的处理器,比如Intel/AMD x86架构处理器上,为了编程的方便,核心可以替你完成这种费力的操作。这一切的根源在哪里?
在对整个事情分析之前,必须要明白的是,现代微处理器是超大规模集成电路的结晶,芯片是雕刻在硅这种半导体上的,工艺极其考究,因此布线的简单就是一切的根本,在硅晶体上雕刻电路是多么的令人崇拜啊(还记得我们学过的《核舟记》吗)。硅晶体雕刻不像高级语言编程,它甚至不允许你将事情搞得太复杂(主要是晶体的布线难度),因此很多事情就要规定死,比如数据的自然对齐。完美的处理器架构只需要实现一个完成任意操作的最小指令集即可,架构简单,可以将更多的硅晶体的空间用于实现高效的流水线而不是复杂的指令(某个复杂的指令往往和其他复杂指令拥有公共部分,这就造成了空间的浪费),这种处理器叫做RISC处理器。以上这个事实是人们在早期集成电路技术开启这个新时代的伊始,走了很多弯路才总结出来的,只可惜,Intel就是那个最先吃螃蟹的人,随后它获得了巨大的成功,兼容它的指令集将AMD也拉下了水。
我们知道,抛开微处理器内部必须实现的运算逻辑,其与外部的接口就是寻址和IO。
Intel在最初实现16位微处理器的时候,恨不得为每个我们能想到的寻址逻辑都实现一条硬件指令,事实上它也是这么努力做的。翻开任何一本汇编语言的书,首先映入眼帘的就是大量的寻址指令,如果你对着Intel的手册观摩,你会发现很多寄存器都不是作为操作数存在的,而是内置于指令本身!因此mov eax 1和mov ebx 1可能就是两条完全不同的指令,而不仅仅是操作数的不同,就这样,复杂指令的实现占据了大量的芯片面积,由于单独指令完成的事务依然可分割,且长度不一,Intel便很难实现高效的长流水线。机器指令就是硬件和软件相遇的地方,是软件请求到硬件落实的唯一接口,为了兼容曾经的软件,接口是不能随意更改的,但是当Intel意识到指令必须是足够简化,功能足够单一以便实现长流水线时,它上面的应用程序已经遍地开花了。于是Intel和AMD的策略就是修改指令的实现,取指令之后,将单条的复杂指令分割为多条简单指令,即使用了万变不离其衷的神喻-添加一个中间层!
于是,Intel的指令事实上最终也是由简单操作实现的,这些操作称为微操作,实现一条指令的微操作集合称为微程序,所有的微程序称为微码。于是乎,x86架构实际上只是在接口是CISC体系架构,其内核已经是RISC体系架构了!
为了解释内存对齐问题,必须知道其物理构造,关键并不是如何设计内存芯片,而是如何将内存芯片的引脚和CPU引出的地址总线对应起来,如何建立两者之间的关系是最重要的,别忘了,计算机在CPU之外要解决的唯一问题就是寻址问题!如果数据是任意对齐的,那么在CPU发出寻址指令时,就可能出现数据存在两片芯片上的情况,而为了布线的简单,这种寻址指令必须两次完成,如果两次操作之间锁住总线,就会大大降低效率,如果不锁总线,就会有原子性问题。要求编译器或者程序员不写出那样的指令是最简单的,因此这个任务就落在了编译器或程序员的头上。这就是为何在某些机器上数据一定要自然长度对齐。
UNIX之初
应用程序蓬勃发展的今天,很难想到UNIX等分时系统在构建之初的目标。分时系统是为了让系统以一种流水线的方式代替批处理,让用户不必长时间等待,实际上当时的分时系统是以一种时间片错觉的方式展示给系统用户的,事实上,系统的吞吐量和总的工作延迟并没有改善,改善的只是人们对这种错觉的认可。这就是最初的分时系统。那么UNIX呢?UNIX在很多方面走得更远,对于AT&T来讲,它的电话业务是大头,它当然希望用一种性价比更加高的投资为用户提供一种基于错觉的服务方式,而用户根本就感觉不到这种差异。贝尔实验室最初的分时系统并非去运行什么应用,而是为了控制语音通话,但是后来,它走向了终端。
电话用户很容易和打印机,终端用户联系起来,因为它们都属于远程用户,共享同一主机资源。分时系统出现之前,要么等,要么建立并行系统,不管对于用户来讲,还是对于投资回报而言,都不是令人满意的,分时系统解决了所有的问题。文件抽象也是为了更方便的I/O,因此可以看到,早期的UNIX的I/O扮演了什么角色。正如现在的路由器,交换机一样,早期的UNIX在进程模型抽象层面提供控制平面,在文件抽象层面提供数据平面,它的内核就是为控制平面而生,而数据平面应该尽可能使用用户态的I/O来完成,遵循这一原则的,将会得到回报,直到今天依然如是。
幸运的是,今天很多的厂商已经走向了这条路,也出现了很多这样的技术,比如PF_RING技术。而这一切,早在朴素的UNIX时代就已经被决定了。