彼得·本特利 计算机:一部历史

图灵机

计算机的思想雏形应当追溯至阿兰图灵和他的图灵机。图灵机指的是这样一种机器,能够看懂任何数学命题,并且判定其推理正误。因为数学命题可能用不同的符号#陈述,可能使用不同的公理系统。这台机器也应当能够学会任何规则。图灵机构想同当时的思想环境密不可分:人们想给数学大厦封顶,自此以后数学能够严密证明一切数学命题。大卫·希尔伯特提出的“判定”问题就与此密切相关:存不存在一台机器能够自动判定一切数学命题的真假。图灵仔细研究这个问题,提出了图灵机这一理论构想。它能够依照一定规则计算,并且随时能够学习新的规则。图灵机的构想不是为了证明“判定”问题而是为了证否“判定”问题,设若有一个图灵机在运行,怎么判定它会停机还是会死循环呢。图灵认为这必然要靠另一架图灵机来判定。这另一架图灵机检测到前一台机器停机则自身并不停机,检测到前一台机器死循环则停机并输出“死循环”。这后一台图灵机也是在运作着的,当它反观自身时,就会出现悖论。要么它自己会停机,但此时它应当不停机;要么它永不停机,据规则它应当停机。这是一个机器版的罗素悖论。总之,存在那么些问题,图灵机是解决不了的。这一事实产生了“可计算性”这个概念。一切图灵机可计算的命题都是可计算的

  • 我很搞不懂这个前提。凭什么另一台图灵机检测到前一台停机时它自己非要继续运作下去?它也可以停机并且输出“停机”啊。这样的话当此图灵机反观自身时,要么它会停机,自己也输出停机。要么它自己会死循环,自己应当停机,但这样就有故障了。这似乎有点半吊子的感觉。
  • 有一种$\lambda$语言,在数学上拿来表述算法,也成功地证否了“判定”问题。

可计算性这个概念还衍生出了$P=NP$问题。世界上有些问题固然是可计算的,可是算出来都要等到宇宙终结,这种可计算实在没有什么意义。我们用时空复杂度来衡量计算出一个问题的时跟空间成本。P指的的polynomial,可在多项式时间$O(n^x)$内解决的问题;NP即non-polynomial则可在多项式时间内检验解是否正确却难以在多项式时间内求解的问题。$P=NP$就是问理论上$NP$问题是否都有多项式时间内可求解的算法。

  • 有种可能的解决这个问题的方向是处理布尔可满足性问题。
  • 本书在此叉出一节讨论了柯氏复杂性(柯尔莫哥洛夫)和压缩文件的关系。

图灵机迈入现实

图灵机是个很漂亮的想法,但是离今天威力无穷的计算机差的还远。计算机真正落地要归功于冯诺依曼和香农。香农告诉世人电路可以由布尔逻辑式表达,而一切数学问题又可以划归为逻辑问题(这是数理逻辑学家罗素等人的功劳),因此可计算数学命题的图灵机才有了真正落地的可能。冯诺依曼1945年给出的初稿中谈到计算机必然包含三个组分:

  1. 中央处理器
  2. 中央控制器
  3. 存储器

再加上输入部件和输出部件,算成五大组分也是可以的。冯诺伊曼机器也是今后使用计算机的鼻祖原型。

指挥计算机

在计算机的肇始,“教”给计算机新的规则要靠插入不同的线路对计算机进行重新装配。威尔克斯在剑桥大学领衔研制的电子延迟存储自动计算机(Electronic Delay Storage Automatic Calculator,简称EDSAC)于1949年5月6日投入运行,它是在软件层面学习新的规则。这比在硬件层面改变计算机的规则要简单多了,但还是很难,因为计算机处理的最原始的信息是二进制码,无论是编写还是改进二进制码都是很费脑子也很不直观的。最初的人们想到的改进手段是微代码。也就是把一些常用的操作组合打包在硬件本身中,人们编写程序时只消把这一操作组合当成一个整体来看待就行了。引用书后的一条注释:

