1. 第一章在408里的真实分量:别被"概述"两个字骗了
每年九、十月,总有一批同学私信问我同一个问题:操作系统第一章是不是可以直接跳过?他们的理由几乎一模一样——"概述嘛,翻翻就行,又不会出大题"。这个判断对了一半,也错了一半。对的部分是,第一章确实很少出综合应用题,几乎清一色是选择题;错的部分是,它的概念密度高得离谱,一个不留神两分就没了,而两分在408里是什么概念,考过的人都清楚。
我自己的复习经历是,第一遍看第一章的时候飞快,四十分钟翻完,还在笔记本上写了一行"简单"。结果做第一套真题时傻眼了:用户态到内核态的转换途径、中断和异常的区别、系统调用发生在哪个态,这几道题错得干干净净。那一刻我才意识到,第一章不是让你背概念,而是给你后面所有章节搭建认知框架。进程调度为什么要陷入内核?IO操作为什么要关中断?这些问题的答案全埋在第一章里。这一章是操作系统整个知识体系的地基,地基没打牢,后面楼盖得越高越危险。
从考点分布看,第一章的选择题集中在四块:操作系统的概念与特征、发展历程、运行机制(尤其是中断与系统调用)、体系结构与引导。分值通常在三到五分之间浮动,偶尔会跟第二章的进程状态转换结合出题。所以正确的定位是——投入产出比很高,花两三个小时吃透,换来的是稳定的几分,以及后面章节理解成本的直线下降。
1.1 为什么说它是"框架章"而不是"记忆章"
很多人把第一章当成名词解释来背,这是最大的误区。比如"并发"和"并行",如果只背"并发是宏观同时微观交替,并行是真正同时",选择题稍微换个说法就容易翻车。但如果你理解了单核CPU在同一时刻只能执行一条指令这个物理限制,那么"并发是多个程序在同一时间间隔内交替运行"就变成了自然而然的结果,根本不用死记。第一章所有的概念,背后都有硬件约束在支撑。我建议的复习方式是:每读一个概念,先问一句"为什么会是这样",把物理限制想清楚,概念就自己长在脑子里了。
再比如"共享"分为互斥共享和同时共享。互斥共享的代表是打印机,同时共享的代表是磁盘文件。为什么打印机不能同时共享?因为它是一台物理设备,同一时刻只能接收一份打印任务,这是硬件层面的串行性决定的。磁盘文件之所以能同时共享,是因为读操作可以并发,操作系统对文件读写的控制粒度更细。把这些和硬件特性挂钩,记起来就顺了,也能应付各种变形的选择题。
1.2 复习节奏与时间分配建议
我的建议是第一章至少过三遍。第一遍通读,建立整体印象,不求记住所有细节;第二遍精读,每个概念都追问为什么,把易混点单独抄到一张纸上;第三遍在复习完第二章进程管理之后回头再看,你会发现当初觉得抽象的"系统调用""中断"突然变得具体了。这三遍加起来不要超过四小时,但效果比囫囵吞枣看五遍强得多。
具体到王道这本书,第一章的框架很清晰,基本遵循"概念—发展—运行机制—结构—引导"这条线。我的做法是每看完一节立刻合上书,用自己的话把这一节复述一遍,讲不出来的地方就是没真懂的地方。这个方法看起来很笨,但对第一章这种概念密集的章节特别有效。因为选择题考的就是对概念边界的精确把握,你能复述清楚,说明你真的分清了"是什么"和"不是什么"。
提示:第一章最容易考的从来不是定义本身,而是定义之间的边界。把"并发vs并行""中断vs异常""用户态vs内核态""大内核vs微内核"这几组边界搞清楚,这一章就稳了。
2. 操作系统的定义:一句话里藏着的三个高频考点
抛开那些绕口的学术表述,操作系统的定义可以压缩成一句话:它是管理计算机硬件和软件资源的程序集合,同时为用户和应用程序提供使用这些资源的接口。这句话看起来平平无奇,但它包含了三个独立的考点模块——管理什么(资源)、怎么管(特征)、管完之后给谁用(功能与接口)。命题人就是围绕这三点反复出题。
先把"管理资源"这一层说透。计算机系统的资源大致分四类:处理机、存储器、文件和IO设备。操作系统对它们的管理分别对应处理机管理、存储器管理、文件管理、设备管理,这四大管理又常被称为"四大功能"。注意,有时候教材会把"提供用户接口"也列为功能之一,所以你会看到"四大功能"和"五大功能"两种说法。这不算矛盾,只是分类口径不同。考试时按题目给的口径来,不要纠结哪个更"标准"。
我特别想强调"接口"这个概念,因为它是后面系统调用章节的引子。操作系统不是直接让用户去操作硬件,而是通过接口做了一层封装。命令接口面向用户直接输入命令,程序接口面向程序员,也就是我们常说的系统调用。图形用户界面(GUI)其实也算命令接口的一种呈现形式。理解了这个层次关系,你就明白为什么用户程序不能直接访问硬件——不是不能,是不允许,操作系统用接口把硬件保护起来了。
2.1 四个特征里,只有两个是"地基"
操作系统有四大特征:并发、共享、虚拟、异步。初学的人容易把它们并列记忆,但实际上并发和共享才是最基本的两个特征,虚拟和异步是建立在这两个之上的。
为什么这么说?先看并发。没有并发,就没有"多个程序同时推进"这件事,那么虚拟技术里的虚拟处理器(时间片轮转)也就无从谈起,异步性(程序走走停停、以不可预知的速度前进)更是只有在并发环境下才会出现。再看共享。共享分互斥共享和同时共享,没有并发,也就没有"多个程序竞争同一资源"的问题,互斥共享自然无从谈起。所以并发和共享互为存在条件,是操作系统区别于其他软件的根本标志。
虚拟技术值得单独说一下。虚拟分两类:时分复用和空分复用。时分复用就是把时间切片分给不同用户,典型代表是虚拟处理器;空分复用是把空间分割,典型代表是虚拟存储器。为什么单核的机器上你感觉在同时开好几个软件?因为CPU切换速度极快,人眼和操作根本察觉不到,这就是时分复用的直观体现。理解了这一层,虚拟就不再是抽象名词,而是一个你能在任务管理器里看到的现象。
异步性是最容易被忽视的一个。因为程序并发执行、资源竞争、走走停停,所以每个程序的执行顺序和执行时间都是不确定的。异步性带来的直接后果是,同样的程序同样的输入,跑两次的耗时可能不同,甚至中间的执行顺序都会变。这个特征是后面讲进程同步、互斥、死锁的根本原因,第一章埋下种子,第二章才开花。
2.2 功能与接口:别把命令接口和程序接口搞混
功能这一块,四大管理(处理机、存储器、文件、设备)比较好记,真正容易混的是接口的分类。命令接口是给用户用的,又分联机命令接口(交互式,用户敲一条命令系统执行一条,比如在终端里输命令)和脱机命令接口(批处理命令,用户把一批命令写成文件一次性提交)。程序接口是给程序用的,也就是系统调用,只能通过程序代码里的调用指令来使用。
这里有个非常经典的考点:系统调用只能由程序发起,用户不能直接在命令行里"调用系统调用"。命令行里输命令走的是命令接口,底层可能触发系统调用,但用户操作的对象是命令接口而不是系统调用本身。很多选择题就是利用这个模糊点设置干扰项,比如"用户可以通过命令直接调用系统调用",这是错的。
3. 从手工操作到实时系统:每一代都在解决上一代的什么毛病
操作系统的发展史不是一段平庸的流水账,它本质上是一部"发现问题—解决问题—暴露新问题"的技术演化史。命题人喜欢考的不是年份,而是每个阶段解决了什么、又留下了什么新问题。抓住这条主线,这一节就活了。
最早的计算机没有操作系统,用户直接用机器语言或穿孔卡片操作,这段时间叫手工操作阶段。它的最大问题是用户独占全机,人机速度严重不匹配——人思考速度慢,机器速度快,机器大量时间在等人。这个矛盾催生了批处理思想:把一批作业组织好,让机器连续处理,减少人的干预。
3.1 单道批处理:内存里只放一道程序,CPU被迫等IO
单道批处理阶段,内存里只允许放一道程序,程序运行过程中如果遇到IO请求,CPU只能干等着IO完成,才能继续执行。这个等待是纯粹的浪费,因为IO速度比CPU慢好几个数量级。形象地说,就像你煮饭的时候必须站在锅边看着,米没熟之前什么都不能干,时间全耗在等待上。
这个阶段彻底解决了"人机速度不匹配"吗?没有,它只解决了人工切换作业的等待,但没解决CPU等IO的等待。所以新的矛盾出现了:CPU利用率太低。于是多道批处理应运而生。
3.2 多道批处理:资源利用率上去了,人机交互却彻底没了
多道批处理的核心思想是,内存里同时放多道程序,当一道程序因为IO而暂停时,CPU立刻切换去执行另一道程序。这样一来,CPU和IO设备可以并行工作,资源利用率大幅提升。这是操作系统发展史上的关键一步,现代操作系统的并发思想就源于此。
但它带来了新问题:用户提交作业后,必须等到一批作业全部处理完才能拿到结果,中间没有任何交互。也就是说,你写程序、提交、等几个小时、看结果,如果程序有错,再来一轮。这种"无人机交互"的特性,对于需要调试的程序员来说是灾难。而且因为作业批量执行,用户对自己作业的运行进度完全没有把控。这个矛盾直接催生了分时系统。
3.3 分时与实时:一个追求交互体验,一个追求时间确定性
分时系统的思路是把CPU时间切成很短的片,轮流分配给各个用户,用户通过终端和系统交互。因为切换极快,每个用户都感觉自己独占了一台机器。分时系统解决了人机交互问题,也做到了多用户共享主机,及时性得到了较大改善。它的核心指标是响应时间,也就是从用户提交请求到系统给出响应的时间。
实时系统则是另一条路。它不追求交互,也不追求高的资源利用率,只追求一件事:在严格规定的时间内完成处理。实时系统分硬实时和软实时,硬实时要求必须在绝对期限内完成,比如工业控制、航空航天,超时就是事故;软实时允许偶尔轻微超时,比如视频播放,偶尔掉一帧用户察觉不到。判断一个系统是不是实时系统,看的是它能否在规定时间内响应,而不是它跑得快不快。一个跑得飞快的通用系统如果无法保证响应期限,也不能叫实时系统。
把这个演化主线串起来:手工操作的问题是速度不匹配→单道批处理的问题是不够快→多道批处理的问题是不交互→分时系统解决了交互但实时性不够精确→实时系统专攻时间确定性。每一代都在填上一代的坑,同时留下新的坑。这条逻辑链一记住,发展史上的所有选择题基本都是送分题。
4. 用户态与内核态:中断、异常、系统调用的三角关系
这一节是第一章真正的硬骨头,也是选择题的重灾区。很多同学学完还是一团浆糊:中断和异常到底谁包含谁?系统调用算不算中断?用户态到内核态靠什么触发?我当年就是在这儿栽了跟头,后来用一套自洽的框架理顺了,这里完整分享给你。
先建立最根本的物理前提:CPU在任何一个时刻,只能处于两种状态之一——用户态(也叫目态)或内核态(也叫管态、核心态)。内核态可以执行所有指令,包括特权指令;用户态只能执行非特权指令。什么是特权指令?比如开关中断、设置时钟、清内存、修改程序状态字,这些都是直接影响系统全局的指令,不能让用户程序随便执行,否则一个恶意程序就能把系统搅乱。
那么用户程序想干"大事",比如读文件、申请内存,怎么办?它自己不能碰特权指令,只能求助操作系统,方式就是系统调用。系统调用发生在用户态——是用户程序主动发起的请求;但系统调用的执行会在内核态完成。这里有个极容易错的点:系统调用"发生在用户态"和"在内核态执行"并不矛盾,发起动作在用户态,处理过程在内核态。
4.1 中断和异常:从用户态进内核态的两条腿
现在说触发状态切换的途径。用户态切到内核态,只有一条路:中断(广义上包含异常)。这句话要记死,它意味着用户程序不能通过普通指令主动把自己切成内核态,只能通过触发中断或异常来"惊动"操作系统。
中断分两大类。第一类是外中断,也叫中断,由CPU外部的事件引起,比如时钟中断(时间片用完)、IO中断(设备完成了一次IO)。外中断和当前执行的指令无关,是"别人喊你"。
第二类是内中断,也叫异常,由当前执行的指令本身引起,和当前指令强相关。内中断又细分成三种:陷阱(trap)、故障(fault)、终止(abort)。陷阱是程序员有意为之的,典型就是系统调用;故障是可以被修复的错误,比如缺页,处理完可以重新执行那条指令;终止是不可恢复的严重错误,比如硬件故障、除零,只能终止程序。
很多同学记混的地方在于:系统调用到底是不是中断?准确的答案是,系统调用属于内中断(异常)里的陷阱,广义上也算"中断"的一种,因为它是用户态切换到内核态的途径之一。所以"用户态到内核态的转换途径是中断或异常"这个说法是完整的,"只有外中断才能切换状态"是错的,"系统调用不属于中断"也是错的。这几个变形都考过。
4.2 系统调用的完整过程
把系统调用的执行过程拆开看,会更清楚。第一步,用户程序把系统调用号和相关参数放进指定寄存器;第二步,执行陷入指令(trap指令,也叫访管指令),这条指令会主动引发一个内中断;第三步,CPU检测到异常,切换到内核态,跳转到对应的中断处理程序;第四步,内核根据系统调用号执行相应的服务例程;第五步,返回用户态,把结果交给用户程序。
注意第二步里的"陷入指令"本身不是特权指令,它在用户态就能执行。它做的事情就是"故意制造一个异常",把控制权交给内核。这个设计很巧妙:用户程序没有权限直接进内核,但它可以通过一个合法的指令请求内核帮忙。这就像你在小区门口按门铃,你没有钥匙进不去,但你可以按铃让里面的人来开门。
我把这几组关系整理成一张表,复习的时候扫一眼就够了:
| 概念 | 触发来源 | 与当前指令关系 | 典型例子 |
|---|---|---|---|
| 外中断 | CPU外部 | 无关 | 时钟中断、IO中断 |
| 内中断(异常) | CPU内部 | 强相关 | 系统调用、缺页、除零 |
| 陷阱(trap) | 有意触发 | 相关 | 系统调用 |
| 故障(fault) | 可修复错误 | 相关 | 缺页 |
| 终止(abort) | 不可恢复错误 | 相关 | 硬件故障 |
注意:中断处理程序一定运行在内核态,这是判断状态的一道铁律。任何需要内核介入的处理,都在内核态完成。
5. 内核到底在内什么:体系结构的取舍逻辑
学到这里,你已经知道用户态和内核态的分工了。那内核里面具体装了哪些东西?这个问题就是操作系统体系结构要回答的。很多同学背"大内核""微内核"就是记不住特点,其实是没抓住背后的取舍逻辑——所有的结构设计,本质上都是在"功能放在哪里"和"性能与可靠性的平衡"之间做选择。
先明确内核的基本功能。不管哪种结构,内核通常都会包含这几样:时钟管理、中断处理、原语(一段不可被中断的、完成特定功能的程序)。原语的典型代表是进程的创建和撤销,这些操作一旦开始就必须一次做完,中间不能被别的进程打断,否则系统状态会乱。至于进程管理、存储器管理、设备管理这些"大块头"功能放在内核还是放到用户态,就是区分大内核和微内核的关键。
5.1 大内核与微内核:拿功能和性能做交换
大内核(也叫宏内核、单内核)把操作系统的主要功能模块全部塞进内核,包括进程管理、存储管理、设备管理、文件管理等等。它的优点是各模块直接调用函数,没有额外的通信开销,性能极高。缺点也很明显:内核极其庞大,几百万行代码,任何一个模块出bug都可能导致整个系统崩溃,可靠性和可维护性差。
微内核的想法正好相反。它只把最基本的功能——时钟、中断、原语——留在内核里,其他功能全部移到用户态,作为独立的服务进程运行。这样一来内核非常小巧,稳定可靠,一个服务进程崩了不至于拖垮整个系统。但代价是什么?用户程序要使用某个服务,得经历"用户态→内核态→用户态服务进程→内核态→用户态"多次反复的状态切换,通信开销大,性能明显不如大内核。
所以这道题的逻辑链是:大内核用性能换可靠性(或者说牺牲可靠性换性能),微内核用性能换可靠性。考试如果问你"微内核为什么性能低",答案就是状态切换和消息传递开销大;问"大内核的缺点",答案就是内核庞大、可维护性和可靠性差。抓住"取舍"两个字,这组对比就再也不会混。
5.2 分层、模块化、外核:三种偏小众的结构
除了大内核和微内核,第一章还会考到分层结构、模块化结构和外核结构。分层结构把操作系统按层次组织,每层只能调用下一层的功能,调试和验证非常方便,但层层调用导致效率低,而且现实中的分层很难做到严格。模块化结构把内核划分成多个可独立替换的模块,模块间通过接口通信,效率和可维护性兼顾,但模块间的依赖性难以彻底消除。
外核是相对冷门但考点明确的结构。传统操作系统的抽象是把硬件资源抽象成进程、虚拟内存这些概念;外核则反其道而行,直接给应用程序分配未抽象的物理资源,比如给它一块磁盘扇区。应用程序自己决定怎么用这块资源,灵活性和效率高,但管理复杂。
这几种结构我的记忆方法是按"抽象程度"排序:大内核什么都抽象进内核,微内核只保留最小抽象,外核干脆不抽象。理解了每种的定位,特点就是它的自然属性。
6. 按下电源键之后:操作系统引导的四步
引导(bootstrap)这部分内容不多,但每年都有固定的选择题,属于那种"不看就没分,看了就稳拿"的考点。很多同学觉得它细碎记不住,其实只要把整个过程当成一条启动链,顺着链子走就行。
第一步,CPU加电后,从某个固定内存地址(通常在ROM里)读取第一条指令,开始执行BIOS(基本输入输出系统)。这一步是硬件层面写死的,不需要任何软件参与。第二步,BIOS进行自检,然后寻找启动设备(硬盘、U盘等),把启动设备的第一个扇区——也就是主引导记录MBR——读入内存。MBR里存着引导加载程序(也叫启动管理器)。
第三步,引导加载程序读取磁盘上的活动分区,从这个分区里找到操作系统的引导记录,把操作系统的内核程序加载到内存。第四步,内核开始初始化硬件、建立数据结构、启动第一个进程,然后交给用户使用。
这条链子最关键的记忆点是:MBR在磁盘的第一个扇区,BIOS先把MBR读进来,再由MBR里的引导程序去加载操作系统内核。考试如果问"操作系统引导的第一步是什么",答案是从ROM读取BIOS(或者说执行自举程序),不是直接加载内核。
6.1 为什么引导过程要设计成这么绕
有同学会问,为什么不直接把操作系统内核放在一个固定位置让CPU加电后自己去找,非要经过BIOS、MBR这一串?原因在于灵活性。如果CPU加电后硬编码内核位置,那么换个启动设备、装个双系统就完全没法实现。通过BIOS加引导程序这一层间接,操作系统可以放在不同的磁盘、不同的分区,甚至可以通过启动管理器让用户选择启动哪个系统。这种"多一层间接换来灵活性"的设计思想,在操作系统里到处都是,比如页表就是通过多级间接来实现虚拟地址到物理地址的映射。理解了这层共性,记忆就更牢固。
7. 虚拟机与第一章的易混概念清单
虚拟机是第一章的收尾内容,考点不多但很固定。虚拟机的本质是用软件模拟硬件,让一台物理机器上能同时运行多个"看起来独立"的操作系统。它允许一个程序运行在比它实际更高的抽象层上——这句话有点绕,翻译成人话就是:你在Windows上装个软件,就能运行一个虚拟的Linux,这个Linux以为自己独占了一台完整的机器。
管理虚拟机的软件叫虚拟机管理程序(VMM),也叫Hypervisor。它分两类。第一类VMM直接运行在硬件之上,不依赖宿主操作系统,虚拟机就在它上面跑,性能和安全性更好,常用于服务器。第二类VMM运行在宿主操作系统之上,需要先有Windows或Linux这样的宿主系统,它作为宿主上的一个程序存在,灵活性好但性能略逊。
7.1 一组容易混死的对照清单
我把第一章最容易被设坑的几组概念整理成表,考前扫一遍:
| 概念A | 概念B | 核心区别 |
|---|---|---|
| 并发 | 并行 | 并发是宏观同时微观交替,并行是真正同一时刻 |
| 中断(外) | 异常(内) | 外中断与当前指令无关,异常与当前指令强相关 |
| 用户态 | 内核态 | 内核态能执行特权指令,用户态不能 |
| 大内核 | 微内核 | 大内核性能高可靠性低,微内核反之 |
| 单道批处理 | 多道批处理 | 单道内存只有一道程序CPU等IO,多道可并发 |
| 分时系统 | 实时系统 | 分时追求交互和响应时间,实时追求时间确定性 |
| 第一类VMM | 第二类VMM | 前者直接跑在硬件上,后者跑在宿主系统上 |
7.2 我踩过的几个真实坑
第一个坑是关于"中断是用户态到内核态的唯一途径"这句话的。有一道题问"以下哪种方式可以让用户程序进入内核态",选项里混了个"执行特权指令",我第一反应是"用户程序本来就不能执行特权指令",但当时脑子一抽选错了。后来想清楚了:用户程序根本不可能执行特权指令,所以这个选项本身就是不可能的,自然也就谈不上进入内核态。能触发状态切换的只有中断和异常。
第二个坑是系统调用和库函数的关系。库函数是语言层面封装好的函数,比如C语言的printf,它在底层可能会调用系统调用,但库函数调用本身不一定是系统调用。有些库函数是纯计算,压根不进内核;有些则包装了系统调用。选择题常拿这个做文章,问"库函数调用一定导致系统调用吗",答案是不一定。
第三个坑是把"缺页"归到外中断。缺页是当前指令访问的内存页不在内存里引起的,和当前指令强相关,它是内中断里的故障(fault),处理完可以重新执行那条指令。凡是"和当前指令相关"的,都是异常,不是外中断。这个判断标准一旦建立,很多题一眼就能选出来。
第一章的内容看起来碎,但其实所有的点都能挂到"用户态与内核态的分工"这条主线上。你只要时刻问自己"这个操作发生在哪个态、由谁触发、要不要切换状态",大部分概念题都能顺出来。我复习这一章最大的体会是,别急着背结论,先把每个结论背后的"为什么"想明白,想明白之后你会发现要背的东西其实很少,真正需要的是理解边界。等你看完第二章进程管理再回头看第一章,那种"原来如此"的感觉会特别明显,那一刻你就知道这一章的功夫没白花。