Eileen'blog

第三章:操作系统的诞生:从裸机到管家

2026-05-14T23:10:00 文章

你打开电脑,点开浏览器、微信、音乐播放器,三个程序同时跑着,来回切换,从没出过乱子。

你想过没有:是谁在背后管理这一切?

谁决定你的微信能用多少内存?谁保证浏览器不会把音乐播放器的数据给覆盖了?谁在感知你敲击键盘、移动鼠标?

答案是:操作系统

但在计算机诞生的头十几年,这东西根本不存在。那时的工程师想跑一个程序,得先插线、拨开关、手动加载。机器跑完一个任务,停下来,等你去换下一个。

这就像买了一辆法拉利,但每次换挡都得你下车去推轮子。

这太蠢了。

于是,1950年代中期,一批程序员开始琢磨:能不能写一个“管家程序”,常驻在机器里,自动帮人类安排任务、分配资源?

这个念头,开启了操作系统的历史。

但在讲故事之前,先记住一个关键前提。

硬件前提

操作系统的诞生依赖一个硬件跃迁:晶体管(第二代计算机)。

第一代电子管计算机(如ENIAC)动不动就烧管,运行几分钟就得检修。在这种机器上写“常驻软件”毫无意义——你还没等它启动,管子先炸了。

晶体管让计算机的可靠性大幅提升,可以连续运行数小时甚至几天不出故障。只有到了这时,让一个“管家程序”永远跑在后台,才成为可能。

记住这个前提。后面讲的每一步,都踩在晶体管铺好的路上。


一、为什么人工操作慢到让计算机大部分时间闲置?——批处理系统的诞生

先穿越回1950年代初。

你是一个“程序员”——不,那时还没有这个称呼。你更像一个电工兼操作员。

你想让计算机算一个东西。步骤是这样的:

  1. 把程序写在纸上。
  2. 把纸上的程序手工转换成机器码,打在打孔卡上。一张卡一行代码,一个程序少则几十张,多则上千张。
  3. 抱着一摞打孔卡走到机房门口,交给穿着白大褂的操作员
  4. 操作员把你的卡放进读卡机,按下启动按钮。
  5. 机器跑起来。你要等。等多久?不知道。可能半小时,可能明天。因为前面还有别人的程序在排队。
  6. 跑完了,操作员拿着一摞打印纸出来,上面是计算结果。如果算错了——你把一个分号写成了逗号——回到第1步。

想象一下这个场景: 你排了一小时的队,终于把卡递进去。然后你回到办公室,喝咖啡,看报纸,等啊等。两个小时后,操作员出来喊你的名字。你接过一摞热乎乎的打印纸,扫一眼——第四十二行少了个分号,编译失败。你叹口气,重新打卡,再排队。一个下午就这样过去了。

注意第5步的“排队”。那不是你想象中“排着队一个一个来”的平静画面。实际情况是:一个程序跑10秒钟,然后机器停下来。操作员走过去,花5分钟把上一摞卡取出来,放进下一摞,按下按钮。机器再跑10秒,再停。

算一下:一天24小时,机器只有不到3%的时间在真正计算,剩下97%在等着操作员换卡

一台计算机当时造价几百万美元(相当于今天的几千万),结果你花钱请它晒太阳。

这不合理。

为什么会出现这种情况?因为人的反应速度比机器慢几个数量级。机器跑一个任务只要几秒,但人工准备下一个任务要几分钟。怎么办?让机器自己接管下一个任务。

第一个站出来想办法的,不是学术大牛,而是搞应用的人。1950年代中期,美国通用汽车公司北美航空合作,为IBM701型计算机写了一个叫GM-NAA I/O的系统。它能做什么呢?自动批量加载任务、自动调用汇编程序、自动把结果打到磁带上。

这就是批处理系统的雏形。

它的核心思想极其简单:让监督程序自己从读卡机里取下一个任务,而不是让人去按按钮。

操作员只需要一次性把一摞卡放进去,然后去做别的事。监督程序跑完一个任务,自动调出下一个。中间没人闲着。

这就像给传送带装了个自动送料器。

GM-NAA I/O在1956年投入使用。它被公认为世界上第一个实锤的操作系统。虽然它还很简陋,但它证明了“常驻监督程序”这条路走得通。


二、为什么批处理之后CPU还是经常空转?——多道程序设计

批处理解决了“人工换卡太慢”的问题,但它带来了一个新问题:CPU还是经常闲着

为什么CPU会空转?因为程序不只是算,还要输入输出(I/O)。

