简介:本资源是一份面向计算机专业本科生的操作系统课程期末复习笔记,聚焦核心概念梳理与考点精讲,帮助学生高效应对期末考试。笔记系统覆盖操作系统目标(方便性、有效性、可扩充性、开放性)、发展历程(人工操作→批处理→分时→实时系统)、四大基本特性(并发性、共享性、虚拟性、异步性),以及处理机、存储器、设备和文件四大管理功能;重点详解进程状态转换、PCB结构、临界区与同步机制(P/V操作、信号量类型对比)、前趋图与经典同步问题等高频难点。资源为1个1.17MB的Word文档(.docx),内容排版清晰、术语准确、逻辑连贯,适合作为考前速记与知识脉络梳理工具。目前已有779人学习下载,内容源自一线学习实践,知识点与高校主流教材及教学大纲高度契合,可直接用于背诵、自测与查漏补缺。
1. 操作系统期末复习:一张不能跳过的知识地图——从“并发黑匣子”到“内存调度血泪经验”
如果你正在翻这本笔记,大概率是临近期末、打开教材第一页就看到“并发”两个字,然后盯着“宏观同时、微观交替”发了三分钟呆;或者在做银行家算法题时,算着算着发现死锁进程数比自己挂科门数还多;又或者对着“快表命中率”和“EAT公式”反复确认单位是不是写错了——别慌,这不是你一个人的玄学现场。操作系统这门课,表面是讲CPU怎么干活、内存怎么分、磁盘怎么转,实则是一张精密咬合的状态-资源-时间三维约束网:进程状态切换受制于资源分配,资源分配依赖于调度策略,而所有调度又必须在时间片、截止期、响应时限这些硬性刻度上跳舞。这张地图里没有孤立知识点——你不可能只懂信号量却绕开PCB结构,也不可能只背熟LRU算法却不理解局部性原理如何支撑它。整张图由四大特性(并发/共享/虚拟/异步)为骨架,五大功能模块(处理机/存储器/设备/文件/接口)为血肉,三层调度+四类内存管理+三类I/O控制为神经末梢,而所有考试高频陷阱,都藏在“看似合理但违反约束”的交界处:比如用FCFS解实时任务、用静态重定位应对动态分区、或在未设互斥信号量时直接操作打印机缓冲区。这份笔记不是按教材章节目录平铺直叙,而是按真实复习动线重构:先建立全局坐标系(第1章),再逐层拆解核心模块的“必考逻辑链”(第2–4章),接着直击最容易翻车的易错点黑洞(第5章),最后给你一套可执行的查漏补缺清单(第6章)。它适合两类人:一类是刚啃完教材想验证理解是否闭环的系统学习者,另一类是翻开习题集前急需快速定位薄弱环节的冲刺党。现在,我们从最常被忽略的起点开始:那个让所有进程“停停走走”的异步性,才是整个操作系统设计的底层心跳。
2. 进程管理:从状态转换到同步机制的完整逻辑链
进程是操作系统资源分配和调度的基本单位,但它的本质远不止“正在运行的程序”这么简单。理解进程管理,必须抓住一条贯穿始终的逻辑链:状态变化驱动资源需求 → 资源竞争引发同步问题 → 同步机制依赖内核原语保障 → 原语执行依托于PCB信息支撑。这条链上的任何一环断裂,都会导致整个并发模型崩塌。下面按模块拆解其核心要点与技术细节。
2.1 进程状态与PCB:状态转换的物理载体
进程状态并非抽象概念,而是由操作系统内核通过修改PCB(进程控制块)中特定字段来实现的。PCB是进程存在的唯一标志,其结构直接决定了状态转换的可行性与效率。
| PCB关键字段 | 作用说明 | 关联状态转换场景 |
|---|---|---|
| 进程标识符(PID/内部ID) | 全局唯一标识,用于进程间通信和调度识别 | 创建状态→就绪态时分配;终止态时回收 |
| 进程状态字段(state) | 存储当前状态(就绪/执行/阻塞/创建/终止/挂起) | 状态转换的核心判据,如执行态→阻塞态需将state置为BLOCKED |
| 程序计数器(PC) | 指向下一条待执行指令地址 | 进程切换时保存/恢复执行现场的关键,确保“停停走走”后能续跑 |
| CPU寄存器集合 | 保存当前运行时所有通用寄存器值 | 上下文切换的实质:将旧进程寄存器值存入PCB,加载新进程寄存器值 |
| 内存管理信息(页表基址/段表基址) | 指向该进程的地址空间映射结构 | 进程切换时需更新MMU(内存管理单元)的页表基址寄存器(PTR) |
| 进程优先级/调度信息 | 决定进程在就绪队列中的位置 | 高优先级调度算法(如PSA)直接读取此字段进行抢占判断 |
提示:PCB不是静态数据结构,而是动态演化的“进程身份证”。当进程从就绪态被调度执行时,内核不仅修改state字段,还会将PCB中保存的寄存器值加载到CPU物理寄存器中;当进程因I/O阻塞时,内核会将当前PC值存回PCB,并将state置为BLOCKED。这种“状态-数据-硬件”的强绑定,正是操作系统实现进程抽象的基础。
五态模型(创建、就绪、执行、阻塞、终止)是理解状态转换的最小完备集,但实际系统中还需考虑挂起态(Suspended)以支持对换(Swapping)。挂起态分为就绪挂起(Ready-Suspend)和阻塞挂起(Blocked-Suspend),其引入直接催生了中级调度——当内存紧张时,将低优先级就绪进程或长期阻塞进程换出到外存,释放内存空间。此时PCB中需增加外存地址字段,记录进程映像在外存中的位置,以便后续换入时精准定位。
2.2 进程同步:为什么“让权等待”是不可妥协的铁律
进程同步的本质,是解决并发执行带来的不可再现性问题。程序顺序执行具有封闭性(独占资源)和可再现性(相同输入必得相同输出),而并发执行打破了这一前提:多个进程共享资源,执行次序不确定,导致结果依赖于随机的时序竞争。同步机制的目标,就是通过施加确定性约束,使并发程序的行为变得可预测、可验证。
同步问题分为两类典型场景:
- 间接制约(互斥):进程因竞争同一临界资源(如打印机、共享变量)而相互制约。例如,两个进程同时尝试修改全局计数器
count++,若无保护,最终结果可能丢失一次自增。 - 直接制约(协作):进程因存在前驱关系(如生产者必须先于消费者执行)而相互等待。例如,消费者进程必须等待生产者放入数据后才能取用,否则会读取无效数据。
所有同步机制的设计,都围绕四个黄金规则展开:
- 空闲让进:当临界资源空闲时,允许请求进程立即进入;
- 忙则等待:当资源被占用时,请求进程必须等待;
- 有限等待:等待时间必须有上限,避免无限期阻塞;
- 让权等待:等待时应释放CPU,避免忙等(Busy Waiting)浪费处理器资源。
注意:“让权等待”是区分高级与低级同步方案的分水岭。早期软件方案(如Peterson算法)虽满足前三个规则,但无法避免忙等——进程在while循环中不断检查条件,CPU周期被白白消耗。硬件方案(如TSL指令)虽原子性强,但同样无法让权。真正突破在于信号量机制,它通过内核提供的wait(S)/signal(S)原语,将“检查-等待-唤醒”封装为不可分割的操作,并在等待时主动将进程挂起(让权),由内核在资源可用时唤醒它。
2.3 信号量家族:从整型到信号量集的演进逻辑
信号量(Semaphore)是OS内核提供的同步原语,其核心思想是用一个整型变量S表示某类资源的可用数量,并通过wait(S)(P操作)申请资源、signal(S)(V操作)释放资源。但单一整型信号量在复杂场景下力不从心,由此衍生出记录型、AND型、信号量集等变体,每一种都是为解决特定约束而生。
整型信号量是最简形式:
// 整型信号量定义(伪代码) int S = 1; // 初始值为1,表示1个临界资源 // wait(S) 原语(P操作) void wait(int *S) { while (*S <= 0); // 忙等!违反“让权等待” (*S)--; } // signal(S) 原语(V操作) void signal(int *S) { (*S)++; }关键缺陷:
while(*S<=0)导致CPU空转,严重降低系统效率。这是理论模型,实际OS绝不采用。
记录型信号量解决了忙等问题,引入了阻塞队列:
// 记录型信号量结构(伪代码) typedef struct { int value; // 资源计数,>=0表示空闲数,<0表示阻塞进程数 struct process_queue *list; // 等待该信号量的进程链表 } semaphore; // wait(S) 原语(P操作) void wait(semaphore *S) { S->value--; if (S->value < 0) { add_current_process_to(S->list); // 将当前进程加入阻塞队列 block(); // 主动让权,挂起当前进程 } } // signal(S) 原语(V操作) void signal(semaphore *S) { S->value++; if (S->value <= 0) { process *p = remove_from(S->list); // 从阻塞队列取一个进程 wakeup(p); // 唤醒该进程 } }物理意义清晰:
S.value的正负直接对应资源状态与等待规模。但局限在于单资源类型——若进程需同时申请打印机和磁带机,需设置两个信号量,易引发死锁(如进程A持打印机等磁带,进程B持磁带等打印机)。
AND型信号量专治多资源死锁:
// Swait(S1, S2, ..., Sn) 原语(原子性检查所有资源) void Swait(semaphore *S[], int n) { for (int i = 0; i < n; i++) { if (S[i]->value <= 0) { // 任一资源不足 // 将当前进程加入所有S[i]的阻塞队列 // 并挂起进程 —— “全有或全无” block(); return; } } for (int i = 0; i < n; i++) { S[i]->value--; // 所有资源充足,一次性分配 } }核心价值:打破“持有并等待”条件,从根源上预防死锁。但代价是资源利用率低——即使其他资源充足,只要一个紧缺,所有资源都不可用。
信号量集则更精细:支持批量申请与阈值控制。例如,某进程需至少3个磁盘块才启动I/O,少于3块则宁可等待:
// Swait(S, d, t) 表示:申请S资源,当前值≥t才分配,分配d个 // 若S.value < t,则阻塞;否则S.value -= d Swait(disk_sem, 3, 3); // 至少3块才分配,每次分配3块工程意义:贴近真实场景。磁盘I/O通常以簇为单位,而非单字节;数据库事务常需锁定多个页。信号量集让同步逻辑与业务语义对齐。
2.4 经典同步问题:生产者-消费者与哲学家进餐的建模本质
教科书常将经典问题视为习题,实则它们是同步建模能力的试金石。能否正确抽象出问题中的资源、约束、角色,直接决定解法是否健壮。
生产者-消费者问题(Bounded-Buffer):
- 资源抽象:缓冲区(N个槽位)、空槽(empty)、满槽(full)、互斥锁(mutex)
- 约束建模:
- 生产者需
wait(empty)(有空槽才生产)→wait(mutex)(互斥访问缓冲区)→ 生产 →signal(mutex)→signal(full)(通知消费者) - 消费者需
wait(full)(有满槽才消费)→wait(mutex)→ 消费 →signal(mutex)→signal(empty)
- 生产者需
- 易错点:
wait(mutex)与wait(empty/full)的顺序不能颠倒!若先wait(mutex)再wait(empty),生产者可能持锁等待空槽,导致消费者无法进入取走数据,形成死锁。
哲学家进餐问题:
- 资源抽象:5根筷子(5个临界资源)、5位哲学家(5个进程)
- 约束建模:每位哲学家需同时拿起左右两根筷子(资源)才能进餐。
- 解法对比:
- 奇偶编号法:奇数哲学家先取左筷再取右筷,偶数反之。打破循环等待。
- 资源分级法:规定所有哲学家必须按编号从小到大顺序取筷(如1号先取1筷,再取2筷;2号先取2筷,再取3筷)。消除循环依赖。
- 服务员仲裁法:引入一个信号量
room(初值为4),限制最多4人同时尝试进餐,确保至少1人能拿到2根筷完成进餐并释放资源。
建模启示:所有同步问题,第一步永远是识别临界资源(什么被共享?),第二步是明确访问约束(谁在何时需要什么?),第三步才是选择同步工具。脱离场景空谈P/V操作,如同无图航海。
3. 处理机调度:从算法选择到死锁防控的决策树
处理机调度是操作系统资源分配的“大脑”,其目标是在满足系统目标(高吞吐量、低延迟、公平性)的前提下,决定哪个进程获得CPU。但调度绝非单纯比拼算法优劣,而是一个多目标权衡、多层级协同、多约束规避的复杂决策过程。本章将梳理调度的三层架构、核心算法逻辑、以及死锁这一调度失效的终极后果。
3.1 三级调度:时间粒度与资源粒度的双重维度
操作系统调度并非单一动作,而是按时间尺度和资源粒度划分为三个层次,形成金字塔式管控体系:
| 调度层级 | 触发时机 | 调度对象 | 典型频率 | 核心目标 | 关键数据结构 |
|---|---|---|---|---|---|
| 高级调度(作业调度) | 长时间间隔(秒/分钟级) | 作业(Job,外存中的一批程序) | 低频(如每分钟几次) | 提高系统吞吐量,平衡CPU与I/O负载 | 作业后备队列、作业控制块(JCB) |
| 中级调度(内存调度) | 中等时间间隔(毫秒/秒级) | 进程(Process,内存中的执行实体) | 中频(如每秒数次) | 提高内存利用率,缓解内存紧张 | 进程挂起/激活队列、PCB中的挂起标志 |
| 低级调度(进程调度) | 极短时间间隔(微秒/毫秒级) | 线程/进程(CPU执行单元) | 高频(如每毫秒一次) | 保证响应时间,提升CPU利用率 | 就绪队列(按优先级/时间片组织)、调度队列指针 |
关键洞察:三级调度是解耦资源管理与执行控制的必然选择。高级调度决定“谁有资格上场”,中级调度决定“谁现在能上场”,低级调度决定“场上谁先踢球”。例如,在多道批处理系统中,高级调度从外存作业池中选一批作业调入内存;当中内存吃紧时,中级调度将部分阻塞进程换出到外存(挂起);而低级调度则在就绪队列中,按时间片轮转选出下一个执行进程。三者协同,才能实现“宏观高吞吐、微观低延迟”的平衡。
3.2 调度算法:从公平性到实时性的性能光谱
不同调度算法服务于不同系统目标,其优劣不能脱离场景评判。下表对比主流算法的核心逻辑与适用边界:
| 算法 | 核心逻辑 | 优点 | 缺点 | 典型适用场景 |
|---|---|---|---|---|
| FCFS(先来先服务) | 按进程到达时间顺序排队 | 实现简单,公平(先到先得) | 平均等待时间长;“护航效应”(长作业阻塞短作业);无交互性 | 单道批处理系统(历史意义) |
| SJF(短作业优先) | 优先调度预计运行时间最短的作业 | 平均等待时间最优(理论下界) | 需预知运行时间(实践中难);可能导致长作业饥饿 | 短作业密集的批处理环境 |
| HRRN(高响应比优先) | 响应比 = (等待时间 + 要求服务时间) / 要求服务时间,选最大者 | 兼顾等待时间与服务时间;避免长作业饥饿 | 每次调度需计算所有就绪进程响应比,开销增大 | 通用批处理系统(平衡FCFS与SJF) |
| RR(时间片轮转) | 就绪队列首进程执行一个固定时间片(如10ms),超时则放回队尾 | 响应时间可预测(≤(n-1)×q);公平;适合交互 | 时间片过小→频繁切换,系统开销大;过大→退化为FCFS | 分时系统(如Linux默认CFS的简化版) |
| PSA(优先级调度) | 按进程优先级高低调度,可抢占或非抢占 | 可区分任务紧急程度;实时系统基础 | 低优先级进程可能永久等待(饥饿);优先级反转风险 | 实时系统、多媒体应用(音视频线程高优) |
| EDF(最早截止时间优先) | 总是选择截止时间最早的就绪进程 | 在单处理器上,是实时任务可调度的充要条件 | 需精确知道截止时间;实现复杂;多处理器扩展难 | 强实时系统(如航天器控制) |
| LLF(最低松弛度优先) | 松弛度 = 截止时间 - 剩余服务时间 - 当前时间,选最小者 | 比EDF更激进地应对紧迫任务 | 松弛度计算开销大;对时钟精度要求极高 | 超实时系统(如自动驾驶决策) |
重点参数说明:
- 时间片q:RR算法的生命线。q值选择需权衡:q太小(如1ms)→ 进程切换开销(上下文切换耗时约1-10μs)占比过高;q太大(如100ms)→ 交互响应迟钝(用户感觉卡顿)。经验值:q ≈ 交互响应时间容忍阈值(如100ms)的1/10至1/2。
- 响应比:HRRN中
(等待时间+要求服务时间)/要求服务时间,分子是“已付出成本”,分母是“预期收益”,比值越高说明“性价比”越好,体现了对长等待进程的补偿机制。- 松弛度:LLF的核心指标,代表任务“还能拖多久”。松弛度=0意味着任务已超时,=负值表示严重超时。该算法本质是动态调整优先级,让最紧迫的任务永远最高优。
3.3 死锁:当资源分配走入逻辑死胡同
死锁是调度失效的极端表现,指一组进程因循环等待资源而永久阻塞,且无外力干预无法自行解脱。其产生非偶然,而是四个必要条件同时满足的必然结果:
- 互斥条件:资源不能被多个进程同时使用(如打印机、临界区);
- 请求和保持条件:进程已持有某些资源,又申请新资源,若申请失败则阻塞,但不释放已持资源;
- 不可抢占条件:已分配给进程的资源,不能被系统强行收回(如内存、CPU);
- 循环等待条件:存在一个进程等待环,P0等待P1持有的资源,P1等待P2持有的资源,…,Pn等待P0持有的资源。
死锁防控三策:
- 预防(Prevention):破坏任一必要条件。如破坏“请求和保持”:要求进程一次性申请所有所需资源(AND型信号量);破坏“循环等待”:对所有资源类型编号,进程必须按编号递增顺序申请。代价是资源利用率下降或编程约束增强。
- 避免(Avoidance):允许前三个条件存在,但通过算法确保系统永不进入不安全状态。核心是银行家算法:
# 银行家算法核心逻辑(伪代码) def is_safe_state(available, max_need, allocation): work = available.copy() # 当前可用资源 finish = [False] * n # 每个进程是否完成 safe_sequence = [] while len(safe_sequence) < n: found = False for i in range(n): if not finish[i] and all(max_need[i][j] - allocation[i][j] <= work[j] for j in range(m)): # 进程i可完成:work += allocation[i] for j in range(m): work[j] += allocation[i][j] finish[i] = True safe_sequence.append(i) found = True break if not found: return False, [] # 不安全状态 return True, safe_sequence关键点:
max_need[i][j]是进程i对资源j的最大需求,allocation[i][j]是已分配量,need[i][j] = max_need[i][j] - allocation[i][j]是尚需量。算法模拟“如果满足进程i,它能否顺利完成并归还所有资源”,若能,则将其加入安全序列,并更新work。若遍历所有进程都无法找到可完成者,则系统处于不安全状态。- 检测与解除(Detection & Recovery):允许死锁发生,但定期运行死锁检测算法(如资源分配图化简),一旦发现死锁环,则采取措施解除。常用方法:
- 终止进程:牺牲部分进程(如优先级最低、运行时间最短者)以打破循环;
- 资源剥夺:强制从某些进程回收资源(需考虑回滚代价,如数据库事务需回滚到检查点)。
4. 存储器管理:从连续分配到虚拟内存的演进逻辑
存储器管理是操作系统资源管理的基石,其核心矛盾在于:程序逻辑地址空间的无限扩张需求与物理内存容量的有限现实之间的鸿沟。从早期的连续分配到现代的虚拟内存,每一次技术跃迁都是为弥合这一鸿沟而生。本章将梳理内存管理的演进脉络,聚焦关键机制(分页/分段)的实现细节,以及虚拟内存如何通过局部性原理实现“以小博大”。
4.1 连续分配:碎片之痛与紧凑之术
连续分配要求为每个进程分配一块连续的物理内存空间,虽实现简单,但面临严峻的外部碎片(External Fragmentation)问题:内存中存在许多小的空闲分区,总和足够大,却因不连续而无法满足一个大进程的需求。
| 分配方式 | 核心思想 | 碎片特点 | 典型算法 | 关键局限 |
|---|---|---|---|---|
| 单一连续分配 | 内存分为系统区+用户区,用户区仅一个进程 | 无外部碎片(用户区独占) | 无 | 仅支持单道程序,资源利用率极低 |
| 固定分区分配 | 将用户区预先划分为若干大小固定的分区 | 产生内部碎片(Internal Fragmentation):分区大于进程需求,剩余空间浪费 | 首次适应/循环首次适应 | 分区大小固定,无法适应大小差异大的进程 |
| 动态分区分配 | 按进程实际大小动态划分空闲区 | 产生外部碎片:空闲区分散,难以利用 | 首次适应(FF)、最佳适应(BF)、最坏适应(WF)、循环首次适应(NF) | 需维护空闲分区表/链,分配效率受算法影响 |
算法对比深度解析:
- FF(首次适应):从空闲分区链首开始查找,找到第一个满足大小的分区即分配。优势:链首大分区易被保留,利于后续大进程;劣势:链首易产生大量小碎片(“千疮百孔”)。
- BF(最佳适应):将空闲分区按大小升序排列,分配能满足的最小分区。优势:分配最精准,剩余碎片最小;劣势:产生大量难以利用的微小碎片,且排序开销大。
- WF(最坏适应):分配最大的空闲分区。优势:剩余碎片较大,仍可能满足后续进程;劣势:快速耗尽大分区,后期无法容纳大进程。
- NF(循环首次适应):从上次分配位置开始查找,避免FF的链首碎片集中。优势:碎片分布更均匀;劣势:需记录上次位置指针,实现稍复杂。
动态可重定位分区分配是解决外部碎片的终极手段——紧凑(Compaction)。其思想是:将内存中所有正在运行的进程移动到一端,使空闲分区合并成一个大块。但移动进程意味着其代码和数据的物理地址全部改变,因此必须支持动态重定位:
- 硬件支持:CPU增设重定位寄存器(Relocation Register),存放进程在内存中的起始物理地址;
- 地址变换:CPU生成的逻辑地址(相对地址) + 重定位寄存器值 = 实际物理地址;
- 紧凑操作:只需更新重定位寄存器的值,无需修改进程内部所有地址引用。
关键区别:动态分区分配(Dynamic Partitioning)仅解决“如何分”,而动态可重定位分区分配(Dynamic Relocatable Partitioning)增加了“如何移”(Compact)和“如何算”(Relocate)的能力,是迈向离散分配的过渡形态。
4.2 分页与分段:离散分配的双轨并行
为彻底解决外部碎片,操作系统转向离散分配,将进程地址空间和物理内存均划分为固定大小的块,按需分配。分页(Paging)与分段(Segmentation)是两种根本不同的离散策略,源于对“空间抽象”的不同哲学。
| 特征 | 分页(Paging) | 分段(Segmentation) |
|---|---|---|
| 划分依据 | 物理单位:为提高内存利用率而划分,大小固定(如4KB),由系统决定 | 逻辑单位:为满足用户需求而划分,大小不等(如主程序段、数据段、栈段),由用户/编译器决定 |
| 地址空间 | 一维线性:逻辑地址 = 页号 + 页内偏移量 | 二维结构化:逻辑地址 = 段号 + 段内偏移量 |
| 核心目的 | 解决外部碎片,实现高效内存管理 | 实现信息共享、保护、动态链接,符合程序员思维 |
| 地址变换 | 页表(Page Table):页号 → 物理块号 | 段表(Segment Table):段号 → 段基址 + 段限长 |
| 越界检查 | 页内偏移量 ≥ 页面大小? → 产生缺页异常 | 段内偏移量 ≥ 段限长? → 产生段错误(Segmentation Fault) |
分页地址变换详解(含快表TLB):
设页面大小为2^n字节,则逻辑地址L可拆分为:
- 页号
p = L >> n(右移n位)- 页内偏移量
d = L & (2^n - 1)(低n位掩码)无快表(TLB)时:
- CPU将页号
p送MMU;- MMU查内存中的页表,得到物理块号
f;- MMU计算物理地址
= f × 2^n + d;- 访问该物理地址内存。
→ 两次内存访问(访页表+访数据),效率低下。有快表(TLB)时:
TLB是MMU内置的高速缓存,存储热门页表项(页号→物理块号映射)。
- TLB命中:MMU直接从TLB获取
f,一步计算物理地址 →一次内存访问;- TLB未命中:MMU访问内存页表获取
f,并将该页表项载入TLB,再计算物理地址 →两次内存访问。有效访问时间(EAT)公式:
EAT = (TLB访问时间 + 内存访问时间) × 命中率 + (TLB访问时间 + 页表访问时间 + 内存访问时间) × (1 - 命中率)
通常TLB访问时间≈0,故简化为:EAT = 内存访问时间 × (1 + (1 - 命中率)) = 内存访问时间 × (2 - 命中率)
→ 命中率90%时,EAT≈1.1×内存访问时间;命中率99%时,EAT≈1.01×内存访问时间。
4.3 虚拟内存:基于局部性原理的“逻辑扩容”
虚拟内存(Virtual Memory)并非真实内存,而是操作系统提供的一种逻辑内存抽象,它让每个进程“感觉”自己拥有一个巨大、连续、私有的地址空间(如32位系统下4GB),而实际物理内存可能远小于此。其核心技术是请求分页(Demand Paging)与页面置换(Page Replacement),其可行性根基是局部性原理(Locality Principle)。
局部性原理的两大表现:
- 时间局部性:一个内存单元被访问后,短期内很可能被再次访问(如循环变量、函数调用栈帧);
- 空间局部性:一个内存单元被访问后,其附近的内存单元很可能被访问(如数组遍历、指令顺序执行)。
虚拟内存工作流程:
- 进程启动时,不将整个程序装入内存,只装入部分页面(如代码段入口、初始栈);
- CPU访问逻辑地址,MMU查页表;
- 若页表项中“存在位(P位)=0”,表示该页不在内存 →缺页中断(Page Fault);
- OS内核处理缺页:
- 若内存有空闲页框,分配之;
- 若无空闲页框,执行页面置换算法,选一页换出到外存(Swap Out);
- 从外存(如硬盘交换区)将所需页调入内存(Swap In);
- 更新页表项(P=1,块号=新页框号);
- 重新执行引发缺页的指令。
页面置换算法对比:
| 算法 | 核心思想 | 是否可实现 | 评价 |
|---|---|---|---|
| OPT(最佳置换) | 淘汰未来最长时间内不再被访问的页面 | 不可实现(需预知未来) | 理论最优,作为其他算法性能基准 |
| FIFO(先进先出) | 淘汰最早调入内存的页面 | 可实现 | 简单但性能差,存在Belady异常(增加页框数,缺页率反而上升) |
| LRU(最近最少使用) | 淘汰最久未被访问的页面 | 近似可实现(用栈/寄存器记录访问时间) | 性能接近OPT,是实际系统(如Linux)首选 |
| Clock(时钟置换) | 用环形链表模拟LRU,每个页有访问位(A位) | 可实现 | 硬件开销小,性能优于FIFO,是x86架构常用算法 |
| 改进Clock | 增加修改位(M位),优先淘汰A=0&M=0页,其次A=0&M=1页 | 可实现 | 减少I/O:A=0&M=0页既未用又未改,直接覆盖;A=0&M=1页需先写回外存 |
关键参数说明:
- 存在位(P位):页表项中1位,P=1表示页在内存,P=0表示页在外存;
- 访问位(A位):硬件自动置1,表示该页近期被访问过,供LRU/Clock算法使用;
- 修改位(M位):硬件自动置1,表示该页被写入过,换出时需写回外存;
- 外存地址:页表项中记录该页在外存(交换区)的起始盘块号,供换入时定位。
5. 易错点与易忽略点:那些让你在考场上拍大腿的细节
操作系统知识点环环相扣,一个微小的概念偏差或实现细节疏忽,就可能让整个解题逻辑崩塌。以下是根据历年真题、学生错题本及实验调试经验总结的六大高频易错/易忽略点,每一条都附有“为什么容易错”和“如何彻底规避”的实操建议。
本文还有配套的精品资源,点击获取