1. 第一章在全书的真实位置,以及我为什么把它翻来覆去啃了三遍
操作系统这门课我在准备考试和工作复盘时前后过了三遍,每遍的抓手都不一样。第一遍是跟着王道考研的课程把“计算机系统概述”从头到尾听下来,第二遍是自己动手把概念画成流程图和对比表,第三遍是上岸之后回头做项目,才发现第一章里那些当时觉得“背一背就行”的概念,其实全都在后面章节里反复出现。很多同学会觉得这一章分值不高、内容又碎,随便翻翻就跳过去了,结果到了第二章进程管理,连“内核态”和“用户态”为什么能切换都说不清楚,后面的概念就成了空中楼阁。
计算机系统概述这一章的地位,有点像盖楼时的地基放线,你在地面上看不出多少视觉效果,但后面所有墙体的位置都由它决定。它讲的是操作系统的定义、特征、功能、运行机制、中断异常、系统调用、体系结构这些内容,几乎每一个概念都会在后续章节被再次调用。比如“并发”要在进程里用,“共享”要在同步互斥里用,“系统调用”要在文件管理和设备管理里用,“内核态与用户态”更是贯穿整个运行机制的底层逻辑。把这些概念第一次就理解正确,比后面反复返工要省太多时间。
这篇内容我打算按笔记复盘的方式来写,不会照搬讲义,而是把我自己整理时的思路、踩过的坑、以及实际做实验验证过的细节都摊开讲。适合两类人看:一类是正在准备考研、想把这一章彻底吃透的同学,另一类是已经工作、想回头把操作系统底层补齐的开发者。无论你基础如何,只要你愿意跟着一步步拆,这一章不会再只是“背了忘、忘了背”的循环。
1.1 先看清楚:这一章到底要掌握到什么程度
我把这一章的考点按照“必须理解、必须记忆、可以放过”三档做了区分,效率比一锅端要高很多。必须理解的是运行的底层逻辑,比如为什么要有特权指令、为什么用户态不能直接访问硬件、状态切换靠什么触发;必须记忆的是分类和定义,比如中断的几种类别、四大特征各自的含义、宏内核和微内核的区别;可以放过的是那些偏冷门的历史细节,比如早期某型号批处理系统的具体参数,这些考到的概率极低。
具体到题型,这一章主要以选择题形式出现,偶尔在综合题里作为背景知识出现。选择题的可怕之处在于它的选项往往只差一两个字,比如“并发”和“并行”、“中断”和“异常”、“库函数”和“系统调用”,稍不留神就选错。所以这一章的复习重点不是长篇大论,而是把易混淆的概念对齐整齐,做到看到选项就能立刻反应出差别在哪里。
我给自己定的标准是:合上书,能用大白话把“操作系统是什么、它在什么时候进入工作、靠什么手段管理硬件、应用程序怎么找它帮忙”这一整条链路讲清楚。如果这条链路中间卡壳,说明这一章还没过关,需要回头补。这个标准后来证明非常有用,因为它把零散的概念串成了一条线,记忆负担反而小了。
1.2 我的笔记整理方式:概念卡加对比表
我整理这一章没有用大段抄写,而是用了两种载体。第一种叫概念卡,每张卡只写一个概念,正面写名称,背面写“一句话定义 + 一个生活类比 + 一个反例”。比如“并发”这张卡,正面写并发,背面写“宏观上同时、微观上交替执行”,类比是“一个人一边打电话一边敲键盘,其实手和嘴在快速切换”,反例是“并行,那是真有两个人在干”。这样一张卡反复过几遍,概念就很难再混淆。
第二种载体是对比表,专门用来处理成对出现的概念。这一章里成对的概念特别多:内核态与用户态、特权指令与非特权指令、中断与异常、外中断与内中断、宏内核与微内核、系统调用与库函数。把这些放进同一张表里横向对照,差异点一目了然,比分开记忆靠谱得多。我在下面几个章节里会把关键的表直接给出来,你可以直接拿去用,也可以按自己的理解再改。
这里先给一个我踩过的坑:早期我整理笔记时喜欢把讲义原话抄下来,觉得抄一遍就记住了,实际效果很差,因为抄写的时候大脑是关闭的。后来改成“看完一段,合上书,用自己的话写三行”,记忆留存率明显提高。这个方法很笨但很有效,尤其是这一章概念密集、理解门槛不高的情况下,主动复述是性价比最高的手段。
2. 操作系统的概念、特征与功能:把所有定义拆到不能再拆
这一章的很多概念看起来是“送分题”,实际上是最容易丢分的地方,因为太熟悉反而会忽略细节。我的做法是把每个定义拆成“它解决什么问题、它怎么解决、它的边界在哪里”三层来理解。比如操作系统的定义,如果只背“管理硬件和软件资源”,那遇到“操作系统是不是一种软件”这种判断题就会犹豫。只有理解了它是系统软件、是硬件之上的第一层软件、向下管理硬件向上提供服务,才能应对各种变形问法。
另外要提醒一点,概念之间的关系比概念本身更重要。并发和共享为什么互为存在条件,虚拟和异步为什么是建立在并发基础上的,这些关联点才是选择题喜欢挖坑的地方。下面我把特征和功能逐条拆开,尽量用生活场景来对应,让抽象的东西落地。
2.1 操作系统到底是什么:从一台裸机说起
一台只有CPU、内存、硬盘、显示器、键盘的机器,如果不装任何软件,它本质上就是一堆精密但没有灵魂的电子元件。你给它通电,CPU会从固定的地址开始取指令执行,但取到什么、做什么,全看里面预置了什么代码。操作系统就是在这台裸机上铺设的第一层“管理平台”,它把硬件的复杂性封装起来,向上提供一套相对统一、友好的接口,让应用程序不用关心硬盘是哪个牌子、内存条插在哪个插槽。
从资源角度看,操作系统是一个资源管理者。CPU时间、内存空间、磁盘容量、外设使用权,这些都是有限的资源,多个程序同时想用,就必须有人来分配、调度、回收。这个角色很像一个仓库管理员,谁要借工具、借多久、怎么归还,都要登记和协调,否则就会乱套。操作系统还要负责记录每个资源的使用状态,防止冲突和死锁。
从用户角度看,操作系统又是一个服务提供者。你想读一个文件,不需要自己去操作磁盘的磁头,只要调用文件系统提供的接口;你想让程序运行,不需要手动分配内存,操作系统会帮你完成地址映射。这种“屏蔽底层细节、暴露上层接口”的定位,是操作系统最核心的价值。理解了这一点,后面所有关于系统调用、接口、内核的讨论就都有了落脚点。
有一点要区分清楚:操作系统本身是系统软件,不是硬件,也不是普通应用软件。它随机器启动而运行,运行在最高的权限级别,直接和硬件打交道。这个定位决定了它必须有特权、必须稳定,也决定了它的代码必须非常谨慎,任何一处错误都可能导致整机崩溃。
2.2 并发与共享:一对绑在一起的兄弟
并发指的是两个或多个事件在同一时间间隔内发生。注意这里是“时间间隔”,不是“同一时刻”。单核CPU上,程序其实是交替执行的,只是切换速度极快,让人感觉它们在一起跑。并行则不同,它要求同一时刻真的有多条指令在多个处理单元上执行,比如多核CPU上两个线程同时跑。所以“单核上不存在并行,但存在并发”这句话是对的,考试里经常拿它做判断题。
共享指的是系统中的资源可以被多个并发执行的进程共同使用。共享有两种方式:互斥共享和同时共享。互斥共享的资源在一段时间内只允许一个进程访问,典型例子是打印机,两个进程同时往同一台打印机发数据会乱掉,所以必须排队;同时共享的资源允许多个进程“同时”访问,比如磁盘上的文件,多个进程可以同时读,因为读操作不会破坏数据一致性。这里的“同时”在单核上依然是宏观同时、微观交替,但逻辑上互不干扰。
并发和共享的关系是互为存在条件。没有并发,就没有多个进程同时存在,共享也就无从谈起;反过来,如果资源不能共享,多个进程无法协调使用同一套硬件,并发执行也就没法实现。这个相互依赖的关系是高频考点,看到“并发和共享的关系”这类问法,直接答“互为存在条件,缺一不可”基本不会错。我当年第一次做这道题时只写了一半,漏了反向依赖,扣了分,印象特别深。
2.3 虚拟与异步:最容易背错的另外两个特征
虚拟是指把一个物理实体变成若干个逻辑对应物。听起来抽象,拆开看就两种实现方式:时分复用和空分复用。时分复用是把时间切片,比如CPU只有一个,但通过快速切换,让每个进程都以为自己独占了一颗CPU,这就是虚拟处理器;空分复用是把空间切分,比如物理内存只有8GB,但通过虚拟内存技术,每个进程都以为自己拥有完整的地址空间,这就是虚拟存储器。虚拟技术的前提依然是并发,因为没有并发就不需要这种“欺骗式”的抽象。
异步是指进程的执行不是一气呵成的,而是走走停停,以不可预知的速度向前推进。为什么不可预知?因为进程什么时候被调度、什么时候被中断、什么时候能拿到资源,取决于操作系统的调度策略和其他进程的行为。这种不确定性是并发的必然结果。理解了异步,就能明白为什么并发程序需要同步机制,为什么共享数据要加锁,因为如果不加控制,两个进程的执行顺序可能完全出乎意料,结果自然不可控。
这两个特征经常被一起考,常见问法是“虚拟和异步分别建立在什么基础上”。标准答案是:虚拟以并发为基础,异步也以并发为基础,而并发又以共享为条件。整条逻辑链是共享支撑并发,并发产生虚拟和异步。把这条链条理清楚,四个特征就不再是四个孤立的名词,而是一个有内在联系的整体。
2.4 四大管理功能与两类接口
操作系统的功能通常归纳为四个方面:处理机管理、存储器管理、文件管理、设备管理。处理机管理负责进程的创建、调度、同步、通信和死锁处理,是并发控制的核心;存储器管理负责内存分配、地址映射、内存保护和虚拟内存,解决的是“多个进程抢内存”的问题;文件管理负责文件的组织、存储、检索和权限控制,让用户能按名字方便地存取数据;设备管理负责外设的分配、驱动和缓冲,屏蔽不同硬件的差异。
除了管理功能,操作系统还提供两类接口。第一类是命令接口,分为联机命令接口和脱机命令接口。联机接口就是你打开终端一条一条敲命令,系统执行一条你敲一条;脱机接口是批处理方式,你事先把一批命令写成脚本交给系统,系统一次性执行完。第二类是程序接口,由一组系统调用组成,供应用程序在代码中调用,比如读写文件、申请内存。程序接口是应用程序和操作系统之间的正式通道,也是后面要重点讲的内容。
这里有个容易忽略的点:图形界面算不算接口?从用户使用的角度看它当然是一种交互方式,但在教材的体系里,图形界面通常被归为命令接口的一种延伸或者独立的用户界面,考试时按题目要求回答即可。我建议把重点放在命令接口和程序接口这两个大类上,图形界面的细节不必过度纠结。记住“面向用户的是命令接口,面向程序的是系统调用”,这条主线就够用了。
3. 运行机制:内核态、用户态与那条不能碰的红线
运行机制是第一章里最抽象、也最容易被低估的部分。很多同学背下来“内核态管态、用户态目态”就觉得完事了,结果一到题目里问“谁能在用户态执行”“状态切换靠什么触发”就开始犯迷糊。我的理解方式是把CPU想象成一个有权限等级的执行环境,高权限环境里什么指令都能跑,低权限环境里只能跑安全指令,两者之间的切换必须走特定通道。这个类比虽然简化,但抓住了本质。
这一节的关键是把“指令、状态、切换”三件事串起来。指令决定了哪些操作是危险的,状态决定了当前能执行哪些指令,切换则是从一个状态跳到另一个状态的机制。三者环环相扣,任何一个环节不清楚,后面的中断、系统调用就都理解不透。下面分三小节拆开讲。
3.1 为什么非得分成两种状态
如果所有程序都能直接操作硬件,后果会非常严重。一个普通程序可以随意修改内存中其他程序的数据,可以关闭中断让系统失去响应,可以直接控制磁盘导致数据损坏。为了避免这种混乱,硬件层面就设计了权限分级,把指令分成特权指令和非特权指令,把CPU运行状态分成内核态和用户态。内核态下可以执行全部指令,用户态下只能执行非特权指令,一旦用户态程序试图执行特权指令,硬件会直接拦截并触发异常。
这种设计的思路和现实中的门禁很像。大楼里普通访客只能走公共区域,只有持卡的管理员才能进机房、配电室。公共区域的设施随便用,但涉及核心设备的操作必须由专人来做。操作系统内核就是这个“管理员”,普通应用程序就是“访客”。访客要办涉及核心设备的事,不能自己动手,只能通过窗口提交申请,由管理员代为处理。
理解了这个设计动机,就能明白为什么“用户态程序不能直接访问硬件”。不是技术上做不到,而是安全机制不允许。这也解释了为什么系统调用必须切换到内核态,因为很多操作本质上是特权操作,必须在高权限环境下完成。把这个逻辑记牢,后面遇到“谁在什么态下运行”的题目就不会靠猜了。
3.2 特权指令与非特权指令怎么判
特权指令是指那些只能在内核态执行的指令,常见的有:输入输出指令、置时钟指令、关中断和开中断指令、清主存指令、修改程序状态字寄存器的指令、切换进程的指令等。判断标准其实很简单:只要这条指令能影响到整个系统的运行状态、能访问其他程序的资源、能改变硬件配置,它就是特权指令。非特权指令则是普通运算、赋值、跳转这类只影响当前程序自身状态的指令。
考试里常考的坑是把“陷入指令”当成特权指令。陷入指令又叫访管指令,它的作用是从用户态主动切到内核态,而它本身是在用户态执行的。这一点非常关键,因为它是唯一一条在用户态执行、却能引发状态切换的指令。很多同学会想当然认为“能切换状态的指令肯定是特权指令”,结果就选错了。记住:陷入指令在用户态执行,它的执行结果才是进入内核态。
还有一个容易混的点是“修改PSW的指令”。程序状态字里保存了CPU的运行状态标志,能改它的指令当然属于特权指令,否则用户程序可以把自己改成内核态,整个保护机制就废了。所以凡是涉及状态寄存器、中断控制、内存管理寄存器的操作,一律按特权指令处理。判断时抓“是否影响全局”这条主线,基本不会跑偏。
3.3 状态切换的时机,以及一个高频陷阱
从用户态切到内核态,只有三个途径:中断、异常、系统调用。更准确地说,系统调用本身是通过陷入指令触发的,而陷入属于内中断的一种,所以教材里常说“中断和异常是用户态进入内核态的唯一途径”。这句话里的“中断”是广义的,包含了外中断和异常。理解这一点,题目再怎么变形都能应对。
从内核态切回用户态,则是通过执行一条特权指令来修改程序状态字,把状态位改回用户态,然后返回用户程序继续执行。注意这个动作只能在内核态做,用户态自己做不了。整个切换过程由硬件和操作系统配合完成,用户程序感知不到细节,只知道自己发起了一个请求,然后拿到了结果。
这里有个高频陷阱:状态切换一定会引起进程切换吗?答案是不一定。中断处理完可能直接返回原进程,系统调用也可能只是短暂进内核处理完就返回,不涉及调度其他进程。只有当中断或异常导致当前进程需要让出CPU时,才会发生进程切换。这两个概念分开考的时候,很多同学会混淆,记住“状态切换是权限层面的,进程切换是执行主体层面的,两者不等价”就行。
4. 中断与异常:操作系统的神经中枢
中断和异常是操作系统从“被动等待”变成“主动响应”的关键机制。没有中断,CPU只能按顺序执行程序,外设完成了工作它也不知道,多道程序根本无从谈起。可以说,中断机制是现代操作系统能够运转的神经中枢,所有并发、调度、I/O完成通知都建立在它之上。这一节内容多、分类细,是选择题的重灾区,我把容易混淆的地方单独拎出来对比。
学这一节我建议抓住一条主线:谁触发的、在什么状态下触发的、处理完往哪返回。把这三个问题问清楚,外中断、内中断、故障、终止、陷入这几种情况就能分得明明白白。下面先讲分类,再讲处理流程,最后讲一个必考的结论。
4.1 内外中断怎么分,别被名字骗了
中断按来源分为外中断和内中断。外中断又叫中断,来自CPU外部,与当前执行的指令无关,比如时钟中断、I/O设备完成中断、键盘输入中断。它的特点是“别人通知你”,你正在干活的时候外部设备或定时器发来信号,你必须暂停手头的活去响应。外中断又可分为可屏蔽中断和不可屏蔽中断,前者可以通过关中断暂时屏蔽,后者通常是紧急事件,不能被屏蔽。
内中断又叫异常,来自CPU内部,与当前执行的指令直接相关,比如除零、地址越界、缺页、非法操作码、系统调用。它的特点是“自己出了问题或主动求助”。异常通常细分为三类:陷入是主动发起的,典型就是系统调用;故障是执行指令过程中出现的可恢复错误,比如缺页,处理完可以重新执行那条指令;终止是出现了无法恢复的严重错误,比如硬件故障、控制器出错,只能终止当前程序。
这里最大的坑是把“系统调用”归到外中断去。很多人一看到“调用”就以为是从外面进来的,实际上系统调用是通过陷入指令主动触发的,属于内中断里的陷入。判断的时候不要看名字,要看触发源:来自CPU外部的硬件信号是外中断,来自CPU内部执行指令产生的都是异常。抓住这条标准,再花哨的题目也能拆开。
4.2 中断处理流程:从关中断到中断返回
中断处理的完整流程可以拆成几个阶段。第一个阶段是中断隐指令,这部分由硬件自动完成,不需要操作系统介入,主要包括关中断、保存断点(保存程序计数器PC和程序状态字PSW)、根据中断向量找到中断服务程序的入口地址。注意“中断隐指令”不是真正的指令,而是硬件在响应中断时自动执行的一系列动作,这个名字容易让人误以为是软件指令。
第二个阶段是保存现场和屏蔽字。进入中断服务程序后,需要把当前寄存器的内容保存到栈里,同时保存中断屏蔽字,防止处理过程中被某些中断打断。然后开中断,允许更高优先级的紧急中断进来,这叫中断嵌套。接着执行具体的中断服务程序,处理完关中断,恢复现场和屏蔽字,再开中断,最后执行中断返回指令回到被中断的程序。
整个流程里“开中断”的时机是有讲究的。如果一直关中断,紧急事件无法及时响应;如果一进来就开中断,又可能在现场还没保存好的时候被打断,导致数据错乱。所以标准做法是先保存现场、再开中断,让高优先级中断可以嵌套进来。这些细节在选择题里经常以“下列顺序正确的是”的形式出现,把顺序记牢就不会丢分。
4.3 为什么说中断是用户态进入内核态的唯一入口
前面提过,用户态切到内核态只有中断、异常、系统调用三个途径,而系统调用本质上是陷入,属于异常,所以教材用一句话概括:中断和异常是用户态进入内核态的唯一途径。这句话之所以重要,是因为它把“用户程序如何获得操作系统服务”这个问题回答清楚了。用户程序自己不能执行特权操作,只能通过触发中断或异常,把控制权交给内核,由内核代劳。
理解这一点,就能明白为什么操作系统必须处理中断。如果内核不能及时响应中断,系统调用就得不到处理,I/O完成通知也收不到,整个系统会陷入僵局。中断机制就像是内核和外界沟通的电话线,没有它,内核就成了聋子和哑巴。这也是为什么“关中断”被列为特权指令,因为谁掌握了这个开关,谁就掌握了系统响应能力。
还有一个延伸结论:中断处理程序一定运行在内核态。因为响应中断需要执行特权指令,比如保存和恢复状态、访问硬件,这些在用户态做不了。所以只要是在处理中断,CPU必然处于内核态。这个结论在判断“某个程序运行在哪个态”的题目里非常有用,看到中断服务程序直接判内核态,不会错。
5. 系统调用:应用程序叩开内核大门的正确姿势
系统调用是第一章里和实际开发联系最紧密的部分。写代码的时候我们经常调用read、write、fork这些函数,但很少有人去区分哪些是系统调用、哪些是库函数包装。这一节我想把系统调用的执行链路完整讲一遍,再带大家用工具亲手验证一次,把纸面知识和真实系统对上号。
系统调用的本质是操作系统提供给应用程序的一组“标准服务窗口”。应用程序不能直接操作硬件,但可以通过这些窗口提交请求,由内核代为完成。理解系统调用的关键,是搞清楚“请求怎么发出去、内核怎么接住、结果怎么回来”这三步。下面逐层展开。
5.1 系统调用执行的完整链条
一次系统调用大致经历这样的过程:应用程序先把系统调用需要的参数准备好,比如文件描述符、缓冲区地址、读写长度,这些参数通常通过寄存器或栈传递。然后执行陷入指令,这条指令在用户态执行,作用是触发一个内中断,把CPU状态切换到内核态,同时把控制权交给内核中对应的处理程序。
内核接到请求后,会根据系统调用号找到对应的服务例程,执行真正的操作,比如从磁盘读取数据、向终端输出字符、创建新进程。执行完成后,内核把返回值放到指定位置,执行一条特权指令把状态改回用户态,返回应用程序继续执行。整个过程对应用程序来说是透明的,它只知道自己调用了一个函数,然后拿到了结果。
这里要特别注意:陷入指令是用户态执行的,但处理系统调用的服务例程运行在内核态。这条链路上有明确的权限边界,用户程序负责“发起”,内核负责“执行”,两者分工清晰。考试里问“系统调用过程中CPU状态如何变化”,标准答案是“从用户态切换到内核态,处理完再切回用户态”,中间不接受任何“一直保持用户态”的说法。
5.2 系统调用、库函数、API 三者不是一回事
很多同学把库函数和系统调用混为一谈,这是理解上的一大障碍。系统调用是操作系统内核直接提供的服务接口,运行在内核态;库函数是编程语言或第三方库提供的函数,运行在用户态,它可能内部封装了系统调用,也可能完全不涉及内核,比如纯计算的数学函数。API 则是更上层的概念,是一组接口的统称,既可以是库函数接口,也可以是系统调用接口。
举个例子,C语言里的printf是库函数,它最终会调用write这个系统调用来输出内容,但它自己还做了格式化处理,这部分是在用户态完成的。而read、write、open这些,在不同的系统里可能直接就是系统调用的封装,也可能经过一层库的包装。判断的关键不是看名字,而是看它有没有请求内核提供服务。凡是需要内核代劳的操作,底层一定有系统调用。
为什么操作系统不直接让应用程序调用系统调用,还要搞一层库函数?一方面是为了跨平台兼容,不同系统的系统调用细节不同,库函数可以屏蔽差异;另一方面是为了方便使用,库函数提供了更友好的接口和缓冲机制,性能往往更好。理解了这层关系,就不会再纠结“我调用的到底是系统调用还是库函数”这种问题了。
5.3 动手验证:用 strace 亲眼看看系统调用
纸上得来终觉浅,我强烈建议你在Linux环境下用strace命令实际观察一次系统调用。比如写一个最简单的C程序,只做一次输出,然后用strace跟踪它的执行,你会看到一长串系统调用记录,包括execve加载程序、brk分配内存、mmap映射文件、write输出内容、exit_group退出。这些都是真实的系统调用,不是书本上的抽象概念。
操作步骤很简单:先准备一个输出hello的程序并编译,然后在终端执行strace -f ./程序名,观察输出。你会看到每一行都是一次系统调用,格式是“调用名(参数) = 返回值”。如果只关心某几类调用,可以用-e trace=write,read这样的过滤选项。再加上-T可以显示每次调用耗时,加-c可以统计各类调用的次数和总时间。
实测下来,这个小实验对理解的帮助非常大。你会直观地看到程序启动过程中触发了多少次内核服务,也会发现看似简单的一句输出背后有多少系统调用在支撑。更进阶一点,可以用ltrace观察库函数调用,和strace对照着看,库函数与系统调用的层次关系就一目了然了。这种“亲手看一眼”的方式,比死记硬背概念要牢靠得多。
6. 内核体系结构:宏内核、微内核与虚拟机的取舍
操作系统内核怎么组织,是一个典型的工程权衡问题。功能全放在内核里,性能好但维护难;功能拆到用户态,稳定易扩展但切换开销大。这一节讲的就是不同体系结构的取舍逻辑,以及虚拟机这个延伸概念。这部分内容理解起来不难,但对比点多,适合用表格来整理。
我学这一节的体会是,不要孤立地记“宏内核有什么特点”,而要理解它为什么这么设计、代价是什么。任何体系结构都是在性能、可靠性、可维护性之间做平衡,没有绝对的好坏,只有适不适合具体场景。带着这个视角去看,很多细节就顺理成章了。
6.1 宏内核与微内核的正面交锋
宏内核又叫大内核,它的思路是把操作系统的绝大部分功能都放在内核里,包括进程管理、内存管理、文件系统、设备驱动、网络协议栈。优点是各模块之间可以直接函数调用,不需要频繁的状态切换,性能高、效率好。缺点是内核体积庞大、耦合度高,一处出错可能拖垮整个系统,维护和扩展都比较困难。传统的主流操作系统大多采用这种结构。
微内核走的是相反路线,它只把最核心的功能留在内核里,比如时钟管理、中断处理、原语操作、进程通信,其他功能如文件系统、设备驱动都移到用户态作为服务进程运行。优点是内核小巧、稳定可靠、易于扩展,某个服务崩溃不会影响内核。缺点是服务之间通信需要频繁的状态切换和消息传递,性能开销大。这两种结构的选择,本质上是“性能优先”还是“可靠性优先”的取舍。
对比维度可以整理成一张表来记:
| 对比项 | 宏内核 | 微内核 |
|---|---|---|
| 功能分布 | 大部分功能在内核态 | 仅核心功能在内核态 |
| 性能 | 高,调用开销小 | 较低,状态切换频繁 |
| 可靠性 | 较低,耦合度高 | 高,服务隔离 |
| 可维护性 | 较差 | 较好 |
| 典型代表 | 传统通用操作系统 | 强调可靠性的系统 |
考试里问“微内核的性能为什么低”,答案就是服务之间通过消息传递通信,涉及频繁的用户态与内核态切换,开销比内核内直接调用大得多。这个因果关系要讲清楚,不能只说“微内核慢”。
6.2 分层、模块化与外核
除了宏内核和微内核,还有几种组织结构值得一提。分层结构把操作系统分成若干层,每层只能调用相邻下层的接口,优点是结构清晰、便于调试和验证,缺点是层次划分困难、效率较低,因为一次请求可能要穿越多层。模块化结构则是把功能拆成独立模块,模块之间可以按需调用,灵活性和效率都不错,但模块间的依赖关系可能变得复杂。
外核结构的思路比较特别,它不直接提供传统意义上的抽象,而是把硬件资源的分配和具体的资源管理分开,内核只负责资源的安全分配,具体怎么用交给用户态的程序库去决定。这样做的好处是减少了抽象层的开销,灵活性更高,但编程复杂度也上去了。外核在实际系统中用得不多,但作为考点偶尔出现,了解其基本思想即可。
这几种结构不用背得太细,重点是理解它们的核心思路和典型优缺点。我的记忆方法是抓住“抽象层次”这个关键词:分层是纵向切,模块化是横向切,外核是把抽象权下放。抓住这条线索,几种结构的区别就清楚了。
6.3 虚拟机与系统引导
虚拟机的本质是用软件模拟出一台完整的计算机,让多个操作系统可以在同一套物理硬件上互不干扰地运行。按实现方式可以分为两类:第一类虚拟机管理程序直接运行在硬件上,它自己就像一个精简的操作系统,负责分配硬件资源给各个虚拟机;第二类则运行在宿主操作系统之上,依赖宿主系统来管理硬件。两种方式在性能和隔离性上各有特点,第一类通常更接近硬件、开销更小。
系统引导是另一个常考的小点。机器加电后,CPU从一个固定的地址开始执行固件中的引导程序,引导程序负责硬件自检、初始化基本设备,然后找到并加载操作系统的内核到内存中,把控制权交给内核,内核再完成初始化并启动第一个用户进程。整个过程环环相扣,任何一步失败机器都无法正常启动。理解引导过程有助于理解“操作系统是第一个在机器上运行的复杂软件”这个定位。
虚拟机这一块我在实际工作中用得比较多,体感是它对学习和测试非常有价值。你可以在虚拟机里随便折腾,装系统、改配置、模拟故障,都不影响宿主机。这种“安全沙箱”的特性,让它成为学操作系统最实用的工具之一。建议有条件的话,自己装一个虚拟机环境,把这一章提到的概念在实践中过一遍。
7. 操作系统发展历程与常见问题速查
发展历程这部分内容偏历史,考试分值不高,但梳理清楚有助于理解“为什么操作系统会演化成今天这个样子”。我的建议是不要死记年份,而是记住每个阶段的驱动因素:是什么需求推动了这种形态的出现,它解决了什么问题,又带来了什么新问题。带着问题意识去看发展脉络,记忆会轻松很多。
这一节我还整理了一份常见问题速查表,把这一章里最容易混淆的几组概念集中对比。复习后期我基本只靠这张表来回滚,效率很高。你可以把它打印出来贴在书桌前,每天扫一眼,比反复翻讲义有用。
7.1 发展脉络:从手工操作到分时实时
最早的阶段是手工操作,用户直接通过插线板或控制台操作机器,一人独占,效率极低。随后出现批处理系统,目的是提高资源利用率。单道批处理一次只允许一个程序在内存中运行,CPU经常等待I/O,利用率依然不高;多道批处理允许内存中同时驻留多道程序,当一道程序等待I/O时切换另一道执行,CPU利用率大幅提升,但用户无法交互,作业周转时间长。
为了解决交互问题,分时系统出现了。它把CPU时间切成很短的时间片,轮流分配给各个终端用户,用户感觉自己独占了一台机器。分时系统的特征包括多路性、独立性、及时性和交互性,重点在“及时响应”和“人机交互”。再往后是实时系统,它要求系统在规定时间内必须完成处理,分为硬实时和软实时,前者超时会造成严重后果,后者超时只影响体验。实时系统最看重的是可靠性和及时性,而不是吞吐量。
再往后还有网络操作系统和分布式操作系统。网络操作系统是给网络环境下的计算机提供通信和资源共享功能,分布式系统则更强调整体性和透明性,多个机器对外表现为一个系统。这两个概念容易混,记住“网络系统是互联,分布式是协同”这个区别就够了。至于个人计算机操作系统,就是我们现在最熟悉的那一类,追求易用性和丰富的应用生态。
7.2 常见疑问速查表
下面这张表是我反复整理后留下的精华版,把最容易出错的几组概念摆在一起对比:
| 概念对 | 核心区别 | 记忆抓手 |
|---|---|---|
| 并发 vs 并行 | 并发是宏观同时、微观交替;并行是同一时刻真并行 | 单核只有并发,多核才能并行 |
| 互斥共享 vs 同时共享 | 互斥共享一次一个;同时共享可多个并发访问 | 打印机 vs 磁盘文件 |
| 特权指令 vs 非特权指令 | 特权指令只在内核态执行;非特权指令可用户态执行 | 影响全局的就是特权指令 |
| 内核态 vs 用户态 | 内核态可执行全部指令;用户态只能执行非特权指令 | 权限高低不同 |
| 外中断 vs 内中断 | 外中断来自CPU外部;内中断来自指令执行内部 | 时钟中断 vs 系统调用 |
| 故障 vs 终止 | 故障可恢复,处理后重执行;终止不可恢复,只能结束 | 缺页 vs 硬件故障 |
| 系统调用 vs 库函数 | 系统调用进内核态;库函数多在用户态 | 是否请求内核服务 |
| 宏内核 vs 微内核 | 宏内核功能集中性能高;微内核功能分散可靠性高 | 性能 vs 可靠 |
这张表建议配合概念卡一起用,先看表建立整体印象,再用卡片逐个击破细节。我在考试前一周基本就是表加卡片来回滚,效果比漫无目的地翻书好太多。
还有几个零散的高频问题一并列出来。问“用户态程序能不能直接访问硬件”,答不能,必须通过系统调用。问“中断处理程序运行在哪个态”,答内核态。问“系统调用属于内中断还是外中断”,答内中断里的陷入。问“操作系统是硬件还是软件”,答系统软件。这些问题看着简单,但越是简单越容易在紧张时答错,提前把它们固化下来很有必要。
8. 复盘:我在这一章踩过的坑和最后的记忆方式
写到这里,我把这一章最核心的内容基本都过了一遍。回顾我自己的学习过程,有几个坑是反复踩的,值得单独拿出来提醒。第一个坑是把“并发”和“并行”混用,选择题里两个词经常同时出现,一旦混了就必错;第二个坑是把系统调用误判成外中断,原因是我早期只记了“中断是外部来的”这一半;第三个坑是忽略状态切换和进程切换的区别,导致后面的调度章节理解吃力。这些坑的共同点,都是没有把概念的边界划清楚。
后来我找到的比较有效的记忆方式,是把整章内容压缩成一条主线故事:操作系统的任务是什么,它用什么特征来描述自己,靠什么机制运行,怎么被打断,怎么接收请求,内核怎么组织,一路演化成什么样。沿着这条线走一遍,每个知识点都有它自己的位置,不再是孤立的名词。比如中断是“怎么被打断”这一环,系统调用是“怎么接收请求”这一环,状态切换则是贯穿这两环的底层机制。
如果让我给正在准备这一章的同学一个建议,那就是别急着刷题,先把这条主线讲给自己听一遍,讲不顺的地方就是薄弱点。讲顺之后再去做选择题,正确率会明显不一样。另外,动手实验的价值远超想象,有条件就在虚拟机里跑跑strace、看看中断信息,把纸面概念和真实系统对上号,记忆会牢得多。
关于这一章后续还能怎么扩展,我的做法是把它和第二章的进程管理连起来学。理解了状态切换,再去看进程切换和上下文切换,会非常顺;理解了系统调用,再去看进程创建和文件操作,也就水到渠成。第一章打下的这些地基,会在后面每一章里反复派上用场,所以别嫌它碎,值得多花两个晚上把它彻底盘清楚。