比如你的程序要读一盒磁带。磁带的机械臂转起来,找到数据,再读进来——这个过程比CPU慢几百倍。在I/O完成之前,CPU只能干等。

单道批处理系统中,内存里只有一个程序。这个程序一发起I/O,CPU就进入空闲状态,直到I/O完成。而I/O完成的时候,下一道程序还躺在读卡机里呢——因为一次只能跑一道。

这就好比一个厨师,做一道菜等5分钟烤箱预热,然后下一道菜又要等另一个烤箱。烤箱在工作的时候,厨师站在旁边发呆。

那能不能让CPU不等呢?

比如,程序A在等读磁带的时候,CPU去跑程序B?

可以。但你需要两个东西:一是内存里能同时放多个程序;二是CPU能快速在程序之间切换。

这就是多道批处理系统

1960年代初,IBM在大型机System/360上实现了这一技术。它被称为OS/360——一个支持多道批处理的、真正意义上的操作系统。

工作流程是这样的:内存里同时驻留3-5道程序。程序A发起I/O,操作系统立刻把CPU切换给程序B。B跑着跑着也发起I/O,操作系统再切给C。当A的I/O完成时,操作系统收到一个中断信号,记下“A可以继续跑了”,等当前正在跑的程序让出CPU,再切回去。

这样一来,CPU几乎永远不会闲下来。利用率从不到10%飙升到60%以上。

这笔账在当时太划算了——操作系统本身会消耗一点CPU时间(用来做调度、处理中断),但换来的整体利用率提升,让计算机在固定硬件上多干了几倍的活。


三、为什么多道批处理还是不能跟计算机实时对话?——分时系统的诞生(CTSS与DTSS)

多道批处理虽然提高了CPU利用率,但有一个问题仍然没解决:没有交互

你提交一个程序,等一个小时拿到打印结果:“第42行少了一个分号”。改完再提交,再等一个小时。一个下午改了两行代码。

为什么不能像今天这样,敲完命令立刻看到结果?

要回答这个问题,必须先补上一段关键的硬件演变——否则你会奇怪:第二节里计算机还在读磁带,怎么第三节突然就能从键盘读取指令了?

先补一个硬件前提:终端和通信控制器的出现

你可能已经注意到了,刚才讲多道批处理时,计算机读的是磁带,输出的是打印纸。怎么到了分时系统,突然就能从键盘输入了?

中间差了整整一套硬件。

1960年代初,晶体管技术让计算机变得可靠且便宜到可以走出机房。大学实验室和研究机构开始探索“远程交互”的可能性。要让计算机响应远处的键盘输入,需要解决两个问题:

第一,把键盘和打印机“搬到”远处。 最早被用作终端的是电传打字机(Teletype)——一台集成了键盘和打印机的设备。用户坐在电传打字机前敲键盘,字符通过电话线传到主计算机。主机运算完的结果,再通过电话线传回来,由电传打字机上的打印机打在纸上。这就是分时系统最原始的“终端”。

第二,让计算机听懂这些信号。 主机原本只认识磁带和打孔卡(并行I/O),不认识电话线上串行传来的电信号。所以要加一个叫通信控制器(Communication Controller)的硬件。它的工作是:接收来自几十台终端的串行信号,转换为主机能识别的并行数据,再通过中断机制告诉CPU“有输入来了”。

通信控制器+电传打字机这套组合,让一台IBM 709既能读写磁带(批处理的遗产),又能同时连接几十台交互终端。这正是分时系统的硬件基础。

这套设备在1960年代极其昂贵。一台Model 33 ASR电传打字机的价格,远高于当时普通学生一年的学费。在那个没有个人电脑的年代,无数程序员的第一行代码就是在这种“咔咔作响”的设备上完成的。直到1970年代,更便宜、带屏幕的CRT终端(俗称“玻璃电传打字机”)问世,才逐渐取代了纸面打印的电传打字机。

有了这个前提,我们再来正式讲分时系统的原理。

分时系统的核心原理

分时系统的目标很简单:让多个用户同时坐在终端前,每个人都能随时输入命令,并很快看到结果。它不是为了让单个任务跑得更快,而是为了让所有人都不需要“排队等待”。

它靠两个关键技术实现:

1. 中断驱动:让CPU能“听见”你的输入

每个用户的终端通过通信控制器连接到计算机。当你敲下一个键,电信号沿电话线传到通信控制器,控制器向CPU发送一个硬件中断。操作系统立刻暂停当前正在运行的程序,保存其状态,跳转到终端驱动程序,把那个字符读入缓冲区。

