Many worters are in it and make many manythingss是什么地方

第一章 UNIX——世界上第一个电脑病毒

“伯克利的两项最著名的产品是UNIX和LSD (一种毒品),我想这不是巧合”

病毒依赖于微小的个体和强大的适应性得以生存。它们并不复杂:它们没有为呼吸,新

陈代谢,肌体活动等功能提供什么,只有足够的DNA或RNA以供繁衍。比如,肺炎病毒比

起它们入侵的细胞要小得多,但它们在每个肺炎流行季节都能够产生新的变种,造成无

病毒做的事情不多,所以不需要很大。有人认为病毒不是生物,只是一些有破坏性的酸

病毒经常变异,以便以不同的方式攻击不同的细胞。据说AIDS就是由猴子身上的病毒变

UNIX具有以上所有优点。在它刚诞生时,很小,功能不多,缺乏真正操作系统所需要的

功能(如文件映射,告诉IO,健壮的文件系统,设备锁,合理的进程间通讯),它的移

植性很好。UNIX耗尽主机的资源,没有系统管理员的时时呵护,UNIX会不断恐慌,core

dump,挂起。UNIX不断变异:同一个补丁在一个版本上工作,在另一个版本上就不行。

UNIX是有用户界面的计算机病毒。

自从UNIX 80年代开始流行以来,UNIX厂商一直在努力进行UNIX标准化工作。SUN, IBM,

HP和DEC在这个他们自己制造的难题上倾注了数百万美元。

为什么UNIX厂商不喜欢UNIX标准化?

许多用户受够了复杂繁多的UNIX,最终只好使用Windows,因为他们的这个UNIX无法支持

那个UNIX上的应用程序。

如果UNIX标准化了,谁还会买SUN的机器呢

欢迎新用户如同用一把上了六颗子弹的左轮枪玩俄罗斯轮盘赌

Ken Thompson 自己设计过一辆汽车。和其他车不同,它没有速度计、汽油计,也没有那

些愚蠢的指示灯讨司机的厌。如果司机犯了什么错误,仪表盘上就会出现一个大大的“

?”。“有经验的司机,”Thompson说,“应该知道哪儿搞错了。”

计算机系统的新手需要一个友好的系统。至少,一个得体的系统会这样招待自己的客人

与功能有逻辑关系的命令名

一致的命令行为和命令行参数解析

当命令失败时,给出可理解和有用的错误反馈

在建造UNIX的过程中,从没邀请过住户。来访的都是些戴着安全帽的建筑工人,被安插

在这个破木板房子的各个角落。不幸的是,不仅没有人性因素(human factors)工程师

的参与,而且住户的需要就从来没有被考虑过。所以抽水马桶、中央供暖、窗户等这些

方便设施在后期就很难再添加了。但是建筑师们仍然为UNIX的设计而骄傲,似乎他们并

不介意在一个没有烟火探测器的屋子里睡觉。

在其发展的大部分历史中,UNIX只是大学和工业研究人员的研究工具。随着大批便宜工

作站的出现,UNIX作为平台软件进入了新时代。这一变化大约发生在1990年,其标志就

是工作站厂商把C编译器从UNIX发布中剔除出去,以降低成本满足非开发用户的需求。可

见,只是最近几年中UNIX厂商才开始考虑非程序员用户的需要,开始为他们提供shell以

UNIX新手总是对UNIX对命令的命名表示惊讶。在DOS和Mac上受的教育不足以让他们体会

到cp、rm、ls这类两字母命令的简洁和优美。

像我们这样用过70年代早期的IO设备的人都能理解,ASR-33 Teletype这类设备的速度、

可靠性,以及它的键盘是万恶之源。和今天这种基于反馈原理、只需要关闭一个微开关

的键盘不同,你必须用足力气揿下Teletype的键至少半英寸,以发动一个类似自行车上

用的小型发电机,在上面操作要冒指骨骨折的危险。

”和”rm”而是”copy”和”remove”了。(Ken Thompson曾被问道如果他能重新设计U

NIX他将做什么修改,他回答说:“我会在creat命令后加上个e。”),科技在拓宽我们

