/*您不需要理解这一点*/

2020-10-22 08:51:06

这篇帖子的标题,你是不会理解的,这是有史以来最著名的评论之一。它位于Unix的V6版本中,Unix是Linux、Android和iOS的母版操作系统。UNIX是由贝尔实验室的丹尼斯·里奇和肯·汤普森于20世纪70年代初开发的。当我说注释时,我使用的是编译器忽略的源代码注释的技术意义上的这个词。

Unix的V6版本特别出名,因为澳大利亚新南威尔士大学的John Lions编写了源代码的注释版本,许多计算机科学系都使用该版本来教授操作系统设计。那是下面那本橙色的书。

但AT&;T决定在Unix上赚钱,并坚称莱昂斯的书只能在付费Unix许可证的学校使用(因为它包含了源代码的副本)。这几乎没有效果,每个人都只是简单地影印,就像苏联的Samizdat出版物一样。最终,这本书是通过正常渠道出版的,你仍然可以在亚马逊上买到它。有趣的是,在右上方的封面上,学生们非法复印了原版。我研究生时代的复印件是复印件。我在写这篇文章的时候才发现封面原来是橙色的。

代码的一部分涉及所谓的上下文切换。同样的事情也发生在你的笔记本电脑上,甚至你的手机上,但既然你是唯一使用它的人,那么它就不那么明显了。回到20世纪70年代,计算机太大太贵,不能给每个人都有自己的,所以它们是共享的,这种方式被称为分时。每个用户都有键盘和屏幕,并通过一条线连接到计算机(也有拨号的方法)。诀窍在于,在每个用户看来,他们似乎拥有整台机器。当用户击键或时钟中断时,操作系统将从一个用户切换到另一个用户,等等。

我还记得我的第一堂操作系统课,当时讲师罗杰·李约瑟(Roger Needham)解释了进程的概念。一旦你理解了,这是一个极其简单而优雅的想法。我会试着解释一下。一台计算机,如早期Unix运行的PDP11,有许多通用硬件寄存器(用于计算、字处理等),以及其他一些更专用的寄存器。每个用户(实际上是每个进程,但让我们假设每个用户一次只做一件事)分配了一点足够大的内存来保存这些寄存器的副本。当一个进程完成运行时(例如,它正在等待其用户键入密钥,这大约每一个世纪在计算机时间内发生一次),那么操作系统将执行上下文切换。所有通用寄存器和特殊寄存器都将保存在该进程的特殊存储器位中。

然后,将选择下一步运行的新进程。总是有一个,因为如果没有真正的工作要做,就会有一种称为空闲进程的最后一搏进程,它总是准备好运行(当它运行时什么也不做,只是在需要运行更有用的东西之前浪费时间)。为了运行新进程,从该进程的专用内存中保存的值加载通用寄存器和专用寄存器,这些值是上次运行时保存的。当代码开始执行时,在坐在终端的用户看来,就好像在两次击键之间什么都没有发生一样,但实际上,系统继续执行其他用户想要实现的所有其他事情。

这条著名的注释出现在操作系统中的一小段代码的末尾,该代码在新进程加载到一些寄存器后将控制权交给它。在编写了我的部分代码之后,我将指出这是相当棘手的。毕竟,加载了寄存器之后,操作系统就处于某种折中状态,一些寄存器包含操作系统值,另一些包含新的进程值。所以每件事都必须非常仔细地按照正确的顺序做。以下是1975年Unix V6源代码中的代码:

/**切换到新进程的堆栈并设置*HIS分段寄存器。*/retu(rp->;p_addr);sureg();/**如果新进程因为*换出而暂停,请将堆栈级别设置为对savu(U_Ssav)的最后一次调用*。这意味着,在调用aretu*之后立即执行的return*实际上是从执行*savu的最后一个例程返回的。**您不需要理解这一点。*/if(rp->;p_flag&;SSWAP){rp->;p_flag=&;~SSWAP;aretu(U.U_ssav);}/**此处返回的值有许多微妙的含义。*见newproc评论。*/return(1);

这段代码是操作系统代码,不同于在正常程序中会发生的事情。";retu";语句用该进程的小块中保存的值覆盖堆栈上的返回地址(和其他一些寄存器)。末尾的RETURN语句将把返回地址从堆栈中取出并转到那里。在正常的程序中,返回总是返回到调用过程的地方,因为普通用户无法调整返回地址。但是有一个复杂的问题,如果进程被换出(将其所有内存写入磁盘),那么它需要返回到不同的位置,因为它不应该返回到旧进程最终放弃控制时正在运行的交换代码。相反,它需要将交换代码在执行其工作之前保存的特定值返回。不管进程是否被换出,最后的返回状态都将返回到等价的位置,重新加载其余的寄存器,用户程序将继续运行,就好像什么都没有发生一样。

有趣的是,几年后,肯·汤普森承认,很难理解正在发生的事情的原因是它是错误的。正如汤普森所说:

真正的问题是,我们也不知道发生了什么。用于进行进程交换的SAVU/RETU机制从根本上被打破,因为它依赖于切换到前一个堆栈帧,并在与保存较早状态的过程不同的过程中执行函数返回代码。这在PDP-11上有效,因为它的编译器总是使用相同的上下文保存机制;对于InterData编译器,过程返回代码根据保存的寄存器而不同。

上面这段代码的错误之处在于,假设您只需将return放在代码段的末尾,它就会执行与实际调用的过程完全相同的代码。在PDP上这是真的,但在其他系统上不是这样。所以他们修复了它,并删除了这条著名的评论。

但是,现代操作系统实在太大了,不适合在大学课程中使用。Linux有15+M行代码。正如肯·汤普森(Ken Thompson)在“狮子队”(Lions';)一书的封面上所说:

20年后,这仍然是对真实操作系统工作原理的最好阐述。

但是,Unix V6只有9000行代码,很容易理解,人们继续研究它(也许现在还在这样做),这条著名的评论流传了下来。事实上,每个智能手机操作系统都要归功于这个系统,这使得它在今天仍然具有历史重要性。以及每台超级计算机和大多数云服务器。它必须是有史以来最有影响力的9000行代码。

阅读真实的程序是学习计算机科学的一部分,应该更加强调。作为一名本科生,我们主要使用BCPL编程,这是一种剑桥本地语言,是C和C++的先行者。我们可以访问编译器的全部源代码,阅读它与我们在编译器编写课程中学到的更多理论方面一样有价值。今天,有了开放源码,很容易阅读许多实际使用的系统(Linux、Apache web服务器、Hadoop、TensorFlow等等)的源代码,但这些代码即使不是数百万行,也有数十万行。正如我在上面说过的,Linux的代码行在1500万到2000万行之间,这取决于您包含的内容。

就像没有英语教师会只教写作,而不期望学生阅读好的写作一样(尽管谁知道今天英语部门发生了什么),任何编程教师都不应该期望只教写作,而不期望学生阅读好的编程。我想你可以说,由于有了狮子队,Unix V6成为了计算机科学界的“杀死一只知更鸟”(To Kill A Mockingbird)。

如果你不理解这篇帖子…那么,我的确警告过你。这是这篇文章的标题。