📖 名词溯源:Operating 的「运作」 Operating 来自 operate(操作、运转)。操作系统(Operating System)字面就是「让机器运转起来的系统」——它负责让 CPU、内存、硬盘、外设这些零件协调「运转」起来。
🎯 本章学习目标
- 理解操作系统的两大核心职责:资源管理 + 抽象。
- 理解进程与线程的区别。
- 理解内存管理(尤其是虚拟内存)的基本思想。
- 理解「中断」如何让 CPU 响应外部事件,以及内核态/用户态的隔离。
12.1操作系统:计算机的大管家
裸机(没有操作系统的计算机)是「能用但极难用」的:程序员得自己操作每一个硬件,手动安排每一条指令、每一次内存读写、每一个磁盘动作。任何一个程序出错,都能拖垮整台机器。
操作系统(Operating System,简称 OS,如 Windows、macOS、Linux、Android、iOS)就是来解决这些问题的。它常驻在计算机里,是所有其他软件的地基和管家。
🧠 一句话记忆 OS = 硬件与应用之间的大管家,让一切协调运转。
把「极难用」拆开看:裸机的三个具体痛点
说裸机「难用」太抽象,拆成三个场景就清楚了。其一,没有排队规则:CPU 上电后从固定地址开始取指令(第 11 章的 PC),同一时刻只能跑一个程序;想换一个程序,就得停下机器、把新程序手工装进内存、再从头启动——「切换任务」以分钟计。其二,硬件细节全靠人肉:读硬盘一个扇区要发好几条 I/O 指令、等寻道、等旋转、校验、搬运(第 10 章的知识),每换一种硬盘还得重学一套命令。其三,没有兜底:任何程序都有权写任何内存地址,一个野指针(写错了的地址)就能把别的程序、甚至正在运行的系统代码毁掉,后果是整机崩溃,且无法追责——因为「谁都可以碰一切」。操作系统正是逐一回应这三点的总指挥:它定了排队规则(调度)、把硬件封装成统一接口(抽象)、给每个程序划了带围栏的地盘(隔离)。
那么「大管家」自己是怎么上台的?CPU 上电后从固定地址取第一条指令,那里住着一小段固化在主板上的软件(固件);它负责把磁盘上的 OS 内核搬进内存,随后「交接」——内核初始化中断表、内存管理、设备驱动,正式接管 CPU。此后所有程序的加载、运行、退出,都由 OS 安排。换句话说,OS 不是与生俱来的,而是被一段极小的引导程序一步步请上台的;「开机」这两个字的真实含义,就是把大管家从硬盘请进内存。
📐 例题 1 为什么需要 OS:裸机串行独占与多任务的时间账
某裸机设备上依次运行三个程序:A 需要独占运行 10 分钟,B 需 8 分钟,C 需 5 分钟。每换一次场(卸载上一个程序、装载下一个程序)合计耗时 4 分钟,换场只发生在 A→B 与 B→C 之间,共 2 次。(1) 裸机串行方式下三个程序全部跑完共需多久?(2) C 从「想运行」(t = 0)到「跑完」共等了多久?(3) 若在带多任务 OS 的机器上,三个程序同时启动、平均分享 CPU,粗略估算 C 的完成时间量级。
思路 裸机是「一个程序跑完再换下一个」:总时间 = 各程序运行时间之和 + 换场开销之和。C 的等待 = 排在自己前面的全部时间。多任务下三个程序同时以约 1/3 的速度推进,完成时间约为「原耗时 ÷ 分得的比例」。
解 (1) 总时间 = 10 + 4 + 8 + 4 + 5 = 31 分钟(程序时间合计 23 分钟 + 换场 2 × 4 = 8 分钟)。(2) C 从 t = 0 等到上场,共 10 + 4 + 8 + 4 = 26 分钟,再跑 5 分钟,合计 31 分钟(C 排最后,感知时间恰等于总时间)。(3) 多任务下 C 分得约 1/3 的 CPU,完成时间 ≈ 5 ÷ (1/3) = 15 分钟——不用排队等人,但要忍受「被分走 CPU」的减速。
答:(1) 31 分钟;(2) 31 分钟;(3) 约 15 分钟。多任务没有让总工作量变少,而是把「等待」改造成「减速」——人人立刻开工,这是 OS 带来的体验革命。 ⚠ 易错点:① 换场开销共 2 次 × 4 分钟,不是 3 次(程序之间的「间隙」才叫换场,C 跑完后不再换场);② 估算前先把口径写进假设——「换一次场 4 分钟」这种约定如果含糊,数字就全错了,这是工程估算的第一纪律。
工程视角:安全关键设备(航电、发动机控制、工业控制器)里同样有大管家,但它们的 OS 是另一类物种——实时操作系统(RTOS,Real-Time OS):牺牲「平均公平」,换取「确定性」——每项任务必须在规定期限内完成,晚到即故障。桌面 OS 说「平均 5 毫秒内响应」就很好;实时系统说的是「最坏 5 毫秒内必须响应」,这是两种完全不同的承诺。这也是飞行控制计算机这类设备不装桌面操作系统的根本原因:它要的不是好用,而是每次都准时。
辨析:把软件分层摆正——最底层的固件焊在硬件里负责把世界点亮;其上的OS 内核管理资源、提供系统调用;驱动程序是内核请来的「方言翻译」,替内核操持每一类具体硬件;系统服务与库在用户态提供常用功能;最上层才是应用程序。应用出错通常只崩自己,内核出错则整机崩——出错半径随层次降低而增大,这也解释了为什么内核代码要经受比应用代码严苛得多的评审。
顺带把两个高频词钉死:并行(parallel)是多核 CPU 真的在同一时刻各跑各的任务——物理上的「同时」;并发(concurrent)是单核靠极快切换制造出来的「看似同时」。四车道公路上四辆车齐头并进是并行,单车道路口红绿灯轮放四个方向的车流是并发。口语里常混着用,但读系统文档要能分清:「支持 1024 个并发连接」说的不是服务器有 1024 个核,而是它有本事把 1024 件事轮流照应好。
12.2两大职责:资源管理 + 抽象
操作系统千头万绪,但抓本质,就两件事:
① 资源管理
计算机的「资源」是有限的:CPU 只有一个(或几个核心)、内存有限、硬盘有限、外设各只有一套。OS 负责公平、高效、安全地分配这些资源,让多个程序能同时跑,谁也不独占、谁也不能乱碰别人的数据。
② 抽象
OS 把复杂的硬件「包装」成简单统一的接口。程序员不用管「文件散落在硬盘的哪个磁道」,只需用「打开文件、读文件、写文件」这样的统一命令;不用管「打印机的具体型号」,只需用「打印」这个接口。
核心思想 抽象 = 隐藏底层复杂性,提供简单接口。这是贯穿整个软件世界的思想:每一层都对上一层「藏起」自己的麻烦。OS 把硬件抽象成「文件」「进程」等概念,程序员只需面对这些概念。
🧠 一句话记忆 OS 两件事:分资源(管理)、藏细节(抽象)。
资源管理的三个目标,天生打架
「公平、高效、安全」说起来并列,实际互相牵制。公平要求雨露均沾——每个程序都该有机会跑;高效要求把资源交给最会用的人——正在等 I/O 的程序就不该占着 CPU;安全要求宁可牺牲效率也要隔离——就算两个程序亲如兄弟,也不能共用一片内存。于是每一种资源分配策略都是三者的折中:CPU 调度若把时间片切得太碎,人人响应都快(公平、体验好),但切换开销吃掉吞吐(高效受伤);若切得太粗,吞吐上去了(高效),交互程序的响应却卡了(公平受伤)。OS 的设计里没有「全对」的选项,只有「面向场景」的权衡——桌面系统偏响应公平,服务器偏吞吐高效,实时系统偏确定性安全。
「抽象」这一半也常被误解成「简单」。抽象藏起复杂性,但从不消灭它:文件抽象让你不必知道磁头与扇区,可「顺序读快、随机读慢」「写之前要按块擦除」这些物理脾气仍然存在,只是改头换面,以「性能曲线」「优化建议」的面目从抽象层底下透出来。好工程师使用抽象时有两条纪律:平时住在抽象层里提高效率;性能出问题时有能力穿透抽象,回到物理层找原因。
📐 例题 2 时间片大小的切换开销权衡
单核系统上,调度器按时间片轮转(round-robin)调度:每个进程轮流获得一个固定长度的时间片,用完就让给下一个。设每次进程切换的固定开销为 0.1 毫秒(保存现场 + 恢复现场 + 调度决策)。(1) 时间片取 10 毫秒时,CPU 时间有多大比例被切换开销吃掉?(2) 时间片取 1 毫秒呢?(3) 由此说明时间片为什么不能一味调小。
思路 开销比例 = 切换次数 × 单次开销 ÷ 总时间。轮转下每个时间片结束触发一次切换,1 秒内的切换次数 ≈ 1000 毫秒 ÷ 时间片长度。
解 (1) 时间片 10 毫秒:1 秒内切换约 1000 ÷ 10 = 100 次,开销 100 × 0.1 = 10 毫秒,占 CPU 时间的 10 ÷ 1000 = 1%。(2) 时间片 1 毫秒:切换约 1000 次,开销 1000 × 0.1 = 100 毫秒,占 10%。(3) 时间片越小,每个程序被轮到的间隔越短、交互响应越快;但开销比例 ≈ 单次开销 ÷ 时间片(0.1/10 = 1%,0.1/1 = 10%),片长 1 毫秒时 CPU 已有十分之一在「换班」而非干活。时间片要在「响应快」与「浪费少」之间折中,典型值取几毫秒到几十毫秒。
答:(1) 1%;(2) 10%;(3) 开销比例与时间片长度成反比,响应收益却逐渐饱和,故不能一味调小。 ⚠ 易错点:① 切换次数与时间片长度成反比,容易误当成正比;② 「开销比例 = 单次开销 ÷ 时间片」是理想模型的速算式;③ 真实系统里存在「时间片未用完就阻塞让出」「进程数不足轮空」等情况,本题是简化模型,结论方向不变。
工程视角:这道题的权衡在真实系统里反复出现——服务器为吞吐把时间片调大、减少切换;桌面为交互体验把时间片调小;延迟敏感场景(游戏引擎、高频交易)则干脆「穿透抽象」绕开调度器:绑定专属 CPU 核、屏蔽无关中断,宁可牺牲通用性也要确定性。调度参数从来不是抄来的,是按场景算出来的。
辨析:抽象与优化的关系值得单独说清。抽象层每厚一层,开发效率就高一分,性能感知就钝一分;当性能不够用时,工程师会「打洞穿透抽象」直接对接底层(比如绕开文件系统直接访问磁盘块、绕开标准库手写热点路径)。于是成熟软件的形态常常是:外圈厚抽象保开发效率,内圈关键路径薄抽象保性能——两种取向共存,靠性能测量决定边界画在哪。
12.3进程与线程
一个正在运行的程序,在 OS 眼里就是一个 进程(process)。进程是「运行中的程序」的实例,它拥有自己的内存空间、自己的数据、自己的执行状态。
关键在「同时跑多个程序」:CPU 其实一次只能做一件事,但 OS 靠极快的切换,让每个进程轮流用一小段 CPU 时间,切换快到人眼无法察觉,于是你「感觉」所有程序都在同时运行——这叫 多任务(multitasking)。
一个进程内部,还可以拆成更小的执行单位叫 线程(thread)。
| 维度 | 进程 | 线程 |
|---|---|---|
| 是什么 | 运行中的程序实例 | 进程内的执行单元 |
| 资源 | 拥有独立内存空间 | 共享进程的内存 |
| 类比 | 一个「工厂」 | 工厂里的「工人」 |
📖 名词溯源:process 与 thread process 是「过程、流程」,强调一段正在进行的活动;thread 是「线」,像一条「执行线」。一个进程是一条主「线」在跑,多个线程就是多根「线」并排推进,共享同一个进程的资源。
🧠 一句话记忆 进程 = 工厂(独立资源);线程 = 工人(共享资源、并行干活)。
进程的完整画像:程序、进程与三态模型
先分清两个词:程序是躺在硬盘上的静态文件(指令 + 数据的集合),进程是程序被 OS「叫醒」后的运行实例。双击一个程序,OS 便为它建立一份档案——进程控制块(PCB,Process Control Block),里面记着它的编号(PID)、寄存器现场、内存映射表、打开的文件清单和当前状态。程序是剧本,进程是演出:同一份剧本可以同时开多场演出——开三个记事本窗口,就是一个程序对应的三个独立进程,各有各的 PCB、各改各的文本。
进程不是「要么在跑要么没跑」那么简单,经典的三态模型把它拆成三种状态:运行(正在用 CPU)、就绪(万事俱备,只差 CPU)、阻塞(在等磁盘/键盘/网络等事件,给了 CPU 也没法干)。状态转换只有四条边:运行 → 就绪(时间片用完,被换下);运行 → 阻塞(主动发起 I/O,等数据);阻塞 → 就绪(I/O 完成,数据到了,重新排队);就绪 → 运行(被调度器选中)。注意没有「阻塞 → 运行」这条边——等 I/O 的进程数据到了也不能直接上 CPU,必须先回就绪队列重新排队,调度是唯一通往 CPU 的入口,这个「单入口」纪律正是调度器能掌控全局的原因。
「切换」时发生了什么?被换下的进程把 PC、寄存器值等现场存进自己的 PCB,让出 CPU;被选中的进程从 PCB 恢复现场,从上次断点处继续——对进程自己而言,它完全不知道自己被中断过。所谓多任务,就是用极快的现场保存/恢复,让每个进程都以为自己独占 CPU;切换每秒发生成百上千次,人眼自然无感。
那为什么还要发明线程?进程切换有个贵价步骤:换内存映射表——每个进程地址空间不同,切换时必须整套更换,开销大。若一个进程内部想同时干几件事(一边响应用户输入、一边后台存盘、一边播放动画),开三个进程太贵(要三份内存、还得跨进程通信),于是把「执行线」从进程里再拆细:线程共享进程的内存映射与资源,只各持一套寄存器现场——切换线程不换映射表,只换现场,便宜得多。工厂(进程)只开一次门、铺一次水电,多雇几个工人(线程)并行干活,就是这个道理。
📐 例题 3 时间片轮转调度:甘特图与周转时间计算
单核系统,三个进程同时在 t = 0 到达:P1 需要 CPU 共 8 毫秒、P2 需 4 毫秒、P3 需 6 毫秒(忽略 I/O 与切换开销)。调度采用时间片轮转,时间片 4 毫秒,就绪队列按 P1→P2→P3 排队,队列循环往复。(1) 画出调度甘特图;(2) 求每个进程的完成时间与周转时间(周转 = 完成 − 到达);(3) 求平均周转时间;(4) 若改用「先来先服务」(FCFS,P1 跑完才轮 P2),平均周转时间又是多少?比较两种调度。
思路 轮转调度像发牌:每人一轮拿一张时间片,没干完的排到队尾。逐段推演:每段 4 毫秒,谁被选中就减去 4 的剩余需求,减到 0 就完成退场。FCFS 则是排队到底、绝不插手。
解 (1) 第一轮:0–4 P1(剩 4),4–8 P2(4 用完,完成),8–12 P3(剩 2);第二轮:12–16 P1(4 用完,完成),16–18 P3(2 用完,完成)。甘特图见下图。(2) 完成时间:P1 = 16,P2 = 8,P3 = 18;周转时间(到达均为 0):P1 = 16,P2 = 8,P3 = 18。(3) 平均周转 = (16 + 8 + 18) ÷ 3 = 42 ÷ 3 = 14 毫秒。(4) FCFS:P1 于 8 完成、P2 于 12、P3 于 18;周转 P1 = 8、P2 = 12、P3 = 18,平均 = (8 + 12 + 18) ÷ 3 = 38 ÷ 3 ≈ 12.7 毫秒。总耗时同为 18 毫秒,FCFS 平均周转反而略短;但看 P2——轮转下 8 毫秒就完成,FCFS 下要等 12 毫秒,短任务在轮转下早完工,交互体验(响应)正是靠这一点。
答:(1) 甘特图如上,执行顺序 P1→P2→P3→P1→P3;(2) 完成 P1=16、P2=8、P3=18,周转同值;(3) 平均周转 14 毫秒;(4) FCFS 平均周转约 12.7 毫秒,但短任务 P2 要等 12 毫秒才完工。轮转牺牲一点平均周转,换来「短任务早完成」与「人人有响应」——公平与高效的教科书式权衡。 ⚠ 易错点:① 「阻塞 → 运行」这条边不存在,I/O 完成只能先回就绪;② 周转时间从到达时刻算起,本题三进程同时到达所以周转 = 完成时间;③ 比较调度好坏别只看平均值,短任务的最早完成时间(响应)往往是交互系统的命根。
工程视角:实时系统把这道题的思路推到极致——调度器不再是「雨露均沾」,而是按截止期排优先级:飞控律计算、传感器数据接收永远排在日志记录、界面刷新之前;每个关键任务的「最坏完成时间」都经过数学证明而非碰运气。你在桌面电脑上感觉到的「卡顿」,与实时系统绝不允许出现的「迟到」,本质是同一个调度问题在两种场景下的不同答卷。
辨析:进程 vs 线程的五维对照(批注:这张表是本节的核心考点):
| 维度 | 进程 | 线程 |
|---|---|---|
| 地址空间 | 各自独立,互不可见 | 同进程内所有线程共享 |
| 切换成本 | 高:要换整套内存映射表 | 低:只换寄存器现场 |
| 通信方式 | 跨地址空间,需内核搭桥(管道、消息) | 直接读写共享变量,但要防「同时写」 |
| 崩溃半径 | 一个进程崩,别人安然无恙 | 一个线程崩(如野指针),整个进程陪葬 |
| 数量级 | 重装备,一个进程几 MB 起步 | 轻装备,一个线程几 KB 栈即可 |
一句话总结取舍:要隔离、要容错,选进程;要轻快、要共享,选线程。浏览器正是教科书式用户——每个标签页一个进程(一个页面崩不拖累别人),页面内部再开多条线程做渲染与脚本并行。
最后补上「切换靠谁执法」这块拼图:进程凭什么会乖乖交出 CPU?靠的是时钟中断——硬件定时器每隔固定间隔向 CPU 发一个中断,当前进程应声被打断,控制权落回内核调度器手中,由它清点时间片、决定下一位上场。没有时钟中断,一个死循环程序就能永远霸占 CPU;「分时」的每一拍,都是定时器在替调度器鸣笛执法——这个机制将在 12.5 节正面登场。
12.4内存管理与虚拟内存
多个进程同时跑,每个都要用内存,但内存就那么多。OS 要保证:每个进程有自己独立、隔离的内存空间,谁也读不到、改不了别人的数据——这是 内存管理(memory management)的核心。
这里最精妙的机制是 虚拟内存(virtual memory):
- OS 给每个进程「画」出一大片假想的内存空间(虚拟地址空间),每个进程都以为自己独占一大片内存。
- 实际上,OS 通过一张「映射表」,把虚拟地址翻译成真实的物理地址。
- 不常用的数据,OS 会把它「换出」到硬盘(换页),腾出物理内存给更需要的进程。
🧠 一句话记忆 虚拟内存 = 每个进程都「以为」自己独占一大片内存,实际由 OS 映射翻译。
为什么非要隔离:没有虚拟内存的三个灾难
「每个进程有独立内存空间」这句话若不落实,会发生什么?灾难一:误写邻居——程序 A 的野指针写错了地址,恰好落在程序 B 的数据区,B 的账目悄悄变成乱码,错误在离开案发现场很远的地方才爆发,查无对证。灾难二:抢地盘——两个程序都被装载器放到「从地址 0 开始」的位置,先来的被后来的覆盖;没有统一登记,物理内存的每一格都可能被重复分配。灾难三:越权窥视——恶意程序直接读别人的内存,口令、密钥一览无余,因为「谁都可以读一切」。虚拟地址空间是对这三个问题的一揽子回答:给每个进程发一张独享的「假地图」,进程只能按自己的地图找路;而地图到真实世界的翻译权(页表)牢牢握在内核手里,进程既改不了别人的翻译,也伪造不了自己的入口。
这个设计顺手带来三大收益,一个比一个精妙。隔离:进程 A 的地址 500 与进程 B 的地址 500 指向不同的物理格子,互不干扰,越界访问在翻译环节就被硬件拒绝(产生异常),错误当场抓获而非远走高飞。按需装载:程序可以比物理内存大——映射表只登记「用到的那几页」,暂时不用的数据留在磁盘,用到时再换入;于是「128 GB 的程序跑在 16 GB 内存的机器上」并非魔术,只是边跑边换。装载灵活:因为每个进程都从自己地图的固定位置找路,程序文件本身不必关心「会被装到物理内存的哪里」——重定位的麻烦由映射表一力承担。
映射的粒度叫页(page):把虚拟空间和物理空间切成同样大小的小块(典型 4 KB),页表记录「虚页号 → 物理页帧号」的对照关系。4 KB 是个折中:页太大,浪费在「最后一页用不满」的碎片;页太小,页表条目爆炸、查表成本上升。当进程访问的页恰好在磁盘上,硬件触发缺页(page fault):OS 暂停该进程 → 从磁盘把页读入物理内存 → 更新页表 → 重新执行刚才那条访问指令——进程对此毫无察觉,只感觉这次访问「慢了一点」。
📐 例题 4 页表:手工翻译一个虚拟地址
某系统页大小 4 KB(页内偏移占 12 位),虚拟地址 20 位、物理地址 24 位。进程 A 的页表中有一项:虚页 0x1A → 物理页帧 0x35。(1) 虚拟地址 0x1A2B3 落在哪个虚页、页内偏移是多少?(2) 它翻译成的物理地址是多少?(3) 该系统的虚拟空间共多少页、多大?物理内存共多少页帧、多大?(4) 若虚页 0x1B 映射的页此刻在磁盘上,访问虚页 0x1B 的第一条指令会发生什么?
思路 页大小 4 KB = 212,所以地址最低 12 位是页内偏移,其余高位是页号——「切地址」就是二进制按位切段。翻译物理地址 = 把页号换成页帧号,偏移原样拼接。
解 (1) 页号 = 0x1A2B3 的高 8 位:0x1A2B3 ÷ 0x1000 = 0x1A 余 0x2B3,即虚页 0x1A、页内偏移 0x2B3。(2) 物理地址 = 页帧 0x35 拼偏移 0x2B3 = 0x35 × 0x1000 + 0x2B3 = 0x352B3。(3) 虚拟地址 20 位 → 虚拟空间 220 = 1 M 地址;页数 = 220 ÷ 212 = 28 = 256 页,容量 256 × 4 KB = 1 MB。物理地址 24 位 → 物理内存 224 = 16 M 地址 = 16 MB,页帧数 = 224 ÷ 212 = 212 = 4096 帧。(4) 硬件查页表发现该页「不在内存」,触发缺页:CPU 暂停进程 A → OS 从磁盘把该页换入某个空闲(或按策略腾出)的页帧 → 更新页表项 → 回到断点重新执行那条访问指令——程序自己毫无感觉。
答:(1) 虚页 0x1A、偏移 0x2B3;(2) 0x352B3;(3) 虚拟空间 256 页共 1 MB,物理内存 4096 帧共 16 MB;(4) 触发缺页处理,OS 换入该页后重执行。 ⚠ 易错点:① 「页大小 4 KB → 偏移占 12 位」这步换算(4 KB = 212)是全部计算的钥匙;② 物理地址 = 物理页帧号 × 页大小 + 偏移,页帧号别再当作偏移的一部分;③ 本例物理地址位宽(24 位)大于虚拟位宽(20 位),虚拟空间小于物理内存——真实桌面系统恰好相反(48 位虚拟 > 物理几十 GB),方向不影响翻译方法。
工程视角:安全关键系统把「隔离」推向硬件级强制——航电、汽车电子普遍采用内存分区(partitioning)设计:把不同关键度的功能关进各自的分区,每个分区有独立的时间窗口与内存边界,分区之间由内核严格把关;一个分区的程序哪怕跑飞,也被硬件挡在围墙外,烧不到隔壁。这套思想与本题的页机制同源:都信不过「程序会守规矩」,于是把规矩焊进翻译层。
辨析:分清三块「存储」——虚拟内存是每个进程看到的假想空间(地址从 0 开始排,上不封顶于物理大小);物理内存是真实条子的容量;交换空间是磁盘上划出的「临时仓库」,存放被换出的页。三者关系一句话:虚拟是名义、物理是现场、交换是后院。任务管理器里「占用 80% 内存」说的是现场,「页面文件占用」说的是后院;后院越忙,机器越卡——因为磁盘比内存慢百倍量级,频繁缺页等于让程序踩着泥地跑。
12.5中断:CPU 如何「被打断」
CPU 在闷头执行程序时,外面突然发生了一件事——你按了一下键盘、移动了鼠标、网卡收到了数据。CPU 怎么知道该停下来处理?靠 中断(interrupt)。
中断是硬件发给 CPU 的一个「紧急信号」:当外设需要 CPU 的注意时,就拉高一根中断信号线。CPU 在每条指令执行完的「间隙」检查这根线,发现有中断,就暂停当前程序,保存现场,跳去执行对应的中断处理程序,处理完再回来继续。
核心思想 中断 = 硬件主动「叫醒」CPU 的机制。没有中断,CPU 只能笨拙地「轮询」(不断问外设:有事吗?有事吗?),效率极低。有了中断,CPU 平时专心算,外设有事才来敲门——这正符合第 7 章说的「输入设备通过中断打扰 CPU」。
🧠 一句话记忆 中断 = 外设敲门;CPU 每执行完一条指令就「瞄一眼门」,有事就暂停去处理。
中断的完整流程:保存现场、查向量表、恢复现场
「暂停当前程序」这四个字在硬件层是一套标准动作:CPU 在每条指令执行完的间隙检查中断线,发现有信号,先做保存现场——把 PC(下一条指令的位置)与关键寄存器压入约定好的位置;然后查中断向量表——一张「中断号 → 处理程序入口」的对照表,由 OS 在启动时填好(呼应第 11 章:又是「查表」,硬件世界的万能解题法);接着跳去执行中断处理程序——或读走键盘的按键码,或搬运网卡数据;最后恢复现场,回到被打断的位置继续。全程对应用程序透明:它既不知道、也不需要知道刚才发生过什么。
中断还分优先级:传感器数据到达与界面刷新请求不能一视同仁——紧急的中断可以打断不紧急的处理,后者只能等前者办完。OS 启动时把向量表与优先级一起定好,就像大楼物业排好「火警优先于快递」的值班表。这也解释了为什么中断处理程序要写得极短:它执行期间,低优先级事件全被压着,长处理等于堵门。
「轮询 vs 中断」不该只停留在「一个笨一个巧」的定性印象,算一笔账更清楚——下面例题用数字回答「为什么 CPU 必须学会被打断」。
📐 例题 5 轮询与中断的开销对账
某控制器要监视一个键盘:人手最快每秒 10 次按键。(1) 轮询方案:CPU 每秒读 1000 次状态寄存器,每次读+判空耗时 2 微秒,求一秒内轮询花掉的 CPU 时间与占比;(2) 若还要监视另外 99 个同类设备,每个也按 1000 次/秒轮询,占比变成多少?(3) 中断方案:只有按键发生时硬件才通知 CPU,每次中断处理共耗时 50 微秒,按每秒 10 次按键计,CPU 开销占比是多少?(4) 若 100 台设备每台每秒各有 10 次真实事件,两种方案的开销又各是多少?
思路 开销 = 事件次数 × 单次成本。轮询的次数由「查多勤」决定(与有没有事无关);中断的次数由「真有多少事」决定(没事就零开销)——这是两种机制的本质分野。
解 (1) 1 秒轮询 1000 次 × 2 微秒 = 2000 微秒 = 2 毫秒,占比 2 ÷ 1000 = 0.2%。(2) 100 台设备:100 × 1000 × 2 = 200000 微秒 = 200 毫秒,占比 20%——五分之一的 CPU 在「问有事吗」。(3) 中断:10 次 × 50 = 500 微秒 = 0.5 毫秒,占比 0.05%。(4) 100 台设备共 1000 次/秒真实事件:中断开销 1000 × 50 = 50000 微秒 = 50 毫秒,占比 5%;仍远低于轮询的 20%,且轮询这 200 毫秒里绝大部分是「白问」——事件只有 1000 次,查询却发生了 10 万次。
答:(1) 2 毫秒、0.2%;(2) 200 毫秒、20%;(3) 0.5 毫秒、0.05%;(4) 中断 50 毫秒(5%)vs 轮询 200 毫秒(20%)。轮询开销随「设备数 × 查询频率」增长,中断开销随真实事件数增长——设备越多、事件越稀,中断的优势越大。 ⚠ 易错点:① 轮询开销与「有没有按键」无关,只取决于查询频率,是死成本;② 中断单次成本(50 微秒)比单次轮询(2 微秒)贵得多,贵在保存/恢复现场的完整流程——省不省要看次数,不看单价;③ 轮询的响应延迟最多是一次查询间隔(1 毫秒),中断则几乎即时。
工程视角:机载设备的中断优先级表是安全设计的一部分:大气数据、操纵面传感器的信号处理中断必须高于数据记录、座舱显示刷新;关键中断的处理时间要经过测量并留有裕度。「响应永远来得及」不是写出来的,是算出来的——本例题就是这类核算的最小样例。
辨析:进入内核的「三扇门」值得分清——中断(interrupt,外部硬件来敲门:键盘、网卡、定时器)、异常(exception,程序自己出错被硬件抓住:除数为零、访问非法地址)、陷入(trap,程序主动要求进内核:即系统调用的实现方式)。三扇门的门牌不同,进门后的动作同源:保存现场、查表、处理、恢复。下一节要讲的「系统调用」,走的就是 trap 这扇门。
12.6内核态与用户态:一道安全闸
OS 需要保护自己:普通程序不能随便碰硬件、不能篡改别人的内存、不能搞垮系统。为此,CPU 设计了两级权限,由 OS 控制切换:
- 内核态(kernel mode):权限最高,可以执行任何指令、访问所有硬件。操作系统内核跑在这里。
- 用户态(user mode):权限受限,普通应用程序跑在这里,不能直接碰硬件、不能执行危险指令。
应用程序要做「高级操作」(读文件、联网、分配内存),必须通过一个受控的「大门」——系统调用(system call)——向内核提出请求,内核检查无误后代为执行。
🧠 一句话记忆 用户态 = 受限的普通人,内核态 = 有钥匙的管家;要碰硬件,先过「系统调用」这道门。
隔离靠的不是自觉,是 CPU 硬件
「普通程序不能碰硬件」这句话由谁执行?不是 OS 的「善意提醒」,而是 CPU 内部的一个特权级标志位:CPU 每条指令执行前都会检查自己当前处于哪一级。用户态下,一旦执行到「访问 I/O 端口」「关中断」「修改页表基址」这类特权指令,硬件直接拒绝并触发异常,把控制权交回内核——注意,是硬件拒绝的,程序连讨价还价的门都没有。于是整个安全模型形成闭环:OS 内核跑在内核态、手持全部钥匙;应用程序跑在用户态、处处受限;唯一合法的「进门」方式是系统调用,进门后干什么完全由内核说了算。这套设计的深刻之处在于:它把「保护 OS」从道德约定变成了物理不可能。
那么一次系统调用和一次普通函数调用,差别到底在哪?四维对照:①谁在干活——函数调用全程在你自己的进程内;系统调用则要跨进内核,由另一套代码代劳;②特权级——函数调用不切换权限,系统调用必须从用户态切到内核态(走 trap 指令),办完事再切回来,这一来一回是硬开销;③入口——函数想调哪个调哪个,入口地址自由;系统调用的入口是内核出厂设定的一张表,按「系统调用号」索引,程序只能点菜、不能进后厨;④开销量级——普通函数调用纳秒级,系统调用通常几百纳秒到微秒级,贵几十上百倍,贵在特权切换与现场保存。以打开文件为例走一遍全程:应用调用库函数 open() → 库函数把「系统调用号 + 参数」放进约定位置 → 执行 trap 指令 → CPU 切到内核态、跳到统一入口 → 内核按调用号查表找到对应的打开文件服务 → 校验权限、找到驱动、操作磁盘 → 结果沿原路返回、切回用户态。一次 open(),一次完整的「申请-审批-代办-回执」。
📐 例题 6 系统调用开销与缓冲区大小的权衡
设一次普通函数调用耗时 5 纳秒,一次系统调用(含特权级切换与内核处理,不计磁盘传输本身)耗时 500 纳秒。某程序要读入 1 MB 文件。(1) 若每次 read() 读 4 KB,共需多少次系统调用?仅调用开销共多少微秒?(2) 若每次读 64 KB 呢?(3) 为什么「加大缓冲区」是降低系统调用开销的万能钥匙?代价是什么?
思路 调用次数 = 总量 ÷ 单次读入量;开销 = 次数 × 单次开销。注意单位换算:1 MB = 1024 KB,1 微秒 = 1000 纳秒。
解 (1) 1 MB = 1024 KB;1024 ÷ 4 = 256 次调用,开销 256 × 500 = 128000 纳秒 = 128 微秒。(2) 1024 ÷ 64 = 16 次调用,开销 16 × 500 = 8000 纳秒 = 8 微秒——缩到十六分之一。(3) 开销与调用次数成正比,而次数与缓冲区大小成反比,所以缓冲每放大一倍、开销就减半;代价是内存占用(缓冲区本身要占内存)与延迟(数据要先攒够一批才处理),实时系统里「攒批」可能违背及时性。
答:(1) 256 次、128 微秒;(2) 16 次、8 微秒;(3) 大缓冲摊薄了每次调用的固定成本,代价是内存与延迟。 ⚠ 易错点:① 1 MB 是 1024 KB 不是 1000 KB,进制用错整题全错;② 500 纳秒是「进门费」,不含数据搬运本身的耗时,真实总耗时还要叠加 I/O;③ 权衡永远存在——实时系统宁可用小缓冲多次调用,也要数据尽快到手。
工程视角:数据库、视频编解码这类 I/O 密集软件的底层都在做本题的算术——把小读写合并成大块、减少 trap 次数,吞吐就能明显提升。反过来,标准库的 printf() 不会每次都去敲内核的门:它先把内容攒在用户态缓冲区,攒满或遇换行才批量发出真正的写系统调用。你调了十次 printf,内核可能只被打扰了一次——好的库设计,本质是在替你做这道权衡题。
辨析:把「函数调用 / 库函数 / 系统调用」三层关系摆正:函数调用是语言层的普通跳转,不进内核;库函数是别人替你写好的函数,其中一部分(如 open、read)内部包着系统调用,另一部分(如 strlen、sqrt)纯在用户态完成;系统调用才是真正跨越特权级的那个动作。所以「库函数一定慢」或「系统调用随便用」都是误读——strlen 与 open 的成本差着两个数量级,写性能敏感代码前先分清手里这把是哪个。
把本章四个机制串成一句话:时间靠中断切分,空间靠页表隔离,权限靠特权级把关,资源靠调度器分配——四道防线合在一起,才有了「多个程序共处一台机器而互不伤害」的日常。下一章把镜头拉回程序员这一侧:高级语言写下的每一行,最终都要经由编译器翻译成这一章与上一章里的指令,才有机会走到这套机制面前。
本章小结
- 操作系统是硬件与应用的「大管家」,两大职责是资源管理与抽象。
- 进程是运行中的程序实例(独立资源),线程是进程内的执行单元(共享资源)。
- 多任务是靠极快的进程切换实现的「看似同时」。
- 虚拟内存让每个进程都以为独占大片内存,由 OS 映射到物理内存。
- 中断让硬件主动叫醒 CPU;内核态/用户态隔离权限,系统调用是受控的通道。
- 调度、隔离、中断、系统调用都可以算账:时间片开销 ≈ 单次切换 ÷ 片长;轮转调度的周转时间靠甘特图逐段推演;页表翻译 = 页帧号 × 页大小 + 偏移;轮询成本随设备数线性涨、中断成本随真实事件数走;系统调用开销靠大缓冲摊薄——每一个 OS 概念背后,都有一条可算的权衡公式。
读完本章,你应该能……
- 说出操作系统的两大核心职责,并各举一例。
- 区分进程和线程,并说出它们的关系。
- 解释「多任务」其实是如何实现的。
- 解释虚拟内存如何实现进程间的内存隔离。
- 解释中断的作用,以及内核态/用户态为何需要隔离。
思考题
1. 操作系统的「资源管理」和「抽象」分别指什么?
提示:资源管理=分配 CPU/内存/外设;抽象=隐藏硬件细节、提供统一接口。
2. 进程和线程有什么不同?用工厂和工人类比。
提示:进程=工厂(独立资源),线程=工人(共享资源、并行)。
3. 虚拟内存如何让多个进程「和平共处」?
提示:每个进程有独立虚拟空间,OS 映射到物理内存,实现隔离。
4. 为什么需要「内核态」和「用户态」?程序如何从用户态进入内核态?
提示:隔离权限、保护系统;通过系统调用受控进入。
5. 进程正在「阻塞」等磁盘数据,数据到达后它立即变成什么状态?为什么不是「运行」?
提示:阻塞 → 就绪。数据到了只是「可以跑了」,还须回到就绪队列等调度器选中——进入 CPU 的唯一入口是调度。
6. 三个进程各需 CPU 8、4、6 毫秒(同时到达),时间片 4 毫秒轮转,谁最先完成?平均周转时间是多少?
提示:甘特图 P1(0–4)→P2(4–8)→P3(8–12)→P1(12–16)→P3(16–18);P2 于 8 毫秒最先完成,平均周转 (16+8+18)÷3 = 14 毫秒。
7. 页大小 4 KB、虚拟地址 20 位:页号占几位?虚拟空间共多少页?
提示:4 KB = 2¹²,偏移占 12 位,页号占 20 − 12 = 8 位,共 2⁸ = 256 页(虚拟空间 1 MB)。
8. 系统调用与普通函数调用最大的两点不同是什么?
提示:① 特权级切换(用户态 ↔ 内核态,有硬开销);② 入口受控(只能按系统调用号查内核的表,不能随意跳转)。