的选择的同时,也能限制我们的选择,此一例也。

20多年过去了,还有什么理由延续这一传统呢?理由就是“历史的无可替代的力量”,

历史就是那些存在的代码和教科书。如果一个厂商用remove替代了rm,那么所有UNIX教

科书就不适用于这一系统了,每个使用rm的shell脚本都需要被修改。而且这也不合POSI

一个世纪前,打字高手由于击键过快,经常把打字键柄搅在一起,工程师设计了QWERTY

键盘,于是问题得到了解决,因为没人能在这样的键盘上打得快。计算机的键盘不再有

机械键柄,但QWERTY的键盘布局仍然在使用。同理,在未来的一个世纪中,我们仍然会

用户十分关心自己的数据和文件。他们使用计算机来产生、分析和存储重要信息。他们

相信计算机能够保护他们的重要财产。如果没有了这种信任,他们和计算机的关系就会

蒙上阴影。UNIX辜负了我们的信任,它拒绝对使用危险命令的用户提供保护。比如rm就

是以删除文件为目的的危险命令。

所有UNIX新手都有不小心无可挽回地删除重要文件的经历,即使是专家和系统管理员也

遇到过。因此而每年损失的时间、精力可能价值几百万美元。这是个值得解决的问题;

我们不理解为何UNIX一直拒绝解决这一问题。难道结果还不够悲惨么?

UNIX比其他操作系统更需要提供恢复删除功能,原因是:

UNIX文件系统没有版本功能

自动的版本维护能保留文件的历史版本,防止新版本冲掉老版本。

UNIX程序员在错误处理方面臭名昭著

许多程序不检查是否所有内容都被写入了磁盘,或被写入的文件是否存在。有些程序总

UNIX shell扩展“*”,而不是其子命令

于是rm这样的命令就无法检查“*”这些危险的参数。即使是DOS也对”del *.*”有些提

UNIX没有undelete命令。许多其他更安全的系统则只是标记被删除文件所用的块为“可

被使用”,然后把它移到一个特殊目录下。如果磁盘满了,这些文件块才会被重新使用

。这一技术不是什么火箭科学,Macintosh在1984年就提出了“回收站”的想法,而Tene

x早在1974年就采用了这一技术。连DOS也提供了简单的undelete功能,虽然并不总有效

这四个问题互相合作,制造了无数无法恢复的重要文件。解决的方法早就存在,但UNIX

“标准”版中却从来没有提供。

许多实际的恐怖故事说明了以上的这些原则。以下是puters新闻组上

流传的一系列故事中的一个:

是否有人曾想执行以下命令:

现在你得到了一个空文件o,以及大量的空间来存放它!

事实上,你可能连o也得不到,因为shell的文档并没有说o是在*被扩展前还是被扩展后

上回书说到如何用rm获得一个空文件和很大的磁盘空间,下面是另一种用法:

我也被rm搞过。有一次我想删除一些/usr/foo/下的东西,我在/usr/foo下敲了以下命令

当我要删除./bin目录时,我忘敲了那个点。我的系统似乎不太喜欢这个。

当受了这一致命一击后,UNIX就彻底完蛋了。聪明的系统会给用户一个恢复的机会(或

至少提醒用户这一操作会导致系统崩溃)。