当你按下回车键,操作系统识别到一条完整的命令,便会立即把对应你账号的Shell进程(命令解释器)唤醒,放入就绪队列。

2. 时间片轮转:让所有就绪进程公平分享CPU

操作系统维护一个就绪队列。每个进程每次最多运行一个极短的时间片(比如1/100秒),时间片用完就排到队尾,换下一个进程。这样,你的Shell进程通常会在几十毫秒内获得CPU,执行你的命令,然后把结果送回你的终端。

如果你的命令需要启动另一个程序(比如编译程序),那个新程序也会被放入就绪队列,与其他人的进程一起轮转。

关键点:不是“每个用户固定占用一个时间片”,而是每个活跃进程轮流获得CPU。如果你在打字思考,没有进程需要CPU,调度器就跳过你,把时间片给其他人。

从你敲下回车,到结果出现在屏幕上,整个过程通常只需要几十到几百毫秒。而人类感知“延迟”的阈值大约在200毫秒左右,所以你会觉得计算机在“瞬间”响应。

当时的真实使用场景:坐在电传打字机前

现在,让我们真正“坐进”1961年的MIT机房。

你走进一间屋子,里面摆着几十台电传打字机——看起来像古老的打字机,但是没有屏幕,只有键盘和一卷纸。你坐下,把纸张对齐,打开电源。

你面前不是Windows桌面,只有一个提示符(比如READY$)。你开始打字:

RUN HELLO

电传打字机立刻咔咔咔地打印出:

HELLO, WORLD!
READY

你不敢相信——它居然立刻回答了!不需要打孔卡,不需要等几个小时。

你又打了一个命令:

LIST FILES

机器咔咔咔打出一串文件名。你想编辑其中一个,于是输入:

EDIT MYPROG.BAS

机器进入编辑模式。你可以一行一行地修改程序。每敲完一行,机器立即回应一个行号。你甚至可以在纸上看到刚才打出的内容——这就像你同时在“写”和“运行”。

如果你犯了错,比如把PRINT打成了PRNIT,机器马上会打印出SYNTAX ERROR,你当场就能改。不需要等一个下午才知道少了个分号。

这就是分时系统带来的革命:你不再向计算机“投递”任务,而是和它“对话”。

历史实现

1961年,MITIBM 709计算机上实现了第一个大规模分时系统,叫CTSS。它同时支持30个用户,每个用户坐在电传打字机前,敲命令,打字机立刻咔咔打出结果。不需要打孔卡,不需要等半天。程序员第一次可以和计算机“对话”。

CTSS的成功很快催生了另一个更有影响力的系统:DTSS

1963年,达特茅斯学院的两位教授——约翰·凯梅尼和托马斯·库尔茨——想让全校近千名学生都能用上计算机。但他们只有一台GEGE 225主机。怎么办?他们写了DTSS,同时支持上百个终端。他们还发明了一门极其简单的编程语言,让普通学生也能学会——就是BASIC

DTSS加上BASIC,开启了“人人都能编程”的时代。分时系统的价值,从“提高机器利用率”变成了**“让更多人有机会使用计算机”**。

常见误解澄清

读到这里,你可能会产生两个疑问。我们提前回答一下:

误解1:“分时系统是不是每输入一行就执行一行?那如果我要写一个多行的程序怎么办?”

不是的。你输入的不是“程序中的一行代码”,而是一个完整的命令。比如lsdategcc hello.c。当你编写一个多行程序时(比如一个BASIC或Python脚本),你会先通过编辑器(也是通过命令启动)把整个程序写好,保存成文件,然后执行python myfile.py这样的命令来运行整个程序。并不是每打一行代码就执行那一行。

所谓的“交互”,是指你与Shell之间的命令-响应对话,而不是计算机在执行你程序中的每一行语句。

误解2:“如果分时系统强制平均分配CPU,那是不是所有人的任务都会一起变慢?比如第一个人只需要10分钟,第二个人需要20分钟,批处理下第一个人10分钟就完成了,分时下所有人要等30分钟才一起完成?”

这个推理在纯CPU密集型任务下是对的。但分时系统设计的目标场景完全不同。

在1960年代,分时系统的典型用户不是在跑需要连续计算几十分钟的科学计算(那种任务仍然交给批处理),而是在做交互式工作:编辑文本、编译小程序、调试、发邮件、运行BASIC程序。这些活动的特点是:

  • 突发性:用户花几秒钟敲命令,CPU只需几毫秒响应。绝大多数时间用户在思考、打字,CPU是空闲的。
  • I/O密集:读取磁盘、打印输出时,CPU会自动切换给其他人。