例如烹饪西红柿鸡蛋的步骤包括:购买西红柿和鸡蛋,清洗食材,打鸡蛋,炒鸡蛋,放西红柿继续炒,放盐,炒熟之后盛盘上桌。这样已连续的步骤被集成在CPU内部。外部程序需要烹饪西红柿鸡蛋的时候只需要向CPU下达指令说:“烹饪西红柿鸡蛋”就可以了。

微代码的思想就是把常用操作打包的思想,换个通行的词就叫 “模块化”。其后人们又想到了建立函数库,把常用的一些操作继续在软件层面打包进函数库里。(想一想print、abs之类的函数)这些给汇编语言蓄足了势——如果人们已经能把长长的机器码化约为几个简单的操作,把这几个简单的操作转译为文字语言也并非难事。这就是汇编代码。然而汇编代码仍然没有完全和计算机底层硬件脱离(似乎在汇编代码里设定一段程序的地址必须要靠手动,和具体硬件联系太紧密使汇编代码和机器码不能够跨机器运行),由格伦尼发明的Autocode是世界上最早出现的编译型高级编程语言,让程序员能在更抽象的层次思考(一些地方说编译是高级语言到汇编语言到机器码这整个过程;另一些地方说高级语言到汇编语言才叫编译,汇编语言到机器码那叫汇编。起码在中文世界里这个概念混来混去,难以查考,于此列出以供参考)。编程语言还有更精细的发展:

早期语言基本上都是过程式语言(程序员告诉计算机如何执行过程步骤),后来的语言则采用了不同的编程范型。在面向对象的语言〔20〕(Object-Oriented Language)中,数据及其操控方法都封装〔21〕在“对象”中,以实现代码的模块化,防止数据的意外损坏(这一点也是程序的“副作用”)。函数式编程语言利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是像过程语言一样,设计一个复杂的执行过程。

  • 还存在特化于数据库查询的编程语言,如SQL。

计算机网络

计算机网络首先依赖于于香农的工作。香农为信息论奠基,阐述了信道、信息纠错、信息率等等内容,使人们能够把一切声光电信号转化为数字01信号进行传输。计算机网络的前身是阿帕网。英国国家物理实验室也建立起一个内网,机缘巧合下柯尔斯坦主持了将阿帕网同其相连的工作。尽管有重重困难,最后柯尔斯坦凭借自己在英国邮局的关系,借用邮局掌管的电话线路办成了这件事。

显然,计算机网络在开头是将几帮人弄出的不同网络系统连起来形成的。这些实现方案,传输方案可能完全不同的网络系统如何沟通联络是个必须面对的问题。罗伯特·卡恩和文特·瑟夫发布了TCP/IP协议,以此为标准不同的计算机在联络过程中出错的可能性要低得多。TCP/IP协议尽可能地包容不同的网络协议,具有强大包容性的它在多种因素促进下最终成为互联网的标准协议。此时类似于我们今天所谈的互联网的东西才最终诞生。

计算机网络要发展,必须要给内部的各个计算机编个代号。不然的话怎么办到点对点精确传输信息呢。阿帕网等大多数网络采取两套方案,一套是方便易记的字母地址域名,另一套是给计算机处理的精确数字地址IP。浏览器得到域名后通过叫域名解析的过程向域名服务器(DNS)查询得知域名对应的IP地址:首先计算机查询本地DNS,本地DNS查不到就去根服务器查,从根服务器自顶级域服务器到各次级域名服务器逐步查询直至查询成功。查到后计算机通过本地DNS发出建立TCP连接的请求。

  • 根服务器主要用来管理互联网的主目录,全世界只有13台。1个为主根服务器,放置在美国。其余12个均为辅根服务器,其中9个放置在美国,欧洲2个,位于英国和瑞典,亚洲1个,位于日本。所有根服务器均由美国政府授权的互联网域名与号码分配机构ICANN统一管理,负责全球互联网域名根服务器、域名体系和IP地址等的管理。