UNIX迷认为偶尔的文件误删除是正常的。比如,可以参考以下下面这个 (Randal puters

请千万别让人用“安全”命令去替换标准命令。

(1) 许多shell程序会对多嘴的rm感到惊讶,而且也不会想到删除了的文件仍然占有磁

(2) 并不是所有删除操作都是安全的,有户会因此产生一切都能恢复的错觉。

(3) 那些不标准的命令对系统管理员来说尤其可恨。

如果你想有个有确认功能的”rm”,用下面的命令:

在UNIX上,即使是有经验的用户也会误用rm。我从来没有误删除过文件,可是有一天,

我用!r重复执行一个历史命令,我惊讶地发现被运行的是”rm –r *”。

我还听到过一个用户试图删除一个名叫”*”的文件,好在他没有权限。

这个用户还想修改shell来避免对*进行展开。不幸的是,这个补救如同是在渗水的墙上

再刷一层漆,治标不治本。

用户读打印文档的次数比他们参加选举投票的次数还要少。只有触手可及的在线文档才

是有用的。下面我们看看UNIX的man是如何让最需要它的新用户失望的。

不是每个命令都是平等的,有些是外部命令,有些是内部命令。有些有man page,有些

没有。UNIX要求你能区分这些命令。比如,wc, cp和ls是外部命令,它们都有man page

,而fg, jobs, set和alias(这些长文件名是从哪里来的?)是内部命令,它们没有man

UNIX告诉新手用”man command”命令获得帮助,他们可不知道并不是所有命令都是如此

。另外,如果他们的shell设置得有些不标准,他们就只能请教高手来获得帮助了。

错误信息和错误检查?没门!

新手很容易犯错误,比如用错命令,或用错选项。系统应该能识别这些错误,并且反馈

给用户。不幸的是,UNIX程序从来不麻烦自己。相反,UNIX往往把各种错误混在一起,

上面一节我们说明了rm如何容易造成误删除。但你可能不知道不用rm也能很容易地误删

想删除你的文件么?试试编译器

一些cc版本经常根本不考虑用户的可能输入错误,而删除一些源代码文件。一些本科生

我有一个总在运行的程序foo,用来提供网络服务,并且每隔24小时检查系统内部状态。

一天,我cd到foo所在的目录,因为这不是开发目录,我想看看foo的版本是多少。代码

是由RCS来维护的,所以我自然而然地使用了以下命令:

先别管RCS的种种劣迹,也别管ident如何原始疯狂。我这次的麻烦是,我的手指自行其

是地选择了更像一个词的indent而不是ident:

indent是UNIX的一个愚蠢的C代码风格转换工具。那个写indent的混蛋是否判断了一下输

入文件真的为C程序么 (天哪,至少可以看看文件的后缀是否为.c吧)?我想你知道答案

。而且,这个SB(Said Bastard)认为如果你只给了一个参数,那么你就是想要进行在线

风格转换。不过别着急,这个SB 考虑到了可能带来的麻烦,他保存了一个备份>

经过几个小时的专心研究,我得出了一个重要的结论:

现在,你可能觉得很惊讶,但这是事实。这项研究已经被遍布全球的研究人员所证实了

更为重要的是,这不仅仅是摊狗屎,而是又稀又粘的臭狗屎,是大写的臭狗屎。看看下

面这个例子,你就知道了:

[还有一些选项,这里省略了]

实际上,UNIX最好的文档是经常用strings处理程序二进制代码。使用strings你能得到

所有程序中定死了的文件名,环境变量,未公开的选项,怪异的错误信息等等。比如,

如果你想知道cpp是如何去查找头文件的,你最好使用strings而不是man:

这个是我的最爱。我在一个目录下工作,想用find去找另一个目录里的

看来没有找到。不过别忙,看看这个:

我刚刚发现为什么"find"不再工作了。

尽管"find"的语法非常恶心怪异,我还在勉强用它,以免几小时泡在在

迷宫似的文件目录中去寻找文件。

在这个有NFS和符号链接存在的勇敢新世界里,"find"没用了。我们这

里的所谓文件系统是由众多文件服务器和符号链接组成的一团乱麻,

"find"哪个也不想去处理,甚至连选项也不提供... 结果是大量的搜索

路径被无声无息地忽略了。我注意到了这个,是在一个很大的目录下搜

索时结果一无所获,最后发现是因为那个目录是个符号链接。

我不想自己去检查每一个交给find的搜索目录——这他妈应该是find的

工作。我不想去每次这类情况发生时都要去调查一下系统软件。我不想

浪费时间来和SUN或者整个Unix党徒们做斗争。我不想用Unix。恨,恨,

恨,恨,恨,恨,恨,恨。

——Ken (感觉好些了,可还是有点恼)

如果想写个复杂一点的shell脚本对找到的文件进行处理,结果往往会很

奇怪。这是shell传递参数方式所产生的悲惨后果。

的代码)几乎没有注释,充斥这大"段"没有空行的代码,goto随处可见,