在这种场景下,CPU本来就经常空闲。分时系统只是把空闲的时间拿去服务其他人,并不会显著延长单个任务的完成时间。即使有一两个用户在跑CPU密集型任务,分时系统也会定期切走CPU去响应交互请求——这会牺牲一些CPU任务的速度,但换来了交互用户的实时响应。

批处理的代价不是“排队时间长”,而是“排队期间你完全无法与机器交互”。分时放弃的是“让CPU任务最快完成”,换取的是“所有人都能随时敲命令”。

一个经典比喻:

批处理就像食堂打饭——只有一个人能打,打完才让下一个。你排队一小时,打饭一分钟。
分时就像每个人都有一个饭碗,食堂大厨每秒钟给每个碗里舀一粒米。你随时可以喊“多加点肉”,大厨立刻给你舀。虽然你吃饱的时间比“一个人独占大厨”要长,但你不需要排队,而且可以随时提要求。


四、为什么想做一个“终极分时系统”反而失败了?——Multics的教训

MIT觉得CTSS还不够强。他们想:能不能做一个更大更强、能同时服务几百个用户、还有完善的文件系统、安全机制、动态链接……的系统?

1964年,一个超级项目启动了。它叫Multics。目标是把计算变成一种utility,就像自来水或电力一样。你打开家里的终端,就能用计算机,不用管机器在哪,不用管资源怎么分配。

为此,它引入了一堆革命性设计:

  • 分层文件系统:目录可以嵌套目录。你现在用的Windows和macOS文件夹,就是从这里来的。
  • 动态链接:程序运行时才加载需要的代码库,而不是编译时全部打包。
  • 高安全性:不同用户、不同进程之间严格隔离。一个用户的程序崩溃不会拖垮整个系统。
  • 单级存储:内存和磁盘统一管理,程序员不用手动操心数据换进换出。
  • 进程间通信(IPC):不同程序之间可以交换数据。

这些特性,今天的操作系统全都有。但在1960年代中期,它们太超前了。

项目由MIT贝尔实验室通用电气联合开发。通用电气提供硬件(GE-645大型机),MIT和贝尔实验室写软件。看起来是梦幻组合,实际执行却是一地鸡毛。

为什么Multics失败了?

第一,硬件跟不上设计。GE-645的内存分页机制造成了严重的性能抖动,系统跑起来慢得没法用。第二,复杂度爆炸。每个功能都做到极致,但功能之间相互牵扯,开发进度一拖再拖。第三,目标过于宏大。在当时的硬件条件下,想要实现一个完美无缺的通用分时系统,几乎是不可能的。

1969年,贝尔实验室退出。项目负责人后来回忆说:“Multics太学术了,太复杂了,我们看不到它变成实用产品的希望。”

Multics没有彻底死掉——通用电气后来把计算机业务卖给了霍尼韦尔,霍尼韦尔继续开发,最终在1975年发布了商业版本,跑了好些年,在一些银行、大学和军事机构服役到1990年代。

但它成了一个象征:目标太宏大,设计太复杂,最终被自己的野心压垮。

Multics的哪些东西被证明不可行? 不是它的理念(那些理念后来都被实现了),而是它的一次性做到完美的方式。在1960年代的硬件条件下,那条路走不通。

哪些被证明可行? 它的思想——分层文件系统、动态链接、安全隔离、IPC——全部被后来的操作系统继承。

Multics留下了一个极其宝贵的教训不要试图一次性解决所有问题。 复杂度是工程的头号敌人。先让一个简单的东西跑起来,再慢慢加功能。

这个教训,被一个贝尔实验室的工程师死死记住了。他叫肯·汤普森。退出Multics项目后,他闲得无聊,想玩一个自己写的游戏,但找不到一台能跑它的机器。于是他决定自己写一个操作系统——一个“小而简单”的,绝对不像Multics那么复杂的系统。

这个“顺手写的玩具”,叫UNIX。那是下一章的故事。


五、为什么技术更先进的英国Atlas没有成为主流?——平行叙事与生态的力量

在讲Multics的时候,如果只讲美国,会漏掉另一个重要的早期操作系统:英国的Atlas Supervisor

1962年,英国曼彻斯特大学造了一台叫Atlas的超级计算机。它配备了当时最先进的虚拟存储技术,它的操作系统Atlas Supervisor比CTSS更早实现了虚拟内存(把磁盘当内存用,让程序觉得拥有远超实际物理内存的空间)、单级存储(程序员不区分内存和磁盘)和资源调度(多任务优先级动态分配)。

从技术上来说,Atlas Supervisor非常先进。但为什么它没有进入主流叙事?