以上所谈的是互联网,计算机网络信息传输所赖的种种基础设施。而我们谈的万维网指的是所有网页联系而成的那个总体。万维网产生自超文本这一概念。超文本把数据库中的零散信息链接在一个页面中,这一页面中又有某些通道通向数据库中的其它页面。这个页面的聚合就是Web的雏形。Web上的每个文件都有自己的统一定位标识符(URL),URL亦可以看成更细致的互联网编码系统。程序员想处理整个网页时也有伯纳斯·李发明的HTML语言可用。HTTP协议则解决了万维网的传输标准问题(万维网建立在互联网之上,HTTP协议亦建立在TCP/IP协议之上)。

  • 网络信息传输要讲究信息安全。此事同各类密钥加密算法有关。

深入理解计算机系统 第一章

计算机系统漫游

一个hello程序最初作为一个源程序(亦即源文件)被创建,所谓源文件就是说被编辑器创建出来的文本文件。源程序就其本质来说是一大串0或1组成的位的序列,八位成一组被称为字节。这些字节同我们日常使用的符号有个对应关系,这对应关系是由ASCII标准规定的(字节是八位而成的,导致了有256个ASCII码,因为$2^8$=256)。
源程序的本质就是位序列,不仅如此,计算机系统中的所有消息都是用位表示的。

  • 编程语言C语言与Unix操作系统关系密切,小而简单,又易于实操。它的不足在于指针容易造成混乱和程序错误,它对抽象的显式支持也有所不足。

源文件就等于ASCII标准下的一大串位,这一堆位表示的其实还是高级语言C语言,计算机机器是读不懂的。它还得经历一个继续转变的过程。
编译过程

  1. 预处理阶段:根据以字符#开头的命令修改程序,hello.c变为hello.i,例#include,读取库中文件直接插入程序文本。
  2. 编译阶段:编译器将hello.i变为hello.s。hello.s即是一个汇编语言程序。所谓汇编语言程序,是一种一条语句对应一个低级机器语言指令的语言,为不同高级语言的不同编译器提供了共同语言。
  3. 汇编阶段:汇编器将hello.s翻译成机器语言指令,这些机器语言指令被打包为可重定位目标程序的格式,放在hello.o之中。
  4. 链接阶段:将函数代表的代码从标准库中链接到源程序来。最终的结果是hello文件,一个可执行目标文件。
    • 书中介绍了一点GNU项目,它包含GCC。GNU项目似乎是一个牛逼轰轰的项目,囊括万象,闪耀着开源精神的光辉。
    • shell是一个命令行解释器,输出一个提示符,等待一个命令行(对其进行解释后运行)。如果命令行的第一个单词不是内置命令,那么这个单词就会被假设为一个可执行文件。

系统的硬件组成

计算机系统组成

  1. 一组贯穿整个系统的电子管道被称为总线,携带并传递信息。消息往往被组织成定长字节块的形式,即。字包含的字节数是一个基本的系统参数,根据这一个参数我们区分32位(4字节)系统和64位(8字节)系统。
  2. I/O设备即是输入/输出设备。它包含键盘、鼠标、显示器和磁盘。每一个I/O设备都通过控制器或适配器与I/O总线相连。控制器是置于I/O设备本身的或者系统的主印制电路板(主板)上的芯片组,适配器则是一块插在主板插槽上的区别所在即封装方式。
  3. 主存是一个临时存储设备,在处理器执行程序时用来存放程序和程序需要的数据。物理地说,主存是一组动态随机存取存储器(DRAM)芯片组成的。本质上来说,存储器是一个线性的字节数组,每个字节都有一个唯一地址(数组索引)。
  4. 中央处理单元(CPU)是执行在主存中指令的引擎。处理器的核心是一些寄存器,寄存器能存储一个字长,它们被称为程序计数器。程序计数器指向主存中的某条机器语言指令,处理器再处理,之后再更新程序计数器的指向。这些简单操作涉及到主存、寄存器文件和算术单元(ALU),寄存器文件由许多寄存器组成;ALU计算新数据和新地址。
    • 加载:把一个字节或字从内存复制到寄存器并覆盖旧内容。
    • 存储:同上,不过是从寄存器到内存。
    • 操作:将两个寄存器的内容转到ALU并计算,结果被存放到另一个寄存器并覆盖旧内容。
    • 跳转:从指令中抽取一个字,并将其复制到程序计数器中,覆盖旧内容。