绞尽脑汁给妄图读懂它的人制造麻烦。有个骇客感叹到:“阅读Unix代码就好象

走在伸手不见五指的巷子里。我总是停下来摸摸口袋,脑子里回响着一个声音

‘老天,我就要遭劫了。’”

当然,内核代码有它自己的警报系统。四处散布着这样的小小注释:

意思是有什么东西不太对劲儿。你应该知道哪儿出事儿了。

这绝不可能是bug,我的Makefile需要它!

BBN的程序员应该算是另类。大部分Unix程序员是不去修改bug的:他们没有源代

码。即使修改了也于事无补。这就是为什么Unix程序员遇到bug的第一个反应不

是修了它,而是绕过它。

于是我们看到了悲惨的一幕:为什么不一劳永逸地解决问题,而是一错再错?也

许早期的Unix程序员是尼采“永恒轮回”思想的信徒。

对于调试方法,存在着两个截然不同的派别:一个是“外科手术派”,包括流行

于早期ITS和Lisp系统,程序运行过程中始终有调试器参与,如果程序崩溃了,

调试器(也就是所谓外科大夫)会对问题进行诊断医治。

Unix是属于更古老的“尸体解剖派”。Unix下如果一个程序崩溃了,会遗留下一

个core文件,从各个方面看这都和尸体没什么两样。Unix调试器然后会找出死因。

有趣的是,Unix程序常常和人一样,死于本可治疗的疾病、事故以及疏忽。

如果你的程序吐核(core)了,你首先要做的是找到它。这不该太困难,因为core

文件总是很大——4, 8, 甚至12兆。

core文件之所以这么大,是因为它包括了所有用来调试的信息:堆栈,数据,代

码指针等等,无所不包,除了程序的动态状态。如果你在调试一个网络程序,在

你的程序吐核的时候,已经为时太晚了;程序的网络连接已经没有了,更致命的

一击是,所有打开的文件现在都被关上了。

不幸的是,在Unix上只能如此。

例如,不能把调试器作为命令解析器,或者在内核发生异常时把控制交给调试器。

如果想让调试器在程序崩溃时进行接管,那你只能在调试器里面运行所有程序

(是的,有的Unix版本让你用调试器接管一个运行中的进程,但是你手边必须有

一个还有符号的程序文件)。如果你想调试中断代码,你的调试器必须截获每个

中断,然后把合适的中断返回给程序。你能想像emacs里每敲一键都发生3个进程

Unix 哲学是格格不入的。