因为Atlas只造了一台。它是学术验证机,没有商业化,没有推广到其他厂商。它的文档发表在英国计算机学会的期刊上,被少数专家读到,但没有像CTSS和Multics那样形成多家机构参与的合作开发社区。

而MIT的系统,因为有MIT、贝尔实验室、通用电气三方参与,工程师们在项目上成长起来,然后跳到IBM、DEC、HP,把分时系统的理念带到整个工业界。

这就是生态的力量。一个技术再先进,如果只有一个孤本,它就很难改变历史。而一个技术哪怕没那么完美,只要有一批人带着它到处“传教”,它就可能成为标准。

Atlas的故事告诉我们:计算机史不是“谁技术最好谁赢”。谁参与了、谁传播了、谁形成了生态——这些有时比技术本身更重要。

所以,Atlas Supervisor技术上可行,但因为没有形成生态,最终没有成为主流。


六、分时系统的遗产:编程文化、个人电脑与历史的固定选择

编程范式的革命

分时系统带给世界的,远不止“多人同时用一台机器”。它彻底改变了编程这件事本身。

在批处理时代,编程是“提交-等待-结果”的死循环。你写一行代码,要等几个小时才知道对错。你变得异常谨慎,在纸上反复推演,确保万无一失才敢提交——那不是美德,是被逼出来的

分时系统来了之后,你坐在电传打字机前,敲一行命令,回车,立刻看到结果。错了?改,再试。你可以试十次,没有任何惩罚。

想象这个场景: 1963年,达特茅斯学院一个大一新生,从未接触过计算机。他坐到一台电传打字机前,照着手册打了一行10 PRINT "HELLO",然后打RUN。几秒钟后,打字机打出HELLO。他兴奋地改改,再运行。一个下午写出了第一个小游戏。如果没有分时系统,他需要先学会打孔卡,排队等半天,然后才知道PRINT拼错了——大概率第一周就放弃了。

编程从“一次性的精准作业”变成了“快速迭代的探索过程”。这种交互式编程催生了高级语言(尤其是BASIC)的普及、调试工具的出现、屏幕编辑器的诞生。我们今天所有的编程习惯——写两行,跑一下,再改两行——都源于分时系统创造的环境。

分时系统对单用户有用吗?

你可能会问:分时系统是为多人共享设计的,那今天的个人电脑只有一个人用,分时还有意义吗?

答案是:经典的分时系统(严格时间片轮转)在单用户下是浪费——它强制无意义的上下文切换,降低效率。但现代个人电脑操作系统继承了它的交互思想,抛弃了公平轮转的教条

现代操作系统采用优先级调度:你正在操作的前台程序获得高优先级,后台任务低优先级。键盘或鼠标中断触发时,CPU立即响应你的输入(不等时间片用完)。这叫I/O驱动调度,比固定时间片更智能。如果你只开一个程序,调度器基本不切换,开销几乎为零。

分时系统的真正遗产不是“公平轮流”,而是中断驱动的即时响应。当计算机便宜到人手一台时,分时调度本身成了多余,但它的灵魂——让计算机随时响应人的每一次敲击——永远活在了个人电脑里。

分时系统是为多人共享而生的。当计算机便宜到可以人手一台时,分时调度本身成了多余,但它的灵魂——让计算机随时响应人的每一次敲击——永远活在了个人电脑里。

哪些选择被固定了,哪些可能性被放弃了?

回到开头的问题:我们为什么需要操作系统?

  • 人工操作太慢 → 批处理:让机器自动接任务。

  • I/O让CPU闲置 → 多道程序:让CPU切换任务,别闲着。

  • 没有交互 → 分时:让程序员能实时对话。

  • 想做终极分时系统 → Multics:太复杂,失败了。

  • Multics失败 → UNIX:选择了“小即是美”。

路径不是“最优解”驱动的,而是“解决当下最痛的问题”驱动的。每个解决方案都会带来新问题,然后下一代人解决新问题。这就是操作系统诞生的真实故事——不是英雄史诗,而是一连串的“补丁叠补丁”。但正是这些补丁,让计算机从实验室里的庞然大物,变成了每个人都能交互的工具。


下一章预告: 肯·汤普森为了玩游戏,写出了UNIX。丹尼斯·里奇发明C语言,让UNIX学会“跑”到任何机器上。然后,法律、商业和理想主义把UNIX撕成两半——一半走向封闭(System V),一半走向开源(BSD)。再然后,一个芬兰大学生写了一个“玩具内核”,不小心改变了世界。下一章,我们讲UNIX时代。