另外,处理器按照一个模型进行操作,这个模型由指令集结构决定。文中提到“处理器看上去只是它的指令集结构的简单实现”,并且需要区分“指令集结构”和“微体系结构”。看不懂,留待学习第三章时处理。


讲完计算机系统的硬件构成和操作后,我们回到hello程序的生命中来。
每个命令被输入时,字符被逐一读入寄存器,再被放入存储器。回车键的敲下标志着命令输入完成,shell会执行系列指令来加载可执行hello文件,再将其复制到内存。此时处理器开始执行hello程序,这些指令将最终输出的字符串的字节从主存复制到寄存器,再从寄存器复制到显示设备。
信息一直在往复传递着,压低信息传递的代价能让计算机跑得更快。不过首先我们得了解下计算机存储的基本原则,越大越慢。从寄存器取东西比从内存快得多,从内存取又比从硬盘取又快得许多,然而硬盘,内存却不可或缺,因为它们能放更多内容。为了弥合这种差距,我们采用高速缓存存储区来作为暂时的储存区,用于存放处理器可能用到的信息。它用静态随机访问处存储器来实现。
这体现出一种工程思想,在处理器和主存间插入更小却更快的存储设备可以加快计算机的运行速度。正因如此,计算机的存储设备被组织成了以恶存储器层次结构。

操作系统管理硬件

程序并不直接访问硬件,程序直接访问 操作系统,由操作系统再来访问硬件。操作系统起两个作用。

  • 防止硬件被应用程序滥用。
  • 使应用程序拥有简单一致的机制,来控制硬件。
    操作系统的抽象表示

计算机靠几个抽象概念(进程、虚拟存储器和文件),在操作系统中复现出硬件环境,从而实现两个作用。

  • 进程:对一个正在运行的程序的抽象。一个系统运行多个进程,每个进程似乎又在独立地使用硬件,然而传统系统在一个时刻是只能执行一个程序的。为了完成这种并发运行,CPU在多个进程中并发地执行,执行这种交错执行的机制叫做上下文切换
    操作系统跟踪进程运行的所有状态消息被称为上下文。操作系统要把控制权从当前进程转移到某个新进程时要进行上下文切换
    进程的上下文切换
    进程可以由多个称为线程的执行单元。每个线程共享进程同样的代码和全局数据,更加高校。

  • 虚拟存储器:通过虚拟存储器,每个进程“似乎”能够独占地使用主存。每个进程使用一致的存储器,被称为虚拟地址空间。说它是一致,是因为对每个进程来说都能用一个独立存储器副本,这个副本与其它副本完全一样。
    看不懂这段,留待后来

  • 文件:文件就是字节序列。I/O设备,甚至网络都可视为文件。系统中所有输入输出都通过Unix I/O的系统函数调用读写文件实现。通过文件这个概念,应用程序将一切I/O设备等量齐观。

网络通信

网络将系统与系统连接到一起。系统从主存把字节串复制到网络适配器,然后数据流便可流经网络到达另一台机器。
网络也是I/O设备
通过网络远程运行程序
上图所示表达了telnet是如何基于网络复制信息的功能完成功能的。