:( 下面是给我的上司的一个报告:

一个用来更新Make文件的程序使用了一个指针,对它的访问毁掉了

一个存放倚赖关系的数组,这个倚赖关系被用来生成Makefile。直

接后果是生成的错误Makefile不能用于编译任何东西,没有生成所

需的对象文件(.o),所以编译最终失败了。一天的工作就这么付之

东流了,只是因为一个傻瓜认为10个头文件足够所有人使用了,然

后对它进行了极其危险的优化以在1毫秒内生成所有的Make文件!

网络化的坏处是,你没法再闯进某人的办公室里把他的心给挖出来。

(关于堆栈溢出攻击,可参考经典论文href=>

编写健壮程序的最大挑战是如何正确处理错误和其他异常。不幸的是,C

几乎没有为此提供什么帮助。今天在学校里学会编程的人里很少有谁知道异

异常是函数无法正常运行时所产生的一个状态。异常经常发生在请求系统服务时,

比如分配内存,打开文件等。由于C没有提供异常处理支持,程序员必须自己在

服务请求时加入异常处理代码。

例如,下面是所有C语言课本中推荐的使用malloc()分配内存的方法:

个指向这一结构的指针。这段代码说明了如何分配内存给这个结构。因为C没有

显式的异常处理支持,C程序员必须自己去做这件事(就是粗体的那些代

当然你可以不这么干。许多C程序员认为这是小事一桩,从来不做异常处理。他

们的程序往往是这样的:

多么简单,多么干净,大多数系统服务请求都会成功的,是不是?这样的程序在

大多数场合运行良好,直到它们被应用到复杂特殊的地方,往往就会神秘地失效。

Lisp的实现总是包括一个异常处理系统。异常条件包括OUT-OF-MEMORY这样的名

称,程序员可以为特定的异常提供异常处理函数。这些处理函数在异常发生时被

自动调用——程序员不需要介入,也不需要做特殊的检查。适当地使用,可以让

CLU这样的编程语言也有内置的异常处理。每个函数定义都有一系列可以发出的

异常条件。对异常的显式支持可以帮助编译器检查那些未被处理的异常。CLU程

序总是十分健壮,因为编译器逼着CLU程序员去考虑异常处理问题。C程序是个什

C语言迷们会告诉你C的一个最好的功能是预处理器。可事实上,它可能

一个最蹩脚的功能。许多C程序由一堆蜘蛛网似的#ifdef组成 (如果各

个Unix之间能够互相兼容,就几乎不会弄成这样)。不过这仅仅是开始。

C预处理器的最大问题是它把Unix锁在了文本文件的监牢里,然后扔掉

了牢门钥匙。这样除了文本文件以外,C源代码不可能以任何其他方式

存储。为什么?因为未被预处理的C代码不可能被解析。例如:

这里函数foo有两种不同的开头,根据宏'BSD'是否被定义而不同。直接

对它进行解析几乎是不可能的 (就我们所知,从来没实现过)。

这为什么如此可恶?因为这阻碍了我们为编程环境加入更多智能。许多

Unix程序员从没见过这样的环境,不知道自己被剥夺了什么。可是如果

能够对代码进行自动分析,那么就能提供很多非常有用的功能。

让我们再看一个例子。在C语言当道的时代,预处理器被认为是唯一能

提供开码(open-coded,是指直接把代码嵌入到指令流中,而不是通过

函数调用)的方式。对于每个简单常用的表达式,开码是一个很高效的

技术。比如,取小函数min可以使用宏实现:

假设你想写个工具打印一个程序中所有调用了min的函数。听上去不是

很难,是不是?但是你如果不解析这个程序就无法知道函数的边界,你

如果不做经过预处理器就无法进行解析,可是,一旦经过了预处理,所

有的min就不复存在了!所以,你的只能去用grep了。

使用预处理器实现开码还有其他问题。例如,在上面的min宏里你一定

注意到了那些多余的括号。事实上,这些括号是必不可少的,否则当

min在另一个表达式中被展开时,结果可能不是你想要的。(老实说,这

些括号不都是必需的——至于那些括号是可以省略的,这留做给读者的

min宏最险恶的问题是,虽然它用起来象是个函数调用,它并不真是函

预处理器做了替换之后,变成了:

如果'b'小于'c','b'会被增加两次而不是一次,返回的将

是'b'的原始值加一。

如果min真是函数,那么'b'将只会被增加一次,返回值将是'b'的原

C++对于C来说,就如同是肺癌对于肺

“如果说C语言给了你足够的绳子吊死自己,那么C++给的绳子除了够你上

吊之外,还够绑上你所有的邻居,并提供一艘帆船所需的绳索。”

悲哀的是,学习C++成了每个计算机科学家和严肃程序最为有利可图的投资。它

迅速成为简历中必不可少的一行。在过去的今年中,我们见过不少C++程序员,

他们能够用C++写出不错的代码,不过...

“乔治,我需要一个能打印'Hello World!'的程序”

第一次参加德福考试,作文是4分,第二次进入德福考场,作文满分,我也得以与德福考试分手。 不得不承认,作文作为一项“

”题型,相比大多数中国考生擅长的“

”题型——阅读来说,可能得分较低。 换个角度来说,德福作文由于…

我要回帖

更多关于 manythings 的文章

 

随机推荐