计算机科学入门
1940、1950 年代的电脑,每次只能运行一个程序,程序员在 打孔纸卡 上写程序,然后拿到一个计算机房间, 交给操作员,等计算机空下来了,操作员会把程序放入,然后运行,输出结果,停机。以前计算机慢,这种手动做法可以接受,运行一个程序通常要几小时,几天甚至几周。
但计算机越来越快,很快,放程序的时间 ,比程序运行时间还长。我们需要一种方式 让计算机自动运作,于是 操作系统
诞生了。
操作系统,简称 OS ,其实也是 程序 ,但它有操作硬件的特殊权限,可以运行和管理其它程序。
操作系统一般是开机第一个启动的程序,其他所有程序都由操作系统启动。
批处理
操作系统开始于 1950 年代,那时计算机开始变得更强大更流行,第一个操作系统加强了程序加载方式,之前只能一次给一个程序,现在可以 一次多个 ,当计算机运行完一个程序,会 自动 运行下一个程序,这样就不会浪费时间,找下一个程序的纸卡,这叫 批处理
(batch processing)。
设备驱动程序
电脑变得更快更便宜,开始在出现在世界各地,特别是大学和政府办公室。
很快,人们开始分享软件,但有一个问题,在哈佛 1 号和 ENIAC 那个时代,计算都是一次性的。程序员只需要给那 一台 机器写代码,处理器,读卡器,打印机都是已知的。但随着电脑越来越普遍,计算机 配置并不总是相同的 ,比如计算机可能有相同 CPU ,但不同的打印机。
这对程序员很痛苦,不仅要担心写程序,还要担心程序怎么和不同型号打印机交互,以及计算机连着的其他设备,这些统称 外部设备
(peripherals)。
和早期的外部设备交互,是非常底层的,程序员要了解设备的硬件细节,加重问题的是,程序员很少能拿到所有型号的设备来测代码,所以一般是 阅读手册 来写代码,祈祷能正常运行。
这很糟糕,所以为了程序员写软件更容易,操作系统充当软件和硬件之间的 媒介 ,更具体地说,操作系统提供 API 来 抽象 硬件,叫设备驱动程序
(device drivers)。
程序员可以用标准化机制 和输入输出硬件(I/O)交互,比如,程序员只需调用 print(highscore)
,操作系统会处理 输到纸上的具体细节。
多任务处理
到 1950 年代尾声,电脑已经非常快了,处理器经常闲着,等待慢的机械设备(比如打印机和读卡器),程序阻塞在 I/O 上。
50年代后期,英国 曼彻斯特大学 开始研发世界上第一台超级计算机,Atlas 。
他们知道机器会超级快,所以需要一种方式来最大限度的利用它,他们的解决方案是一个程序叫 Atlas Supervisor
于1962年完成。
这个操作系统不仅像更早期的批处理系统那样,能自动加载程序,还能在单个 CPU 上同时运行几个程序,它通过调度来做到这一点。
假设 Atlas 上有一个游戏在运行,并且我们调用一个函数 print(highscore) ,它让 Atlas 打印一个叫 highscore 的变量值,让朋友知道我是最高分冠军。
print 函数运行需要一点时间,大概上千个时钟周期,但因为打印机比 CPU 慢,与其等着它完成操作,Atlas 会把程序 休眠 ,运行另一个程序。
最终,打印机会告诉 Atlas,打印已完成,Atlas 会把程序标记成可继续运行,之后在某时刻会安排给 CPU 运行,并继续 print 语句之后的下一行代码,这样, Atlas 可以在 CPU 上运行一个程序,同时另一个程序在打印数据,另一个程序读数据。
Atlas 的工程师做的还要多,配了4台纸带读取器,4台纸带打孔机,多达8个磁带驱动器,使多个程序可以同时运行,在单个 CPU 上共享时间,操作系统的这种能力叫 多任务处理
。
虚拟内存
同时运行多个程序有个问题,每个程序都会占一些内存,当切换到另一个程序时,我们不能丢失数据,解决办法是 给每个程序分配专属内存块 。
举个例子,假设计算机一共有 10000 个内存位置,程序 A 分配到内存地址 0 到 999,而程序 B 分配到内存地址 1000 到 1999,以此类推,如果一个程序请求更多内存,操作系统会决定是否同意。如果同意,分配哪些内存块。
这种灵活性很好,但带来一个奇怪的后果:程序 A 可能会分配到 非连续 的内存块 ,比如内存地址 0 到 999,以及 2000 到 2999。
这只是个简单例子,真正的程序可能会分配到内存中数十个地方,为了隐藏这种复杂性,操作系统会把内存地址进行 虚拟化
。
这叫 虚拟内存
,程序可以假定内存总是从 地址 0 开始,简单又一致,而实际物理位置被操作系统 隐藏和抽象 了。
动态内存分配
用程序 B 来举例,它被分配了内存地址 1000 到 1999,对程序 B 而言,它看到的地址是 0 到 999 ,操作系统会自动处理虚拟内存和物理内存之间的映射,如果程序 B 要地址 42,实际上是物理地址 1042。
这种内存地址的虚拟化 对程序 A 甚至更有用,在例子中,A 被分配了两块隔开的内存,程序 A 不知道这点。
以 A 的视角,它有 2000 个连续地址,当程序 A 读内存地址 999 时会刚好映射到物理内存地址 999,但如果程序 A 读下一个地址 1000,会映射到物理地址 2000。
这种机制使程序的内存大小可以灵活增减叫 动态内存分配
(dynamic memory allocation),对程序来说,内存看起来是连续的,它简化了一切,为操作系统同时运行多个程序提供了极大的灵活性。
给程序分配专用的内存范围,另一个好处是这样 隔离 起来会更好,如果一个程序出错,开始写乱七八糟的数据,它只能捣乱自己的内存,不会影响到其它程序,这叫 内存保护
(Memory Protection),防止恶意软件(如病毒)也很有用。
Atlas 既有 虚拟内存
也有 内存保护
,是 第一台 支持这些功能的计算机和操作系统!
终端
到 1970 年代,计算机足够快且便宜,大学会买电脑让学生用,计算机不仅能同时运行多个程序,还能让 多用户能同时访问 。
多个用户用 终端
(terminal)来访问计算机,终端
只是 键盘+屏幕 ,连到主计算机,终端本身没有处理能力 。
多用户
冰箱大小的计算机可能有 50 个终端,能让 50 个用户使用,这时操作系统不但要处理多个程序,还要处理多个用户,为了确保其中一个人不会占满计算机资源,开发了 分时操作系统
(time-sharing)。意思是 每个用户只能用一小部分处理器,内存等,因为电脑很快。即使拿到 1/50 的资源也足以完成许多任务。
早期分时操作系统中,最有影响力的是 Multics
(多任务信息与计算系统),于 1969 年发布。
Multics 是第一个,从设计时就考虑到 安全 的操作系统,开发人员不希望恶意用户访问不该访问的数据,比如学生假装成教授,访问期末考试的文件。这导致 Multics 的复杂度超过当时的平均水准,操作系统会占大约 1 Mb 内存,在当时占了总内存的一半。
Unix
阻碍 Multics 获得商业成功的一个明显问题是,从某种方面来说,它被过度设计了,功能太多了。——Dennis Ritchie,Multics 的研究人员之一。
因此 Dennis 和另一个 Multics 研究员 Ken Thompson 联手打造新的操作系统,叫 Unix 。
他们想把操作系统分成两部分,首先是操作系统的核心功能,如 内存管理 ,多任务和输入/输出处理这叫 内核
,第二部分是一堆有用的工具,但它们不是内核的一部分(比如程序和运行库),紧凑的内核意味着 功能没有那么全面 。
内存恐慌
Unix 不会有 错误恢复代码 ,如果有错误发生,就让内核 "恐慌" (panic),当调用它时,机器会崩溃,这时需要重启电脑,这就是 内核恐慌
(kernel panic)的来源。
内核如果崩溃,没有办法恢复,所以调用一个叫 "恐慌"(panic)的函数,起初只是打印 "恐慌" 一词,然后无限循环,这种简单性意味着 Unix 可以在更便宜更多的硬件上运行,使 Unix 在 Dennis 和 Ken 工作的贝尔实验室大受欢迎。
越来越多开发人员用 Unix 写程序和运行程序,工具数量日益增长,1971 年发布后不久,就有人写了不同编程语言的编译器甚至文字处理器,使得 Unix 迅速成为 1970~80年代最流行的操作系统之一。
个人电脑
到 1980 年代早期,计算机的价格 降到普通人买得起这些叫 个人电脑
或 家庭电脑
,这些电脑比大型主机简单得多,主机一般在大学,公司和政府,因此操作系统也得简单。
MS-DOS
微软的磁盘操作系统( MS-DOS )只有 160 kB 一张磁盘就可以容纳,于 1981 年发布,成为早期家用电脑最受欢迎的操作系统,虽然缺少 多任务
和 保护内存
这样功能,意味着程序经常使系统崩溃,虽然很讨厌但还可以接受,因为用户可以 重启 。
哪怕是微软 1985 年发布的早期 Windows 虽然在 90 年代很流行,但却缺乏 内存保护
,当程序行为不当时,就会 蓝屏 ,代表程序崩溃的非常严重,把系统也带崩溃了。
现代操作系统
幸运的是,新版Windows有更好的保护,不会经常崩溃,如今的计算机有现代操作系统,比如 Mac OS X,Windows 10,Linux,iOS和Android,虽然大部分设备只有一个人使用,操作系统依然有 多任务
, 虚拟内存
, 内存保护
,因此可以同时运行多个程序。