23级计院操作系统期末试题 A 卷解析与答案本文根据 23级计院操作系统期末.pdf 整理。试卷结构比较典型：选择题覆盖概念辨析，后面大题集中考进程互斥同步、处理机调度、银行家算法、虚拟页式存储、磁盘调度和 PV 操作。一、单项选择答案汇总：题号答案简要解析1B现代操作系统最基本的两个特征是并发和共享。2C实时系统强调在被控对象规定的时间内响应并处理事件。3D启动设备、设置中断屏蔽、设置时钟通常是特权指令；读时钟指令可作为非特权指令。4C整数除零会引发异常，read 是系统调用，都会进入内核态；sin() 一般是用户态库函数调用。5C信号量为负时，其绝对值表示等待该资源的进程数，-2 表示 2 个进程等待。6A终止进程时一定要回收自身资源并撤销 PCB，但子进程不一定被终止，也可能被托管或重新挂接。7B最优安排可为：P1 计算 0-60，I/O 60-140；P2 计算 60-180；P1 计算 180-200；P2 I/O 180-220，计算 220-260，共 260ms。8A两程序并发时总时间至少不能小于最长单个程序 30s，所以 20s 不可能。9A中断或异常发生前 CPU 可能处于用户态；进入中断/异常处理程序后才处于内核态。10D时钟中断、控制台中断来自 CPU 外部；访管中断和缺页中断属于内部中断/异常。11B严格说应是每类中断类型有一个中断向量，而不是每一次具体发生的中断事件都有独立向量。若课堂将“事件”理解为“事件类型”，本题表述有歧义。12C为每个应用单独配一个键盘驱动线程不是多线程系统的典型优势。13B线程提高进程内部并发度，有助于提高系统效率；线程不能脱离进程独立存在。14D有序资源分配通过规定资源申请顺序破坏循环等待条件。15C管程中的 signal 是对条件变量的唤醒，不等同于信号量的 V 操作。16D“根据 R 找到消息接收者”本质是找到接收进程的 PCB，再取得其接收缓冲地址等信息。17B最短寻找时间优先可能长期偏向近处请求，远处请求可能饥饿。18C分页管理的主要特点是作业不必装入主存连续区域。19A界地址管理只需基址/限长等简单硬件支持，代价最低。20DCPU 利用率低而交换磁盘利用率极高，是颠簸迹象；增加物理内存能改善。21A通道是独立于 CPU、专门负责 I/O 传输的处理单元。22A缓冲主要为缓和速度不匹配、减少中断和 I/O 次数；设备独立性不是引入缓冲的主要原因。23D工作集大小是指定窗口内访问过的不同页面数。24A每个索引块含 1KB/4B=256 个盘块号，两级索引可寻址 256*256 个数据块，即 64MB。25A删除文件会删目录项、文件控制块并释放相关缓冲/空间，但不应删除该文件所在目录本身。26C进程因 I/O 等待时处理机可被其他进程抢占，但它仍占有相关临界资源，其他进程不能进入相关临界区。27B用户 I/O 请求流程：用户程序 -> 系统调用处理程序 -> 设备驱动程序 -> 中断处理程序。28B缓冲解决传输速度不匹配；缓存提高重复访问速度。29BSPOOLing 的核心是把独占设备的操作转移到高速共享设备如磁盘上排队完成。30C虚拟段页式兼有段、页优点，但实现复杂、系统开销大。二、简答题1. 死锁与饿死的相同点和不同点相同点：都会导致某些进程长期不能向前推进。都与资源分配、调度策略或同步机制有关。都可能表现为进程长时间等待资源或处理机。不同点：死锁是多个进程形成相互等待关系，例如 P1 等 P2 的资源，P2 又等 P1 的资源，通常需要外部干预才能打破。饿死是某个进程长期得不到资源或处理机，原因常是调度/分配策略不公平，例如高优先级进程不断到来、SSTF 长期服务近处磁道。死锁进程集合中的进程通常都处于阻塞等待状态；饿死进程未必一直阻塞，也可能反复进入就绪队列但总是选不上。死锁强调循环等待等 Coffman 条件；饿死强调等待时间无上界和公平性不足。2. 磁头粘性“磁头粘性”指磁盘调度中，如果新请求不断加入当前扫描队列，尤其集中在当前磁头附近或当前扫描方向上，磁头可能不断服务这些新来的近处请求，迟迟离不开某一区域，使远处请求长时间等待。如果每个磁道各有一个磁头，则不存在移动磁头在磁道之间来回移动的问题，也就不存在原意义上的“磁头粘性”。仍可能有请求队列公平性问题，但不是由移动磁头调度造成的粘性。3. test_and_set 算法的有限等待问题与改进原算法：do { while (test_and_set(&lock)) continue; 临界区; lock = 0; 其余部分; } while (1);不满足有限等待的原因：当某进程离开临界区并把 lock 置 0 后，所有等待进程和新来的进程同时竞争 test_and_set。某个等待已久的进程可能反复竞争失败，被其他进程一次次抢先进入临界区，因此等待次数没有上界。可用等待数组改进：boolean waiting[n] = { false }; boolean lock = false; do { waiting[i] = true; boolean key = true; while (waiting[i] && key) { key = test_and_set(&lock); } waiting[i] = false; /* 临界区 */ int j = (i + 1) % n; while (j != i && !waiting[j]) { j = (j + 1) % n; } if (j == i) { lock = false; } else { waiting[j] = false; } /* 其余部分 */ } while (true);解释：退出临界区时不是让所有进程无序抢锁，而是按循环顺序把进入权交给下一个正在等待的进程。这样每个等待进程至多被前面的有限个进程越过，满足有限等待。三、处理机调度进程表：进程到达时间处理时间P1010P236P345P475采用最短剩余时间优先。若剩余时间相同，保持当前进程或按先到达者优先。Gantt 图：0 3 9 14 19 26 | P1 | P2 | P3 | P4 | P1 |计算表：进程到达时间服务时间完成时间周转时间等待时间带权周转时间P10102626162.6P2369601.0P345141052.0P475191272.4所以：平均等待时间：(16+0+5+7)/4 = 7ms平均周转时间：(26+6+10+12)/4 = 13.5ms平均带权周转时间：(2.6+1.0+2.0+2.4)/4 = 2.0四、死锁问题题面表格中 P0 的 D 类资源有一处不一致：P0 的 Allocation(D)=2，但 Max(D)=0，这违反银行家算法要求的 Max >= Allocation。下面先按题面数字计算；如果把 P0 的 Max(D) 修正为 2，则只有 P0 的 Need 中 D 项由 -2 改为 0，安全性结论不变。当前 Available：Available = (1, 5, 2, 0)Allocation：进程ABCDP00012P11000P20354P30011Max：进程ABCDP01320P11756P22356P306561. 等待进程与资源总量以 Need <= Available 判断当前能否继续完成：P0 的 Need 可由当前 Available 满足。P1、P2、P3 当前 Need 不能由 Available 满足，可认为处于等待状态。系统资源总量：Total = Available + sum(Allocation) = (1,5,2,0) + (1,3,7,7) = (2,8,9,7)2. Need 矩阵Need = Max - Allocation：进程ABCDP0131-2P10756P22002P30645若修正 P0 的 Max(D)=2，则 P0 的 Need 为 (1,3,1,0)。3. 安全性初始：Work = Available = (1,5,2,0)P0 可完成，释放其 Allocation：Work = (1,5,2,0) + (0,0,1,2) = (1,5,3,2)此时：P1 Need (0,7,5,6)，B、C、D 不满足。P2 Need (2,0,0,2)，A 不满足。P3 Need (0,6,4,5)，B、C、D 不满足。因此不能找到完整安全序列，系统不处于安全状态。只能得到部分序列 P0，不存在完整安全序列。4. P1 请求 (0,4,2,0) 能否满足先检查：Request1 = (0,4,2,0) Need1 = (0,7,5,6) 满足 Request <= Need Available= (1,5,2,0) 满足 Request <= Available试分配后：Available' = (1,1,0,0) Need1' = (0,3,3,6)用 Work=(1,1,0,0) 做安全性检查，没有任何进程的 Need 能被满足。因此试分配后系统不安全，请求不能被满足，应拒绝或让 P1 等待。五、存储管理页框大小 4KB，所以页内位移为低 12 位。访问逻辑地址对应页号：逻辑地址页号页内位移2582H2582H3D8CH3D8CH9E37H9E37H18F1H18F1H61A3H61A3H8AC2H8AC2H本解按如下口径处理：页框按需分配；同一次缺页若要同时调入当前页和下一页，则作为一次连续页框申请，用最佳适应算法选择空闲块；进程最多占 6 个页框；超过 6 页时按局部 LRU 淘汰；预调入但未真正访问的页访问位为 0。初始空闲页框表：首页框号页框个数3176018196333521......访问过程：访问页 2，页 3 不在内存，同时调入页 2、页 3。申请 2 个页框，最佳适应选 (196,3)，分配 196、197，剩余 (198,1)。访问页 3，命中，访问位置 1。访问页 9，页 10 不在内存，同时调入页 9、页 10。申请 2 个页框，最佳适应选 (31,7)，分配 31、32，剩余 (33,5)。访问页 1，下一页 2 已在内存，只调入页 1。申请 1 个页框，最佳适应选 (198,1)，分配 198。访问页 6，页 7 不在内存，应调入页 6、页 7。此时已有 5 页，先从 (33,5) 分配页框 33 给页 6；再调页 7 时已达 6 页上限，按 LRU 淘汰预调入且未被访问的页 10，用其页框 32 装入页 7。1. 访问前五个地址后的页表这里约定内外标志 1 表示在内存，0 表示在外存。逻辑页号页框号访问位内外标志119811219611319711633117320193111其他页未在内存中，可记为：页框号 = -, 访问位 = 0, 内外标志 = 0其中页 10 曾被预调入，但后来被页 7 置换出去。2. 空闲页框表分配后空闲页框表为：首页框号页框个数344601833521......说明：页 10 被淘汰时，其页框 32 被页 7 直接复用，不回到系统空闲页框表。3. 继续访问 8AC2H 后的页表与物理地址8AC2H 的页号是 8，页内位移是 AC2H。访问页 8 时，下一逻辑页 9 已经在内存，所以只调入页 8。进程已满 6 页，按 LRU 淘汰预调入但未访问的页 7，将页 8 装入页框 32。访问后的页表：逻辑页号页框号访问位内外标志119811219611319711633118321193111逻辑地址 8AC2H 的物理地址：页框号 = 32 = 20H 页内位移 = AC2H 物理地址 = 32 * 1000H + AC2H = 20AC2H六、磁盘引臂调度算法磁道由外向内编号为 0、1、2、...、249，所以“向外移动”表示磁道号变小。N-Step LOOK，N=4，将请求序列分成：Q1: 90, 15, 200, 50 Q2: 10, 180, 70, 240 Q3: 120, 30当前磁头在 100，向外即向小磁道号移动。调度过程：Q1：从 100 向外服务 90、50、15，再反向服务 200。Q2：上一批结束时方向为向内，从 200 向内服务 240，再反向服务 180、70、10。Q3：上一批结束时方向为向外，从 10 向外无请求，于是反向服务 30、120。调度序列：90, 50, 15, 200, 240, 180, 70, 10, 30, 120磁头移动量：|100-90| + |90-50| + |50-15| + |15-200| + |200-240| + |240-180| + |180-70| + |70-10| + |10-30| + |30-120| = 10 + 40 + 35 + 185 + 40 + 60 + 110 + 60 + 20 + 90 = 650 个磁道时间计算：总寻道时间：650 * 0.8ms = 520ms磁盘转速：6000r/min = 100r/s，一圈 10ms平均旋转延迟：半圈 5ms，10 次请求共 50ms每道 60 个扇区，每次访问 1 个扇区，单扇区传输时间 10ms/60 = 1/6ms总传输时间：10 * 1/6ms = 1.67ms总访问处理时间：520 + 50 + 1.67 = 571.67ms七、信号量与 PV 操作同步关系分析关键约束：盘子最多一个水果。爸爸只有在孩子请求时才放对应水果。放水果或取水果的人必须先获得一个座位。如果孩子发现盘子为空，要叫醒爸爸；等待爸爸放水果时不应占着座位，否则两个孩子可能占满 2 个座位，爸爸无法坐下放水果而死锁。同一时刻只允许一个孩子发出并等待一个水果请求，避免爸爸收到多个请求而盘子只能放一个水果。信号量设置semaphore seat = 2; // 两个座位 semaphore order = 1; // 一次只处理一个孩子的水果请求 semaphore request = 0; // 孩子叫醒爸爸 semaphore appleReady = 0; // 苹果已放好 semaphore orangeReady = 0; // 橘子已放好 semaphore mutex = 1; // 保护 plate 和 want enum Fruit { EMPTY, APPLE, ORANGE }; Fruit plate = EMPTY; Fruit want = EMPTY;爸爸进程Father() { while (true) { /* 空闲时在沙发上睡觉 */ P(request); // 被孩子叫醒 P(mutex); Fruit f = want; V(mutex); P(seat); // 放水果前先坐到座位上 P(mutex); plate = f; V(mutex); V(seat); if (f == APPLE) { V(appleReady); } else { V(orangeReady); } } }儿子进程Son() { while (true) { P(order); P(seat); P(mutex); if (plate == APPLE) { plate = EMPTY; V(mutex); V(seat); V(order); eat_apple(); } else if (plate == EMPTY) { want = APPLE; V(mutex); V(seat); // 等爸爸时不能占座位 V(request); // 叫醒爸爸 P(appleReady); // 等苹果放好 P(seat); // 取水果前先坐到座位上 P(mutex); plate = EMPTY; V(mutex); V(seat); V(order); eat_apple(); } else { /* 盘中是橘子，儿子不能取，释放机会让女儿处理 */ V(mutex); V(seat); V(order); } } }女儿进程Daughter() { while (true) { P(order); P(seat); P(mutex); if (plate == ORANGE) { plate = EMPTY; V(mutex); V(seat); V(order); eat_orange(); } else if (plate == EMPTY) { want = ORANGE; V(mutex); V(seat); // 等爸爸时不能占座位 V(request); // 叫醒爸爸 P(orangeReady); // 等橘子放好 P(seat); // 取水果前先坐到座位上 P(mutex); plate = EMPTY; V(mutex); V(seat); V(order); eat_orange(); } else { /* 盘中是苹果，女儿不能取，释放机会让儿子处理 */ V(mutex); V(seat); V(order); } } }正确性与饥饿分析死锁不会发生：孩子在等待爸爸放水果前释放座位，所以爸爸总能获得座位放水果。order 保证同一时刻只有一个孩子提出并等待请求，盘子容量为 1 不会被多个请求破坏。爸爸放完对应水果后必定唤醒对应孩子，孩子取走水果后释放 order。饥饿问题：若信号量队列和处理机调度是公平的，例如 FIFO 信号量，则不会由该同步算法本身导致饥饿。每个获得 order 的请求都会被爸爸服务，完成后下一个等待者再进入。若使用弱信号量且调度不公平，理论上仍可能饥饿。例如儿子总能抢先获得 order，女儿可能长期得不到请求机会。要彻底避免这种饥饿，可将 order 和 seat 实现为 FIFO 信号量，或额外设置公平排队/轮转机制。
本文页码均指课件页码操作系统概述一、核心主线操作系统是位于硬件之上、其他软件之下的系统软件，它负责管理计算机软硬件资源，并为用户和应用程序提供方便、安全、高效的服务。也就是说，操作系统既是：资源管理者：管理 CPU、内存、设备、文件等资源。 用户接口：提供命令行、图形界面、系统调用等使用方式。 虚拟机：把复杂硬件包装成更易用的逻辑资源，比如虚拟 CPU、虚拟内存、虚拟设备。 控制程序：通过中断、调度、保护机制控制程序运行。二、操作系统的概念1. 操作系统的地位课件中强调：操作系统位于 硬件层 HAL 之上，位于 其他系统软件和应用软件之下。可以理解成：应用软件 系统软件 / 系统库 操作系统 硬件 / HAL应用程序通常不会直接操作硬件，而是通过 库函数 或 系统调用 请求操作系统完成底层工作。例如：printf("Hello");表面上是 C 语言函数，实际上最终要通过操作系统完成显示设备或终端输出。2. 操作系统的作用主要有两类：第一，管理资源：资源操作系统做什么CPU调度多个程序运行内存分配、保护、共享、扩充设备分配设备、驱动设备、管理 I/O文件组织文件系统，支持读写、目录、权限第二，提供服务接口：包括 API、GUI、命令行、作业控制语言 JCL 等。3. 操作系统定义课件中的定义可以背下来：操作系统是位于硬件层之上、所有其他软件层之下的系统软件，通过它管理系统中各种软硬件资源，使它们被充分利用，并方便用户使用计算机系统。考试中如果问“什么是操作系统”，答这个定义基本够用。三、操作系统的发展历史这一部分容易考“发展阶段”和“各阶段特点”。1. 手工操作阶段时间大约是 20 世纪 40 年代，无操作系统。特点：程序员手工操作机器； 使用纸带、卡片、开关、接线板； CPU 经常等待人操作，效率极低； 资源被一个作业独占。重点理解：机器越来越快后，人工操作时间反而成了主要瓶颈。2. 批处理阶段为了解决人工干预太多的问题，出现批处理。分为：联机批处理：作业从读卡机进入计算机，按批处理执行。优点是减少人工干预，缺点是 I/O 慢，主机等待时间长。脱机批处理：使用卫星机完成输入输出转换，主机专心计算。优点是减少主机等待 I/O，缺点是需要人工搬磁带和额外设备。3. 执行系统阶段出现了 通道 和 中断 技术。通道可以理解为专门负责 I/O 的处理机，它接受 CPU 委托执行 I/O 操作。这样 CPU 和 I/O 可以并行工作。这一阶段出现了 SPOOLing 假脱机技术。4. 多道批处理系统这是操作系统走向成熟的重要标志。核心思想：内存中同时放入多个作业，当一个作业等待 I/O 时，CPU 可以去执行另一个作业。优点：提高 CPU 利用率； 提高系统吞吐量； 适合大型科学计算任务。但它也带来了一系列新问题：互斥、同步、通信、死锁、饥饿等。这些正是后面几章重点内容。5. 分时系统分时系统面向交互式用户。特点：多用户通过终端连接主机； 系统轮流给每个用户分配 CPU 时间片； 用户感觉自己独占一台机器。典型系统：CTSS、Multics、UNIX。6. 实时系统实时系统强调 及时响应 和 高可靠性。应用场景：工业控制、军事控制、医疗控制、航班订票、联机检索等。重点：实时系统不是单纯追求“快”，而是要求 在规定时间内完成响应。7. 通用操作系统通用操作系统把批处理、分时、实时等功能结合起来。常见模式：前台分时 + 后台批处理； 前台实时 + 后台批处理。四、操作系统的四大特性这部分常考简答题。1. 并发性多个程序在宏观上同时向前推进。注意区分：概念含义并发宏观同时，微观交替并行真正同时，需要多个处理器或多个核心单 CPU 上可以有并发，但不能真正并行执行多个程序。2. 共享性多个程序共同使用系统资源。例如：多个程序共享 CPU； 多个进程共享内存中的某些区域； 多个用户共享打印机； 多个进程共享文件。共享必须由操作系统协调，否则会产生冲突。3. 异步性程序推进速度不可预知。原因是：多个程序交替运行； 中断随机发生； 调度时机不确定。所以程序执行顺序并不完全由程序员控制，而受中断、调度、资源状态影响。4. 虚拟性通过某种技术把一个物理实体变成多个逻辑实体。例子：物理实体虚拟效果一个 CPU多个虚拟 CPU内存 + 外存更大的虚拟存储空间独占设备可共享的虚拟设备这一点非常重要，后面虚拟存储、虚拟设备都会继续展开。五、操作系统的主要类型1. 多道批处理系统关键词：多道、成批、吞吐量、资源利用率。适合非交互、大批量计算任务。重点概念：SPOOLing。SPOOLing 又叫假脱机技术或排队转储技术，由输入井、输出井和常驻 I/O 进程组成。它可以把独占设备改造成共享设备，实现虚拟设备功能。2. 分时操作系统关键词：多路性、交互性、独占性。多路性：一个主机连接多个终端。 交互性：用户可以和系统对话。 独占性：每个用户感觉自己独占一台虚拟机。3. 实时操作系统关键词：响应及时、可靠性高。实时控制系统中，课件用 t2 - t1 表示响应时间。4. 网络操作系统网络操作系统用于实现网络通信和网络资源管理。目标：相互通信； 资源共享； 提供网络服务，如数据库服务、FTP、邮件、Telnet 等。注意：网络操作系统通常没有完全透明的统一视图。5. 分布式操作系统分布式操作系统强调多个计算机在统一操作系统管理下协同工作。特点：统一操作系统； 资源进一步共享； 可靠性高； 透明性强。目标：资源共享、负载均衡、计算加速。常见技术：作业迁移、进程迁移。6. 多处理机操作系统面向具有公共内存的多 CPU 系统。重点概念：SMP，对称多处理机系统； 多个 CPU 没有主从关系； 进程和 CPU 是多对多关系。新问题：调度问题； 并发控制问题。7. 嵌入式操作系统应用于掌上设备、车载系统、通信设备、信息家电等。特点：微内核结构； 核心小； 可裁剪性好； 可移植性好； 可扩展性好； 可靠性高，但效率可能较低。六、操作系统运行环境这一节讲“操作系统为什么能控制程序”。1. 定时装置分为：绝对时钟：记录实际时间，一般不发中断。 间隔时钟：定期发中断，是实现多道程序的重要基础。考试重点：间隔时钟保证操作系统能定期获得 CPU 控制权。如果没有时钟中断，一个死循环程序可能一直占用 CPU。2. 堆和栈每个运行程序通常有：一个堆； 一个用户栈； 一个系统栈。结构作用堆保存动态变量，如动态分配的对象用户栈保存用户函数调用信息、参数、局部变量系统栈 / 核心栈保存中断现场、内核调用信息注意：用户栈属于用户空间； 系统栈属于操作系统空间； 进程切换时，系统栈也要切换。3. 寄存器程序切换时，需要保存和恢复寄存器内容。重要寄存器包括：PSW 程序状态字； PC 指令计数器； SP 栈指针； 通用寄存器； 浮点寄存器； 地址映射寄存器。4. 特权指令与非特权指令特权指令：只能在系统态执行，会影响系统状态。 例如：开关中断、设置 PSW、停机、I/O 指令等。非特权指令：用户态和系统态都可以执行。 例如：取数、四则运算等。5. 处理机状态有两种基本状态：状态又称运行什么系统态管态、核态操作系统内核用户态目态、常态普通应用程序状态转换：系统态 → 用户态：通过设置 PSW 等特权指令。 用户态 → 系统态：通过中断、trap、系统调用。6. 地址映射与存储保护地址映射：逻辑地址 → 物理地址逻辑地址是程序中产生的地址； 物理地址是真实内存地址。存储保护用于防止：应用程序侵犯操作系统空间； 应用程序侵犯其他用户空间； 非法访问共享区域。主要检查：越界检查； 越权检查。7. 中断装置中断是操作系统获得控制权的关键机制。发生中断时，大致过程是：保存当前 PSW 和 PC； 根据中断向量找到处理程序入口； 转入操作系统执行中断处理程序。8. 通道与 DMA二者都能实现 I/O 设备和内存之间的数据直传。区别：项目通道DMA是否有独立指令系统有没有控制能力强较弱控制设备数量可控制多台不同设备通常控制一台或少数同类设备是否可编程可编程不可编程或能力有限七、操作系统的界面形式1. 命令行接口例如 UNIX shell：rm -i *.c其中：rm 是命令名； -i 是选项； *.c 是参数。Shell 的优点：缩小操作系统核心； 不同用户可以选择不同界面； Shell 本身通常是用户态进程。2. GUI 图形用户界面由窗口、图标、菜单、按钮、鼠标等组成。本质上仍然是交互式接口，只是从命令输入变成图形操作。3. 作业控制语言 JCL主要用于批处理系统。用户通过 JCL 告诉操作系统：需要什么资源； 如何控制作业执行； 作业如何输入输出。4. 系统调用 OS API系统调用是应用程序请求操作系统内核服务的方式。常见系统调用类型：文件相关； 进程相关； 进程通信相关； 资源管理相关。系统调用过程可以概括为：用户程序执行系统调用； 通过陷入指令进入内核； 保存现场； 查系统调用入口表； 执行对应内核服务； 返回用户程序。八、操作系统运行机理课件中用“程序 1、程序 2、中断、程序切换”说明 OS 的运行机制。核心过程：程序正常运行； 发生中断； CPU 转入操作系统； 操作系统保存当前程序现场； 选择另一个程序运行； 恢复新程序现场； 返回用户态继续执行。这就是进程切换、调度、中断机制的基础。九、研究操作系统的三种观点1. 进程观点把操作系统看成：若干可以独立运行的程序； 一个协调这些程序的核心。重点问题：互斥； 同步； 通信； 死锁； 饥饿。2. 资源管理观点操作系统是资源管理者。它要做三件事：描述资源状态； 决定资源分配策略； 提供申请和释放资源的机制。3. 虚拟机观点操作系统把硬件包装成更容易使用的虚拟机器。例如：单个 CPU → 多个虚拟 CPU； 内存 + 外存 → 虚拟存储； 独占设备 → 虚拟设备。十、操作系统的基本功能课件最后归纳了四大功能：功能内容存储管理分配、共享、保护、扩充、地址映射处理器管理进程控制、同步互斥、通信、调度文件管理文件访问、组织、目录、读写、权限设备管理设备分配、缓冲、驱动、独立性、虚拟设备、调度这张表很适合背诵，后续章节基本就是围绕这几类功能展开。十一、易考知识点操作系统定义、地位和作用 会答“OS 是什么”“为什么需要 OS”。并发、共享、异步、虚拟四大特性 尤其要区分并发和并行。多道批处理、分时、实时系统的区别 这是常见简答题和选择题。SPOOLing 技术 要会解释输入井、输出井、虚拟设备、缓冲作用。系统态和用户态 要知道为什么要区分，以及如何转换。特权指令和非特权指令 要能举例说明。中断机制 中断是 OS 获得控制权、实现调度和系统调用的重要基础。系统调用过程 用户程序不能直接操作内核资源，必须通过系统调用进入内核。通道和 DMA 的区别 通道功能更强，有指令系统；DMA 更简单。OS 的四大管理功能 存储管理、处理器管理、文件管理、设备管理。十二、用课件中的 Hello 程序理解整章课件最后用 Hello 程序提问，适合串联整章：main() { char *H = "Hello"; printf("%s", H); while(TRUE) { int i = 100; } }可以这样理解：Hello.exe 如何存放？ 由文件系统负责，涉及文件管理。Hello.exe 如何启动？ 由操作系统装入程序、创建进程、分配资源。如何分配内存？ 由存储管理负责，涉及地址映射、内存分配、保护。printf 如何输出字符串？ 通过库函数进一步调用系统调用，由设备管理和 I/O 系统完成输出。while 死循环会不会独占 CPU？ 正常不会。因为间隔时钟会周期性中断，操作系统重新获得控制权并进行调度。程序结束如何退出系统？ 通过系统调用通知操作系统回收资源、关闭文件、释放内存。这道题本质上考的是：一个应用程序从存储、装入、运行、输出、调度到退出，全程都离不开操作系统。十三、小结第一章是操作系统全书的地图。后面章节只是把 CPU、内存、文件、设备、进程这些模块展开讲。进程、线程与作业总体框架这一章主要讲四部分：部分核心问题2.1 多道程序设计为什么一个系统要同时放多个程序运行？2.2 进程的引入操作系统如何描述、管理、切换正在运行的程序？2.3 线程与轻进程为什么进程还不够轻便？线程解决了什么问题？2.4 作业用户提交的任务如何被系统组织和执行？你可以把它们理解成三个层次：作业：用户要求计算机完成的一整套任务 ↓ 进程：作业进入内存后，程序的一次执行活动 ↓ 线程：进程内部更小的执行单位2.1 多道程序设计1. 单道程序设计的缺点单道程序设计指系统中一次只允许一个程序运行。它最大的问题是：资源利用率低。因为程序运行时并不是一直使用 CPU。很多时候程序在等待磁盘、打印机、磁带等 I/O 设备。例如：程序A： CPU计算 → 等待设备1 → CPU计算 → 等待设备2 → CPU计算当程序 A 等待 I/O 时，CPU 可能空闲；当 CPU 工作时，设备可能空闲。所以单道程序设计会造成：CPU等设备，设备等CPU，大家都不能充分利用。2. 多道程序设计的提出多道程序设计是指：多个程序同时进入系统，并发地投入执行。注意这里的“同时”不一定是真正同时。单 CPU 下通常是：程序A运行一会儿 → 程序B运行一会儿 → 程序A继续运行 → 程序C运行……这叫并发执行。多道程序设计能提高：资源效果CPU程序 A 等 I/O 时，让程序 B 使用 CPUI/O 设备CPU 执行程序 B 时，设备可以为程序 A 工作内存同时保存多个程序，提高系统吞吐量本章中给出的例子说明： 单道方式下 CPU 利用率为 50%，多道方式下 CPU 利用率可提高到 89%。计算公式是：CPU利用率 = CPU实际占用时间 / 程序总运行时间考试里常考这种题：给出多个程序的 CPU、I/O 时间段，让你画时序图并计算 CPU 利用率。3. 多道程序设计的问题多道程序设计虽然提高效率，但也带来管理问题。主要有三类：问题具体内容处理器管理程序数量多于 CPU 数量，CPU 分给谁？存储管理多个程序如何共享内存？地址空间如何隔离？设备管理多个程序同时请求同一设备，如何分配？所以，多道程序设计引出了一个重要概念：操作系统必须有一种机制来描述和管理正在执行的程序，这就是进程。2.2 进程的引入1. 为什么需要进程？在多道程序系统中，程序不是从头到尾连续执行的，而是：推进 → 暂停 → 推进 → 暂停 → ……暂停的原因可能是：自己主动等待资源，比如等待 I/O；被操作系统剥夺 CPU，让其他程序运行。因此，操作系统必须能做到：暂停时：保存现场 继续时：恢复现场所谓“现场”，主要包括：内容含义PC程序计数器，记录下一条要执行的指令PSW程序状态字寄存器保存当前运算状态栈指针 SP指向当前栈位置为了保存和管理这些信息，操作系统引入了进程。2. 进程的概念本章给出多个定义，本质上都指向一句话：进程是程序的一次执行过程。也可以说：进程是具有一定独立功能的程序，关于某个数据集合的一次运行活动。考试中最重要的是抓住两个关键词：关键词含义动态性进程是正在执行的程序并发性进程可以和其他进程交替执行所以：程序 = 静态的代码 进程 = 程序运行起来之后的动态活动比如：QQ.exe 是程序 打开一个 QQ 后产生的运行实例是进程 同时打开两个 QQ，可能对应两个进程3. 进程的三种基本状态进程有三种基本状态：状态英文含义运行态Running正在占用 CPU 执行就绪态Ready已经具备运行条件，但还没获得 CPU等待态Waiting / Blocked等待某个事件发生，暂时不能运行状态转换关系非常重要：就绪态 --获得CPU--> 运行态 运行态 --时间片用完/被剥夺CPU--> 就绪态 运行态 --等待I/O或资源--> 等待态 等待态 --事件发生--> 就绪态注意：等待态不能直接变成运行态。它必须先变成就绪态，再等待调度。考试常考判断题：某进程等待的事件发生后，它一定立即运行吗？答案：不一定。 事件发生后，它只是从等待态进入就绪态，是否运行还要看 CPU 调度。4. 进程控制块 PCBPCB 是本章最重要概念之一。PCB，Process Control Block，进程控制块。定义：PCB 是标志进程存在的数据结构，其中保存操作系统管理进程所需的全部信息。可以记成一句话：PCB 是进程在操作系统中的“身份证 + 档案袋”。PCB 中通常包括：内容作用进程标识 pid唯一标识进程进程状态运行、就绪、等待等现场信息PC、PSW、寄存器等调度参数优先级、时间片等用户标识 uid属于哪个用户地址信息程序和数据在内存中的位置打开文件进程正在使用的文件队列指针挂入就绪队列或等待队列重点结论：系统创建进程时创建 PCB，撤销进程时撤销 PCB。所以也可以说：PCB 是进程存在的唯一标志。5. 进程的组成一个进程通常由三部分组成：进程 = PCB + 程序代码 + 数据/堆栈具体来说：部分内容PCB操作系统管理进程所需的信息程序代码实现功能的指令数据静态数据、堆、栈其中：区域作用栈 stack保存函数参数、返回地址、局部变量等堆 heap保存动态分配的变量本章还提到：进程的程序代码和数据称为进程影像 Process Image。6. 进程上下文与上下文切换进程上下文可以理解为：让一个进程能够继续运行所需的全部环境。包括：PCB + 程序代码 + 数据 + 地址空间 + 系统栈 + 打开文件表等上下文切换是指：CPU 从一个进程切换到另一个进程时，保存当前进程现场，恢复另一个进程现场。上下文切换是有成本的。这个成本叫：系统开销 system overhead所以多道程序并不是越多越好。 程序太少，资源利用率低；程序太多，上下文切换和管理开销增加，响应速度下降。7. 进程队列操作系统通常用 PCB 组成各种队列。主要有三类：队列含义就绪队列所有等待 CPU 的进程等待队列等待某个事件或资源的进程运行队列每个处理器上正在运行的进程一个很重要的模型是：创建 → 就绪队列 → CPU运行 → 完成 ↓ 等待事件 ↓ 等待队列 ↓ 事件发生 ↓ 回到就绪队列这个图是考试高频内容。8. 进程的类型与特征进程分为：类型含义系统进程执行操作系统程序，完成系统管理功能用户进程执行用户程序，为用户服务进程的特征有六个：特征含义并发性可以与其他进程一起推进动态性有创建、运行、等待、结束等变化独立性是可以被调度的基本单位交互性进程之间可能相互影响异步性各进程以不可预知的速度推进结构性每个进程都有 PCB这里容易考选择题或简答题。9. 进程间关系与相互作用进程之间有两种关系：类型含义相关进程有逻辑关系，如父子进程、合作进程无关进程没有逻辑关系，但可能竞争资源进程间相互作用分为：类型含义直接相互作用相关进程之间直接通信、同步间接相互作用通过资源竞争产生影响典型问题有：互斥、同步、死锁、饥饿这些内容通常会在后续章节继续展开。10. 进程的创建与撤销进程创建过程一般包括：申请一个空闲 PCB；分配唯一进程标识 pid；为新进程分配资源；初始化 PCB；加载程序；将 PCB 插入就绪队列。引起进程创建的事件包括：事件例子用户登录登录系统后创建用户进程作业调度批处理作业被调入内存提供服务系统创建服务进程应用请求程序主动创建子进程进程撤销过程一般包括：找到被撤销进程的 PCB；如果正在运行，立即停止；设置重新调度标志；撤销其子孙进程；回收资源；回收 PCB。引起进程撤销的事件：类型含义正常结束程序执行完毕异常结束越界、非法指令等错误外界干预被用户或父进程 kill11. 进程等待与唤醒进程等待当运行进程不能继续执行时，会进入等待态。过程是：停止当前进程执行 保存现场到 PCB 状态改为等待态 进入等待队列引起等待的原因包括：原因例子请求系统服务资源暂时无法满足启动 I/O 操作等待读写完成新数据尚未到达生产者-消费者问题无新工作可做系统服务进程等待任务进程唤醒等待事件发生后，操作系统会唤醒进程。过程是：从等待队列移出 状态改为就绪态 插入就绪队列注意：唤醒 ≠ 立即运行唤醒只是进入就绪态。12. 父进程创建子进程 vs 主程序调用子程序这是一个很容易考的比较题。比较父进程创建子进程主程序调用子程序执行关系父子进程可并发执行主程序暂停，子程序执行是否独立子进程是独立进程子程序不是独立进程是否有 PCB子进程有自己的 PCB子程序没有 PCB返回关系父子进程可独立结束子程序返回后主程序继续一句话：创建子进程是并发关系，调用子程序是顺序关系。13. UNIX 进程相关系统调用本章重点讲了几个 UNIX 系统调用：系统调用作用fork()创建子进程execl()装入并执行新程序exit()进程自我终止wait()父进程等待子进程结束kill外部终止进程fork()pid = fork();返回值很重要：返回位置返回值父进程子进程的 pid，正整数子进程0出错-1典型代码：pid = fork(); if (pid == 0) { // 子进程执行 } else { // 父进程执行 }execl()execl("P", 0);作用是：用新程序 P 覆盖原来的程序，从 P 的第一条指令开始执行。wait()父进程等待子进程结束：pid = wait(&status);exit()子进程自我终止：exit(status);14. fork 的问题与 vforkfork 的功能：复制地址空间 code + data + stack 复制控制结构 proc + user问题是：父子进程数据各自独立，不方便共享；如果子进程马上 exec 新程序，那么刚刚复制的地址空间马上被覆盖，浪费时间和空间。所以引入了 vfork：比较forkvfork是否复制地址空间复制不复制父子进程地址空间独立共享适合场景创建相似子进程子进程马上 exec15. 进程与程序的联系和区别这是考试必背内容。比较程序进程本质静态代码程序的一次执行状态静态动态生命周期可长期保存有创建和终止并发性本身不能并发可以并发执行组成代码和数据PCB + 代码 + 数据 + 堆栈对应关系一个程序可对应多个进程一个进程通常执行一个程序一句话记忆：程序是菜谱，进程是按菜谱做菜的过程。2.3 线程与轻进程1. 为什么引入线程？进程虽然解决了多道程序管理问题，但进程切换开销大。因为进程上下文包括：PCB + 地址空间 + 系统栈 + 打开文件表 + 程序代码 + 数据切换一个进程，需要保存和恢复大量信息。而且相关进程之间共享数据不方便，因为它们通常拥有独立地址空间。所以引入线程。2. 线程的概念线程是：进程中一个相对独立的执行流。核心区别：进程是资源分配单位 线程是执行单位也就是说：概念作用进程拥有资源，如地址空间、文件、数据区线程使用 CPU 执行指令一个进程至少有一个线程，也可以有多个线程。3. 多线程的优点多线程的优点包括：优点原因切换速度快同一进程内线程共享地址空间系统开销小不需要切换整个进程环境通信方便线程共享进程的数据空间并发性好一个线程等待，其他线程可以继续执行适合多处理器多线程可真正并行运行比如 Word 可以有多个线程：线程1：编辑输入 线程2：拼写检查 线程3：自动保存在线视频播放器也可以有多个线程：线程1：视频解码 线程2：音频解码 线程3：网络接收 线程4：界面控制4. 线程结构多线程进程中，线程共享：代码段 数据段 堆 打开文件 地址空间每个线程独有：线程 ID 寄存器 PC 栈 线程状态可以记成：同一进程的线程：共享资源，各自执行。5. 线程控制块 TCBTCB 是 Thread Control Block，线程控制块。它类似于 PCB，但更轻量。TCB 通常包括：内容作用线程标识标识线程线程状态运行、就绪、等待等调度参数优先级等现场信息寄存器、PC、SP链接指针加入线程队列存放位置取决于线程类型：线程类型TCB 位置用户级线程用户空间核心级线程系统空间6. 用户级线程用户级线程的特点：线程管理由用户态线程库完成，操作系统不知道这些线程的存在。优点：优点说明不依赖操作系统可以由语言库实现切换快不需要进入内核调度灵活应用程序可以自定义调度缺点：缺点说明不能真正并行操作系统只看到一个进程一个线程阻塞，整个进程可能阻塞因为内核不知道进程内部还有其他线程重点：用户级线程：OS 不可见，切换快，但并行能力差。7. 核心级线程核心级线程的特点：线程由操作系统内核创建、撤销和调度，操作系统知道线程的存在。优点：优点说明可以真正并行多 CPU 上多个线程可同时运行一个线程阻塞，不影响同进程其他线程内核可以调度其他线程缺点：缺点说明开销大创建、切换要进入内核灵活性较低调度由操作系统控制重点：核心级线程：OS 可见，可以真正并行，但系统开销大。8. 用户级线程 vs 核心级线程这是考试高频比较题。比较项用户级线程核心级线程OS 是否可见不可见可见管理由谁完成用户线程库操作系统内核创建/撤销/调度不需要内核支持需要内核支持切换速度快较慢能否多 CPU 并行通常不能可以一个线程阻塞影响可能阻塞整个进程只阻塞该线程调度单位进程线程TCB 位置用户空间系统空间9. 混合线程与轻进程 LWP混合线程试图结合两者优点。以 Solaris 为例，有三层：用户级线程 ↓ 轻量级进程 LWP ↓ 核心级线程 ↓ CPU本章强调：概念说明用户级线程由库支持LWP轻量级进程，对操作系统可见核心级线程由内核支持CPU最终执行核心级线程多个用户级线程可以通过 LWP 映射到核心级线程。10. 线程和进程的比较这也是必背内容。比较进程线程基本角色资源分配单位CPU 调度和执行单位地址空间进程之间通常独立同进程线程共享通信较复杂，需要 IPC较方便，共享数据切换开销大小独立性强弱安全性较高较低，线程间容易相互影响崩溃影响一个进程崩溃通常不影响其他进程一个线程出错可能影响整个进程一句话：进程更安全但更重，线程更高效但更容易相互影响。11. 多任务与多线程的区别概念针对对象含义多任务操作系统系统能同时执行多个程序多线程一个进程一个进程内部有多个执行流例如：一边开浏览器，一边开音乐播放器：多任务 浏览器内部一个线程加载网页，一个线程渲染页面：多线程2.4 作业1. 作业的概念作业是：用户要求计算机系统为其完成的计算任务集合。作业比进程更偏向“用户任务”的概念。一个作业可以包含多个作业步。2. 作业步作业步是：作业处理过程中一个相对独立的步骤。通常：一个作业步可由一个进程完成 某些作业步之间可以并行例如一个编程作业可能包含：编辑源程序 → 编译 → 链接 → 执行 → 输出结果其中每一步都可以看作一个作业步。3. 作业分类作业分为两类：类型特点批处理作业用户提交后由系统自动处理交互式作业用户通过终端与系统交互4. 作业控制块 JCBJCB 是 Job Control Block，作业控制块。它类似于 PCB，但管理对象是作业。JCB 中通常包括：内容作业名称作业状态调度参数资源请求相关进程作业长度输入井、输出井位置记账信息可以这样记：PCB 管进程，TCB 管线程，JCB 管作业。5. 批处理作业批处理作业常用 JCL，即作业控制语言。JCL 用来描述作业执行步骤。例如：$JOB J1 $FORTN … $LINK … $EXEC … $ENDJOB批处理作业的大致过程：作业进入输入井 ↓ 作业调度选中作业进入内存 ↓ 创建作业控制进程 ↓ 解释作业说明书 ↓ 为每个作业步创建相应进程 ↓ 输出结果 ↓ 作业结束6. 交互式作业交互式作业强调用户登录、输入命令、系统响应。以 UNIX shell 为例：显示提示符 $ 读取用户命令 分析命令 如果是内部命令，直接处理 如果是外部命令，创建子进程执行 如果是后台命令，输出子进程号后继续 如果是 logout，注销并记账这里和前面的 fork、exec、wait 联系很紧密：shell 读入命令 fork 创建子进程 子进程 exec 执行程序 父进程 wait 等待子进程结束本章最重要的几组对比1. 程序、进程、线程、作业概念含义重点程序静态代码存在于磁盘或内存中进程程序的一次执行资源分配单位线程进程中的执行流CPU 执行单位作业用户提交的任务集合可包含多个进程2. PCB、TCB、JCB控制块管理对象作用PCB进程标志进程存在TCB线程标志线程存在JCB作业标志作业存在3. 就绪态、运行态、等待态状态是否有 CPU是否具备运行条件运行态有是就绪态没有是等待态没有否，正在等事件易考知识点进程的定义：进程是程序的一次执行。进程和程序的区别：程序静态，进程动态；程序可长期保存，进程有生命周期。三态模型：运行、就绪、等待，以及转换原因。PCB 的作用：PCB 是进程存在的唯一标志。进程组成：PCB + 程序 + 数据/堆栈。上下文切换：保存当前进程现场，恢复另一个进程现场，会产生系统开销。进程创建与撤销过程。fork、exec、wait、exit 的作用和返回值。线程和进程区别：进程是资源分配单位，线程是执行单位。用户级线程和核心级线程区别。作业、进程、线程关系。简答题模板题目：简述进程和程序的区别。程序是静态的，是一组指令和数据的集合；进程是动态的，是程序关于某个数据集合的一次执行活动。程序可以长期保存，而进程有生命周期，会被创建、运行、等待和撤销。一个程序可以对应多个进程，而一个进程在某一时刻通常执行一个程序。进程还具有 PCB，是操作系统进行资源分配和调度的基本对象。题目：简述 PCB 的作用。PCB 是进程控制块，是标志进程存在的数据结构。操作系统通过 PCB 保存和管理进程的全部信息，包括进程标识、状态、现场信息、调度参数、地址信息、打开文件和队列指针等。进程创建时建立 PCB，进程撤销时回收 PCB。因此 PCB 是操作系统感知和管理进程的依据。题目：为什么要引入线程？进程切换需要保存和恢复大量上下文信息，如地址空间、系统栈、打开文件表等，系统开销较大；同时进程之间地址空间相互独立，通信不够方便。线程是进程内部相对独立的执行流，同一进程内的线程共享代码段、数据段和地址空间，只需保存少量现场信息，因此切换速度快、通信方便、系统开销小，还能提高并发性和资源利用率。题目：用户级线程和核心级线程有什么区别？用户级线程由用户态线程库管理，操作系统不可见，创建、撤销和切换不需要进入内核，因此速度快、调度灵活，但不能充分利用多处理器，并且一个线程阻塞可能导致整个进程阻塞。核心级线程由操作系统内核管理，操作系统可见，线程是 CPU 调度单位，可以在多处理器上真正并行，一个线程阻塞不影响同进程其他线程，但创建和切换需要进入内核，系统开销较大。小结单道程序资源利用率低 ↓ 引入多道程序设计，提高 CPU 和设备利用率 ↓ 多道程序需要管理正在执行的程序 ↓ 引入进程 ↓ 进程需要 PCB、状态、队列、上下文切换 ↓ 进程切换开销大，进程间通信不方便 ↓ 引入线程 ↓ 线程轻量、切换快、共享进程资源 ↓ 用户提交的整体任务称为作业 ↓ 作业进入内存后通常对应一个或多个进程本章概括：作业是用户任务，进程是资源分配和管理单位，线程是 CPU 执行单位；多道程序设计通过进程和线程机制实现并发，提高系统资源利用率。中断与处理器调度第三章总主线这一章其实围绕一句话展开：操作系统是中断驱动的。也就是说，操作系统不是一直主动运行，而是很多时候在等待事件发生。一旦发生中断，例如 I/O 完成、时间片用完、程序出错、用户执行系统调用，CPU 就从用户程序转入操作系统，由操作系统决定：继续原进程？让它等待？终止它？还是切换到另一个进程？所以本章可以拆成三条主线：主线解决的问题中断操作系统什么时候获得 CPU 控制权？处理器调度获得控制权后，CPU 应该分给谁？多级/实时/多处理器调度在更复杂系统中，如何调度作业、进程、实时任务、多核 CPU？3.1 中断与中断系统1. 中断的概念中断是指程序运行过程中出现某个紧急事件，CPU 必须暂停当前程序，转去处理该事件，处理完后再返回原程序继续执行。可以把中断理解为：CPU 正在做题，突然有人敲门。CPU 先把当前做到哪一步记下来，去开门处理事情，然后回来继续做题。课件强调：操作系统是中断驱动的。没有中断，操作系统很难及时响应外设、时间片、错误、系统调用等事件。中断系统 = 硬件 + 软件部分名称作用硬件中断装置发现中断、识别中断源、保存现场、转入处理程序软件中断处理程序真正处理中断事件2. 中断装置中断装置的主要职责有四个：发现并响应中断识别中断源多个中断源同时出现时，按优先级排序保存现场，并引出中断处理程序课件第 7 页的图展示了典型中断响应过程：正在运行的程序被打断，硬件保存 PSW、PC 到系统栈，然后根据中断向量转到中断处理程序。这里的两个核心寄存器要特别记住：寄存器含义PC下一条要执行的指令地址PSW程序状态字，包括运行状态、权限状态、中断屏蔽状态等所以保存 PC 和 PSW，本质上就是保存“程序执行到哪里”和“程序当时处于什么状态”。3. 中断源、中断寄存器、中断字概念含义中断源引起中断的事件中断寄存器保存中断事件相关信息的寄存器中断字中断寄存器中的内容例如 I/O 中断：项目内容中断源I/O 设备中断寄存器保存设备相关信息中断字设备状态、错误信息等具体内容不是所有中断都一定需要中断寄存器。例如溢出中断，CPU 根据运算结果就能知道发生了溢出。4. 中断类型课件给了两种分类方式。第一种：按是否由程序主动引起。类型含义例子强迫性中断程序不期望发生，被动被打断时钟中断、I/O 中断、硬件故障、非法指令、缺页自愿性中断程序主动请求操作系统服务系统调用、访管指令 SVC第二种：按来源。类型来源例子外部中断CPU 外部事件I/O 中断、时钟中断、外部信号内部中断CPU 内部事件程序中断、访管中断、除 0、溢出、非法指令考试中常见判断：时钟中断：外部中断、强迫性中断。I/O 中断：外部中断、强迫性中断。系统调用/访管指令：内部中断、自愿性中断。除 0、溢出、非法指令：内部中断、通常属于程序性中断。5. 中断向量中断向量保存的是某类中断处理程序的运行环境和入口地址，主要包括：PSW + PC每类中断事件都有一个中断向量。中断向量的位置由硬件规定，内容由操作系统在初始化时设置好。重点：中断向量中的 PSW 必须是系统态/管态。原因很简单：中断处理程序属于操作系统代码，必须在核心态运行，否则无法执行特权指令。6. 中断嵌套与系统栈中断嵌套是指：系统正在处理中断时，又来了一个新的、更高优先级的中断。一般原则：高优先级中断可以嵌入低优先级中断。实现方法：响应一个中断后，屏蔽不高于当前优先级的中断。允许更高优先级的中断继续进入。每一层中断现场都压入系统栈。返回时按栈的后进先出顺序恢复。课件第 18 页有一句很重要：中断相当于一种特殊的子程序调用，发生时刻具有==不确定性==。普通函数调用是程序自己安排的；中断调用是外部或硬件突然打断的，所以必须由硬件和操作系统共同保证现场保存正确。7. 中断现场和进程现场这是本章非常容易考的点。现场保存位置什么时候保存中断现场系统栈中断发生时，临时保存当前执行点进程现场PCB进程等待、被剥夺、发生进程切换时更细一点：当用户进程被中断时，硬件先把用户态的 PC、PSW 压入系统栈。之后操作系统可能继续保存通用寄存器等信息。如果中断处理完后不切换进程，就从系统栈恢复现场，返回原程序。如果需要切换进程，例如当前进程等待 I/O 或被剥夺 CPU，操作系统就要把该进程的核心级现场保存到 PCB 中。所以说：PCB 保存的是核心级别现场。这是因为发生等待、剥夺等动作时，进程已经进入核心态，正在操作系统中执行，PCB 保存的是当时核心态下的寄存器、SP、PSW、PC 等。8. 进程状态转换：结合用户态和核心态理解课件第 22 到 36 页为状态图，这部分一定要理解。传统进程状态是：创建、就绪、运行、等待、终止但本章进一步把“运行”拆成：状态含义运行 user进程在用户态运行用户程序运行 kernel进程进入核心态，执行操作系统代码典型过程如下：进程创建：建立 PCB、分配资源、加载程序、初始化 PC、PSW、SP、寄存器，进入就绪队列。被调度选中：恢复 PCB 中保存的现场。最后恢复 PSW 和 PC：进程真正进入运行态。用户态运行时发生中断：PC、PSW 等现场压入系统栈，进入核心态。如果进程等待事件：保存核心现场到 PCB，PCB 进入等待队列。如果事件发生：I/O 中断唤醒该进程，PCB 从等待队列转入就绪队列。如果中断嵌套返回：从系统栈弹出现场。如果处理器被剥夺：保存当前进程核心现场到 PCB，进入就绪队列。如果执行 exit：收回资源，撤销 PCB，进入终止状态。9. 各类中断处理程序I/O 中断处理I/O 正常结束时：如果还需要继续传输数据，就准备下一批数据并启动通道。如果有进程正在等待 I/O 完成，就唤醒该进程。如果 I/O 出错：可以重试若干次，例如 3 次。仍失败，则报告系统操作员或做错误处理。时钟中断处理时钟分为：类型是否产生中断绝对时钟不发生中断间隔时钟发生中断软件时钟本身不发生中断，由硬件时钟加程序实现时钟中断非常重要，它负责：实现软件时钟和定时程序。记录进程 CPU 使用时间。判断时间片是否用完。重新计算动态优先级。记录作业等待时间。统计资源使用情况。实时系统中定时发出控制信号。定期运行死锁检测、系统记账等维护程序。控制台中断处理控制台上不同按钮可对应不同中断向量。按下按钮后，系统转入对应中断处理程序。硬件故障中断处理例如电源故障：掉电时，硬件应保证机器继续运行一小段时间。操作系统把内存、寄存器内容保存到外存。停止设备。停止处理机。恢复时，再从外存恢复内存和寄存器。内存故障时，系统可以检测错误单元，并把相关区域永久划为不可用区。程序性中断处理分两类：类型例子处理只能由 OS 处理越界、非法指令通常终止进程需要 OS 协助缺页、缺段动态调入页面或段可由用户处理除 0、溢出用户处理或 OS 标准处理课件第 46 到 60 页讲了“应用程序自行处理中断”。其核心思想是：用户程序可以设置一个“中断续元表”。当发生某些用户可处理的异常时，OS 查表，如果用户设置了处理入口，就转到用户自己的处理程序；否则由 OS 标准处理。自愿性中断：系统调用用户程序不能直接执行特权指令，例如启动外设、修改系统资源、创建进程等。解决办法是：用户程序通过系统调用进入操作系统，由操作系统代替它完成特权操作。系统调用有两种形式：形式例子访管指令形式准备参数 → SVC n → 取返回值系统调用形式返回值 = open(fname, mode)编译器会把系统调用形式翻译成访管指令形式。课件第 63 页的图很重要：用户程序执行陷入指令后，陷入处理机构保护现场，取得系统调用功能号，查入口地址表，转入对应系统子程序，最后返回用户程序。常见系统调用类型：文件类：创建文件、删除文件。进程类：创建进程、撤销进程。通信类：发送消息、接收消息。资源类：申请资源、释放资源。3.2 处理器调度处理器调度解决三个问题：问题对应内容按什么原则分配 CPU？调度算法什么时候重新分配 CPU？调度时机如何完成分配？调度过程1. 调度评价指标常见指标：指标目标CPU 利用率越高越好吞吐量越高越好周转时间越短越好响应时间越短越好系统开销越小越好重点公式：名称公式周转时间 T完成时间 - 进入时间平均周转时间所有周转时间求平均带权周转时间 W周转时间 / 实际运行时间平均带权周转时间所有带权周转时间求平均等待时间周转时间 - 运行时间做调度题时，一般步骤是：画 Gantt 图。求每个进程完成时间。求周转时间。求带权周转时间。求平均值。2. CPU burst 与 I/O burst进程的运行通常不是一直占 CPU，而是：CPU burst → I/O burst → CPU burst → I/O burst → …概念含义CPU burst一次连续使用 CPU 的时间I/O burst一次连续等待或执行 I/O 的时间CPU 调度主要考虑的是已经就绪、准备进入下一段 CPU burst 的进程。CPU burst 预测公式：$$ \tau_{n+1}=\alpha t_n+(1-\alpha)\tau_n $$含义：$t_n$：最近一次实际 CPU burst 长度。$\tau_n$：原来的估计值。$\tau_{n+1}$：新的估计值。$\alpha$：权重，通常取 0.5。如果 $\alpha=1$，完全相信最近一次实际值。如果 $\alpha=0$，完全不看最近一次实际值。3. 剥夺式与非剥夺式调度类型含义运行进程失去 CPU 的原因非剥夺式就绪进程不能抢 CPU运行结束或主动等待剥夺式就绪进程可以抢 CPU运行结束、主动等待、被抢占剥夺式调度响应更快，但系统开销更大，实现更复杂。4. FCFS：先到先服务FCFS = First Come First Serve规则：谁先进入就绪队列，谁先运行。优点：简单。看起来公平。缺点：短作业可能等待很久。容易出现“长作业堵住短作业”的现象。适合考法：给到达时间和运行时间，让你按到达顺序画 Gantt 图，求平均周转时间和平均带权周转时间。5. SJF：短作业优先SJF = Shortest Job First规则：选择 CPU burst 最短的作业或进程先运行。特点：如果所有任务同时到达，SJF 可以得到最短平均等待时间。但长作业可能长期得不到运行，也就是“饥饿”。适合批处理系统，不太适合交互系统。6. SRTN：最短剩余时间优先SRTN = Shortest Remaining Time Next它是可剥夺版 SJF。规则：每次调度时选择剩余运行时间最短的进程。如果新来的进程剩余时间更短，就可以抢占当前进程。做题重点：不只在进程结束时判断。新进程到达时也要判断是否抢占。Gantt 图可能被切成很多段。7. HRN：最高响应比优先HRN = Highest Response Ratio Next响应比公式：$$ RR=\frac{BT+WT}{BT}=1+\frac{WT}{BT} $$符号含义BT运行时间WT等待时间规则：每次选择响应比最高的作业。它兼顾了短作业和长作业：如果等待时间相同，短作业响应比更高。长作业等待越久，响应比也会逐渐增大，因此不容易饿死。8. HPF：最高优先数算法HPF = Highest Priority First优先级分为两种：类型特点静态优先数创建时确定，之后不变；简单但可能不公平动态优先数运行过程中可变化；公平性好但开销大也可以分为：类型特点非剥夺式优先级调度当前进程运行到结束或等待剥夺式优先级调度出现更高优先级进程时可以抢占注意：不同系统对“优先数”的大小含义可能不同。有的系统数值越大，优先级越高。UNIX 例子中 $p_{pri}$ 越小，优先级越高。做题时一定看题目说明或样例。9. RR：时间片轮转RR = Round Robin规则：每个进程轮流运行一个时间片，用完时间片还没结束，就回到就绪队列尾部。时间片选择很重要：时间片效果太长退化为 FCFS，响应慢太短进程切换太频繁，系统开销大RR 适合分时系统和交互系统。做 RR 题时，重点是维护就绪队列顺序。进程时间片用完后，如果还没结束，通常放到队尾。10. MLQ：多级队列算法MLQ = Multi-Level Queue思想：把进程固定分到不同队列，每个队列可用不同调度算法。例如：队列类型调度算法队列 1实时进程HPF队列 2分时进程RR队列 3批处理进程HPFCPU 空闲时先看高优先级队列，高优先级队列为空才看低优先级队列。缺点是进程所属队列固定，灵活性不够。11. FB：反馈排队算法FB = Feed Back也叫多级反馈队列。思想：多个就绪队列，进程所在队列可以变化。典型规则：新进程或被唤醒进程进入高优先级队列。如果一个进程用完时间片还没完成，就降到低一级队列。越低级队列时间片越长、调度频率越低。优点：交互式进程响应快。I/O 型进程容易被快速调度。CPU 型长进程调度频率下降，系统开销小。不需要预先知道进程运行时间。缺点：长进程可能被惩罚，甚至饥饿。12. 处理器调度时机调度通常发生在三种情况：运行进程结束。运行进程等待。处理器被剥夺。一个重要结论：中断是处理器切换的必要条件，但不是充分条件。意思是：没有中断，操作系统一般拿不到控制权，无法切换进程。但发生中断后，不一定要切换进程。例如：中断类型是否一定引起进程切换exit()一定进程等待 I/O一定非法指令、越界导致终止一定时钟中断可能read 系统调用可能13. Dispatcher：处理器分派程序如果中断处理后需要切换进程，就进入 dispatcher。dispatcher 做三件事：保存下降进程现场到 PCB。按调度算法选择上升进程。从 PCB 恢复上升进程现场。恢复现场时要注意：先恢复通用寄存器、地址寄存器、SP，最后恢复 PSW 和 PC。并且：PSW 和 PC 必须用一条机器指令同时恢复。原因是 PC 决定下一条执行哪条指令，PSW 决定以什么状态执行。如果分开恢复，可能出现“地址已经变了但状态没变”或“状态已经变了但地址没变”的危险情况，导致权限、地址空间、中断屏蔽状态不一致。3.3 调度级别与多级调度调度分三级：调度级别又称作用高级调度作业调度决定哪些作业进入内存中级调度交换调度决定哪些进程换入/换出内存低级调度处理器调度决定 CPU 分给哪个就绪进程1. 中级调度：交换交换 swapping：在内存和外存之间调度进程。中级调度的目标是控制并发度。并发度是指同时向前推进的进程个数。并发度太高会导致：系统开销大。响应速度慢。内存资源紧张。进程频繁等待。死锁可能性增加。所以系统可能把一些进程换出到外存，降低内存压力。课件第 106 页为加入“挂起”后的状态图：就绪等待就绪挂起等待挂起事件发生后，等待挂起可以变为就绪挂起；换入后再回到就绪队列。2. UNIX 中级调度UNIX 的 sched #0 思路：系统希望把外存中处于 SRUN 状态的进程换入内存。如果内存不够：先换出 SWAIT 和 SSTOP 状态进程。还不够，再换出 SSLEEP 和 SRUN 状态进程。条件：待移入进程在外存时间 ≥ 3 秒。待移出进程在内存时间 ≥ 2 秒。这里体现了一个思想：不要频繁换入换出，否则交换本身会造成巨大开销。3. 高级调度：作业调度作业调度：把磁盘输入井中的作业调入内存，并为其建立相应进程，使其获得运行资格。批处理作业的状态：状态含义提交输入设备正在向输入井传送后备作业在输入井，尚未进入内存执行作业分解为进程，在内存中执行完成处理完毕，结果在输出井退出从输出井向打印机等输出设备传送课件第 111 页讲到了 SPOOLing。SPOOLing = Simultaneous Peripheral Operation On-Line它通过磁盘输入井/输出井，把慢速外设操作变成相对高效的联机操作。4. JCB：作业控制块JCB 类似于进程的 PCB，只不过它描述的是作业。JCB 中常见信息：作业标识所属用户作业状态调度参数输入井地址输出井地址资源需求进入时间处理时间完成时间JCB 由 SPOOLing 输入建立，作业调度使用，SPOOLing 输出撤销。5. 作业调度算法适合批处理作业调度的算法：FCFSHPFSJFHRN不适合作业调度的算法：RRSRTNFB原因是作业调度面对的是“后备作业进入系统”的问题，不是已经在内存中的进程频繁切换问题。RR、SRTN、FB 更适合低级处理器调度。3.4 实时调度1. 实时任务实时任务是有明确时间约束的任务。例如：必须在某时刻前开始。必须在某时刻前完成。实时调度的目标是：合理安排就绪实时任务的执行次序，使任务满足时间约束。2. 实时任务分类分类类型含义按严格程度硬实时必须满足截止期按严格程度软实时希望满足截止期，偶尔错过可接受按发生规律周期性每隔固定时间发生一次按发生规律随机性由随机事件触发，发生时刻不确定3. 实时调度术语术语含义就绪时间 Ready time任务产生并可以开始处理的时间开始截止期 Starting deadline最迟开始处理时间处理时间 Processing time需要的 CPU 时间，常记为 C完成截止期 Completion deadline最迟完成时间，常记为 D发生周期 Occurring period周期性任务的发生间隔，常记为 T4. 周期性实时任务的可调度条件如果任务 $P_i$ 的处理时间是 $C_i$，周期是 $T_i$，则处理器利用率为：$$ \sum_{i=1}^{m}\frac{C_i}{T_i} $$必要条件：$$ \sum_{i=1}^{m}\frac{C_i}{T_i}\le 1 $$直观理解：所有任务平均需要的 CPU 时间不能超过 CPU 总能力。例如课件中的例子：$C_1/T_1=50/100=0.5$$C_2/T_2=30/200=0.15$$C_3/T_3=100/500=0.2$总和：$$ 0.5+0.15+0.2=0.85<1 $$说明从 CPU 总量上看，有可能调度成功。5. EDF：最早截止期优先EDF = Earliest Deadline First规则：谁的完成截止期最早，谁优先运行。如果新到达任务的截止期早于当前任务，则可以抢占当前任务。EDF 的可调度条件：$$ \sum_{i=1}^{m}\frac{C_i}{T_i}\le 1 $$EDF 的优点是处理器利用率高；缺点是动态比较截止期，实现复杂度比固定优先级调度高。6. RMS：速率单调调度RMS = Rate Monotonic Scheduling规则：周期越短，优先级越高。周期短说明任务发生频率高，所以优先级高。RMS 的充分可调度条件：$$ \sum_{i=1}^{n}\frac{C_i}{T_i}\le n(2^{1/n}-1) $$当 $n\to \infty$ 时，上界趋近：$$ \ln 2 \approx 0.693 $$强调：对比EDFRMS可调度条件更宽松更严格实现复杂度较复杂较简单优先级动态截止期固定周期注意：RMS 的利用率条件是充分条件。如果超过上界，不一定绝对不可调度，只是不能用这个简单公式保证可调度。7. MLF：最小裕度优先MLF = Minimum Laxity First裕度公式：$$ L=D-(T+C) $$符号含义L裕度D截止期T当前时间C剩余处理时间含义：如果现在不运行这个任务，它最多还能拖多久。如果 $L=0$，说明任务必须马上运行，否则一定错过截止期。MLF 比 EDF 更精确，因为它不仅看截止期，还看剩余运行时间。3.5 多处理器调度1. 多处理器调度问题系统中有：M 个进程或线程。N 个处理器。在 SMP 系统中，所有处理器是对称的、同构的。目标：Load sharing，即负载共享，让多个 CPU 尽量均衡工作。常见就绪队列设计：方式特点每个处理器一个就绪队列不容易均衡所有处理器共享一个就绪队列均衡，但访问队列需要互斥2. 自调度Self scheduling规则：多个 CPU 共同访问一个就绪队列，谁空闲谁自己取任务。优点：不需要专门处理器负责分派。任务分配较均衡。缺点：CPU 很多时，共享就绪队列会成为瓶颈。同一线程两次调度可能在不同处理器上运行，影响缓存局部性。不能保证合作线程同时运行。Mach 的改进方式：一个全局队列。每个 CPU 一个局部队列。调度时先看局部队列，再看全局队列。3. 组调度Gang scheduling规则：把一组相关或合作线程同时分派到多个处理器上运行。优点：避免合作线程互相等待。降低同步开销。提高并行程序效率。适合多线程并行计算任务。3.6 系统举例1. Linux 进程调度早期 Linux 的 goodness-based scheduling。核心参数：参数含义priority静态优先级，0 到 40，默认 20，可通过 nice 调整counter剩余可运行时间，单位是时钟滴答goodness调度时用来比较的综合值counter 的变化：运行进程每个时钟中断 counter 减 1。counter 为 0 时，该进程不能继续运行。当所有就绪进程 counter 都为 0 时，重新计算：$$ counter = counter/2 + priority $$goodness 规则：情况goodness实时进程1000 + priority分时进程且 counter = 00分时进程且 counter > 0counter + priority调度发生时机：当前进程 counter 减到 0。当前进程 exit。当前进程等待 I/O 或信号量。原来高 goodness 的进程被唤醒。调度效果：实时进程优先于分时进程。交互进程和 I/O 型进程优先于 CPU 密集型进程。Linux SMP：早期 Linux 2.0 使用一个大的内核自旋锁，保证任意时刻最多一个处理器执行核心代码。后来版本把大锁拆成多个细粒度锁，支持真正意义上的 SMP 并行。2. Windows 线程调度Windows 的调度单位主要是线程，而不是进程。特点：线程是主要调度单位支持实时、前台、后台特征抢占式调度动态优先级RRFeedback支持 SMPWindows 优先级：优先级范围类型16–31实时优先级1–15可变线程优先级0页面清零守护线程线程有两个优先级：名称含义基本优先级继承自进程基本优先级，可上下浮动当前优先级实际调度时使用，可动态提升和下降优先级提升情况：I/O 操作完成等待事件结束前台进程线程完成等待GUI 线程因窗口活动被唤醒就绪时间超过一定时限还没获得 CPU抢占 CPU 的情况：被唤醒线程优先级高于当前运行线程某就绪线程优先级动态提高时间配额 quantum：专业版较短服务器版较长时间片用完后线程进入就绪队列，优先级下降SMP 上的 Windows 调度：尽量让线程在同一个处理器上运行。使用处理器亲和性 affinity。每个线程有理想处理器 ideal processor。空闲处理器选择线程时会考虑缓存利用、理想处理器、等待时间、优先级等因素。3. UNIX 进程调度UNIX 采用：可抢占 + 动态优先级公式：$$ p_{pri}=\min{127,\ USER+p_{cpu}/16+p_{nice}} $$其中：参数含义$USER$通常为 100$p_{cpu}$CPU 使用量$p_{nice}$用户通过 nice 调整的值规则：运行进程每 20ms，$p_{cpu}$ 加 1，优先级降低。其他进程每 1200ms，$p_{cpu}$ 减 10，优先级提高。调度时选择 $p_{pri}$ 最小的进程。这体现了动态优先级思想：CPU 用得多，优先级下降；等待得久，优先级上升。考点总结概念概念总结中断暂停当前程序，处理事件，再返回中断系统中断装置 + 中断处理程序中断向量中断处理程序的 PSW + PC中断嵌套高优先级中断打断低优先级中断系统调用用户程序进入操作系统请求服务处理器调度从就绪队列中选择进程占用 CPU高级调度作业进入内存中级调度进程换入换出低级调度CPU 分配给哪个进程实时调度满足任务时间约束SMP对称多处理器系统公式内容公式周转时间完成时间 - 到达时间带权周转时间周转时间 / 运行时间HRN 响应比1+等待时间/运行时间CPU burst 预测$\tau_{n+1}=\alpha t_n+(1-\alpha)\tau_n$EDF 可调度条件$\sum C_i/T_i\le 1$RMS 充分条件$\sum C_i/T_i\le n(2^{1/n}-1)$最小裕度$L=D-(T+C)$UNIX 动态优先级$p_pri=\min{127,USER+p_cpu/16+p_nice}$调度算法对比算法是否抢占核心规则优点缺点FCFS否先来先服务简单公平短作业等待长SJF否最短运行时间优先平均等待短长作业可能饿死SRTN是剩余时间最短优先响应更好切换多，实现复杂HRN否响应比最高优先兼顾长短作业每次要计算响应比HPF可抢占/可非抢占优先级最高先运行灵活低优先级可能饿死RR是时间片轮转响应快时间片难选MLQ通常可设计多个固定队列分类清晰队列固定不灵活FB是多级反馈，动态升降交互响应好长进程受惩罚做题1. 普通调度题统一步骤：按算法画 Gantt 图。找每个进程完成时间。周转时间 = 完成时间 - 到达时间。带权周转时间 = 周转时间 / 运行时间。平均值 = 求和 / 进程数。特别注意：SRTN 要在新进程到达时判断是否抢占。RR 要维护队列顺序。HRN 每次调度前重新计算响应比。HPF 要看清楚“数值大优先”还是“数值小优先”。2. 中断与进程切换题记住：中断不一定导致进程切换，但进程切换一定需要操作系统通过中断或异常等机制获得控制权。判断题常见说法：“发生中断一定发生进程切换”——错。“发生进程切换一定要保存 PCB”——对。“中断现场保存在 PCB 中”——不严谨，通常中断现场先保存在系统栈中。“PCB 保存核心级现场”——对。“系统调用是自愿性中断”——对。3. 实时调度题先算：$$ U=\sum C_i/T_i $$然后判断：EDF：若 $U\le1$，通常可调度。RMS：若 $U\le n(2^{1/n}-1)$，一定可调度。若 RMS 超过该上界，不能直接说一定不可调度，要看题目是否要求进一步画图验证。思考题用户程序不能执行特权指令，但想启动外设怎么办？答案：通过系统调用。用户程序执行访管指令 SVC/Trap，陷入操作系统，由操作系统在核心态完成外设操作，再返回用户程序。中断现场保存在什么地方？进程现场保存在什么地方？答案：中断现场通常保存在系统栈；进程切换时，进程现场保存在 PCB。为什么说 PCB 保存的是核心级别现场？答案：因为进程只有通过中断、异常、系统调用进入核心态后，操作系统才可能让它等待、剥夺它或切换它。此时保存到 PCB 的是该进程在核心态下的寄存器、SP、PSW、PC 等现场。为什么 PSW 和 PC 必须用一条机器指令同时恢复？答案：因为 PC 决定下一条执行地址，PSW 决定运行模式、权限、中断屏蔽等状态。如果二者分开恢复，可能出现执行地址和运行状态不匹配，造成权限错误或系统安全问题。因此必须原子地同时恢复。小结本章的三层逻辑：第一层：中断让操作系统获得控制权。包括中断类型、中断向量、系统栈、嵌套、系统调用。第二层：获得控制权后，操作系统决定是否切换进程。包括中断与进程切换关系、PCB 保存现场、dispatcher。第三层：如果要切换，按照调度算法选择下一个进程。包括 FCFS、SJF、SRTN、HRN、HPF、RR、MLQ、FB、EDF、RMS、MLF。本章易考：概念判断：中断、系统调用、PCB、系统栈。调度算法计算：画 Gantt 图、算周转时间和带权周转时间。实时调度判断：EDF/RMS 可调度性。简答题：为什么中断是进程切换必要不充分条件；为什么 PSW 和 PC 要同时恢复。互斥、同步与通信主线：并发带来不确定性 → 需要互斥和同步控制 → 用信号量、管程等机制实现 → 再进一步学习进程间通信。一、4.1 并发进程1. 前趋图前趋图是一个有向无环图，用来表示程序、语句、计算步骤或进程之间的先后依赖关系。记作：Pi → Pj含义是：Pj 开始之前，Pi 必须已经完成。前趋图中：概念含义初始结点没有前趋的结点终止结点没有后继的结点权重可表示程序量或执行时间有向边表示必须满足的先后关系考试中如果给你一张图，通常要求判断哪些可以并发、哪些必须先后执行。2. 顺序程序及其特性顺序程序有两层顺序性：内部顺序性：一个进程内部的语句按顺序执行。例如：S1: a = x + y S2: b = a - z S3: c = a + b S4: d = c + 5这里 S2 依赖 S1，S3 依赖 S1、S2，不能随意并发。外部顺序性：多个进程之间也一个接一个执行，比如：I1 C1 P1 I2 C2 P2表示第一个作业输入、计算、打印完，第二个作业才开始。顺序程序的三个特性：特性含义连续性程序执行过程连续推进封闭性程序运行环境不被外界改变可再现性只要初始条件相同，多次运行结果相同其中可再现性最重要，因为并发程序最容易破坏它。3. 并发程序及其特性并发有两种：内部并发性：一个程序内部某些语句无依赖，可以同时执行。例如：S1: a = x + 2 S2: b = y + 4 S3: c = a + bS1 和 S2 互不依赖，可以并发；S3 依赖 S1、S2，必须在它们之后。外部并发性：多个程序之间并发执行。例如多个作业的输入、计算、打印可以流水线式重叠。并发程序的三个特性：顺序程序并发程序连续性间断性封闭性非封闭性可再现性不可再现性并发程序最核心的问题是：进程执行顺序不同，结果可能不同。这类错误称为与时间有关的错误。4. 与时间有关的错误文件中举了几个典型例子：例 1：图书借阅系统设 x = 1，表示某本书只剩 1 本。两个终端同时执行：if x >= 1: x = x - 1 借书 else: 无书可能出现：终端 1 判断 x >= 1 成立；还没来得及执行 x = x - 1；终端 2 也判断 x >= 1 成立；两个终端都借书成功；结果一本书被借了两次。错误原因：多个进程并发访问并修改共享变量 x。例 2：就绪队列整队错误多个进程同时修改就绪队列，可能导致某个进程节点丢失。例 3：两个进程申请两个独占资源P1: 申请打印机 → 申请输入机 P2: 申请输入机 → 申请打印机可能出现：P1 已拿到打印机，等待输入机 P2 已拿到输入机，等待打印机于是双方永久等待，形成死锁。所以并发错误通常来自两个条件：1. 进程交叉执行 2. 访问共享变量或共享资源5. Bernstein 条件为了判断两个程序能否并发执行，引入读集和写集。符号含义R(P)程序 P 执行期间要读取的变量集合W(P)程序 P 执行期间要修改的变量集合两个程序 P1、P2 可以并发执行的条件是：R(P1) ∩ W(P2) = ∅ R(P2) ∩ W(P1) = ∅ W(P1) ∩ W(P2) = ∅合起来就是：R(P1)∩W(P2) ∪ R(P2)∩W(P1) ∪ W(P1)∩W(P2) = ∅理解：冲突类型含义是否允许并发读-读两个程序都只读同一变量可以读-写一个读，一个写不可以写-写两个都写不可以只要涉及写，就可能冲突；读读不冲突。二、4.2 进程互斥1. 共享变量、临界资源、临界区概念含义共享变量多个进程都要访问的变量临界资源一次只允许一个进程使用的资源临界区访问共享变量或临界资源的程序段互斥同一时刻最多一个进程进入相关临界区临界区不是随便一段代码，而是访问共享资源的那段代码。例如：P(mutex) x = x - 1 V(mutex)这里 x = x - 1 是临界区，mutex 用来保证互斥。2. 临界区管理的三个要求实现互斥必须满足三个条件：条件含义互斥性同一时刻最多一个进程进入同一组共享变量的临界区进展性临界区空闲时，应允许某个等待进程进入有限等待一个进程不能无限期等待，应在有限等待后进入考试中常考：判断某算法是否满足这三个条件。3. 软件实现互斥软件方法不依赖特殊硬件指令，主要靠标志变量实现，但通常存在忙等。忙等和阻塞的区别类型特点忙式等待进程一直循环检查条件，不主动放弃 CPU，效率低阻塞等待进程主动放弃 CPU，进入等待队列，效率高4. 几种软件互斥算法尝试 1：turn 变量轮流进入while(turn == 1); critical section; turn = 1;问题：必须严格交替进入临界区。如果 P0 不想进，P1 也可能被迫等待。所以：不满足进展性。尝试 2：先检查后上锁while(flag[1]); flag[0] = 1; critical section;问题：两个进程可能同时看到对方 flag 为 0，然后同时进入临界区。所以：不满足互斥性。尝试 3：先上锁后检查flag[0] = 1; while(flag[1]); critical section;问题：两个进程都先把自己的 flag 置 1，然后都等待对方，谁也进不去。所以：不满足进展性，会死等。Peterson 算法两个进程互斥的经典软件算法：flag[i] = true; turn = j; while(flag[j] && turn == j); critical section; flag[i] = false;思想：flag[i] 表示我想进 turn = j 表示如果发生竞争，我让对方先进Peterson 算法在理论模型下满足互斥性、进展性、有限等待，是两个进程互斥中非常经典的算法。Lamport 面包店算法用于 n 个进程互斥。思想类似面包店排号：谁的号码小，谁先进； 号码相同，则进程编号小者先进。比较规则：(a,b) < (c,d) 当且仅当 a < c，或者 a == c 且 b < d它通过 choosing[] 和 number[] 保证多个进程可以排出一个全序，满足互斥、进展、有限等待。Eisenberg/McGuire 算法也是 n 个进程的软件互斥算法。每个进程状态有三种：状态含义idle不想进入临界区want_in想进入临界区in_cs想进入或已经进入临界区算法思想是：进程先声明自己想进入；从 turn 开始扫描前面的进程；如果没有更优先的竞争者，则把自己置为 in_cs；再检查是否有其他进程也是 in_cs；没有则进入临界区，否则重来。它满足互斥性、进展性和有限等待。5. 硬件实现互斥1）关中断 / 开中断关中断 临界区 开中断原理：单 CPU 中，进程切换依赖中断。关中断后不会发生进程切换，所以可以保证互斥。优点：简单、高效、无忙等缺点：只适合单 CPU 影响并发性 只能由操作系统使用2）Test-and-Set 指令原子操作：int test_and_set(int *lock) { int temp = *lock; *lock = 1; return temp; }使用方式：while(test_and_set(&lock)); critical section; lock = 0;特点：条件是否满足互斥性满足进展性满足有限等待不一定满足因为某个进程可能一直抢不到锁。3）Swap 指令原子交换两个变量：swap(lock, key)同样可以实现互斥，但也可能不满足有限等待。4）多处理机环境下的问题在多 CPU 系统中，不同 CPU 可能同时访问内存。普通的 test_and_set 或 swap 如果没有总线锁或缓存一致性支持，就不一定真正原子。所以多处理机互斥需要：lock bus 执行 test_and_set / swap unlock bus或者现代体系结构中的原子指令、内存屏障等支持。三、4.3 进程同步1. 同步的概念同步是指一组合作进程为了协调推进速度，在某些点互相等待或唤醒。互斥和同步的区别非常重要：对比互斥同步目的防止同时访问临界资源保证前后执行顺序关系进程之间可以逻辑无关进程之间通常有合作关系典型问题多人抢一个资源A 做完后 B 才能做信号量初值常为 1常为 0例如司机—售票员问题：售票员先关门 → 司机才能开车 司机到站停车 → 售票员才能开门这就是同步关系。文件中将同步机制归纳为信号量与 PV 操作、管程、条件临界区、会合、路径表达式、事件等。2. 信号量与 PV 操作信号量结构：typedef struct { int value; PCB *queue; } semaphore;信号量有两个核心操作：P 操作P(S): S.value-- if S.value < 0: 当前进程阻塞，进入 S.queue含义：申请资源 / 等待条件成立。V 操作V(S): S.value++ if S.value <= 0: 唤醒 S.queue 中一个等待进程含义：释放资源 / 发出完成信号。3. 信号量的重要结论初值用途S = 1实现互斥S = 0实现同步S = n管理 n 个同类资源信号量状态解释：S.value含义S.value > 0可用资源数S.value = 0无可用资源，也无人等待S.value < 0等待进程数为 `4. 用信号量实现互斥模板：semaphore mutex = 1; P(mutex); 临界区; V(mutex);例如借书系统中，对 x 的判断和修改必须放入临界区：P(mutex) if x >= 1: x = x - 1 V(mutex) 借书 else: V(mutex) 无书注意：只保护真正访问共享变量的部分，不要把无关代码都放进临界区，否则并发性会下降。5. 用信号量实现同步模板：semaphore S = 0; 先动作进程: 先动作; V(S); 后动作进程: P(S); 后动作;口诀：谁要等，谁 P； 谁做完，谁 V。6. 生产者—消费者问题这是最重要的经典同步问题之一，也叫有界缓冲区问题。资源：信号量初值含义empty / S1k空缓冲区数量full / S20产品数量mutex1缓冲区互斥访问生产者：produce item; P(empty); P(mutex); 放入缓冲区; V(mutex); V(full);消费者：P(full); P(mutex); 从缓冲区取出; V(mutex); V(empty); consume item;核心理解：生产者不能在缓冲区满时放； 消费者不能在缓冲区空时取； 放和取缓冲区时还要互斥。文件还提到提高并发性的策略：生产者只竞争 in 和 B[in]，消费者只竞争 out 和 B[out]，可以用两个 mutex 分别保护生产者和消费者侧，从而允许生产者和消费者在某些情况下更并发地执行。7. 读者—写者问题规则：情况是否允许同时读-读允许读-写不允许写-写不允许读者优先解法变量：semaphore r_w_w = 1; // 控制读写互斥 semaphore mutex = 1; // 保护 read_count int read_count = 0;读者：P(mutex); read_count++; if read_count == 1: P(r_w_w); V(mutex); 读操作; P(mutex); read_count--; if read_count == 0: V(r_w_w); V(mutex);写者：P(r_w_w); 写操作; V(r_w_w);思想：第一个读者锁住数据库； 最后一个读者释放数据库。问题：如果读者源源不断，写者可能饿死。公平解法增加一个信号量 w，让读者和写者都先经过它，避免读者无限插队。写者优先解法一旦有写者等待，新来的读者必须等待。变量包括：r_w_w rmutex wmutex read_q read_count write_count核心思想：第一个等待写者关闭 read_q，阻止新读者进入； 最后一个写者释放 read_q，允许读者进入。8. 障栅 barrier 问题问题：n 个进程都必须到达某一点，等所有进程到达后，才能继续。变量：count = 0 mutex = 1 barrier = 0模板：W1; P(mutex); count++; if count == n: for i = 1 to n: V(barrier); V(mutex); P(barrier); W2;含义：最后一个到达障栅的进程负责唤醒所有进程。9. 三台打印机管理三台同类打印机可以用计数信号量：S = 3 mutex = 1 lp[1..3] = free申请：P(S); P(mutex); 找到一台 free 打印机，标记 used; V(mutex); 返回打印机编号;释放：P(mutex); lp[i] = free; V(mutex); V(S);这里 S 管理数量，mutex 保护打印机状态数组。10. 吸烟者问题传统 PV 操作无法很好解决“同时等待多个资源”的问题，因此引入：SP: 同时 P 多个信号量 SV: 同时 V 多个信号量关键原因：吸烟者必须同时获得两种材料； 如果分别 P，可能拿到一个材料后等待另一个，导致错误或死锁。11. 自行车生产线问题资源：信号量初值含义emptyk箱子空位frame0车架数wheel0车轮数工人 1 生产车架，工人 2 生产车轮，工人 3 取 1 个车架和 2 个车轮组装自行车。简单做法可能死锁：箱子里全是车轮，没有车架； 或者箱子里车架过多，车轮不足。解决：限制箱子中车架和车轮数量：S1 = k - 2 // 车架最多 k-2 个 S2 = k - 1 // 车轮最多 k-1 个保证始终有空间让另一类资源进入，避免死锁。12. 哲学家就餐问题5 个哲学家，5 把叉子，每人需要左右两把叉子才能吃饭。朴素做法：先拿左叉，再拿右叉可能死锁：每个人都拿到左叉，都等待右叉。文件给出的解法使用状态数组：state[i] = thinking / hungry / eating self[i] = 0 mutex = 1测试函数：如果 i 饥饿，且左右邻居都不在 eating， 则 state[i] = eating，并 V(self[i])思想：只有当左右邻居都不吃饭时，哲学家才能进入 eating 状态。这个解法能避免死锁，但仍可能存在饥饿问题。13. 寺庙问题资源：信号量初值含义empty30水缸空位full0水缸中的水bucket5水桶数mutex_bigjar1水缸互斥mutex_well1井口互斥小和尚：P(empty); P(bucket); P(mutex_well); 井中取水; V(mutex_well); P(mutex_bigjar); 水入缸; V(mutex_bigjar); V(bucket); V(full);老和尚：P(full); P(bucket); P(mutex_bigjar); 缸中取水; V(mutex_bigjar); 喝水; V(bucket); V(empty);重点：P 操作顺序不当会死锁。文件明确强调：若多个小和尚、老和尚存在，先 P(bucket) 再 P(empty) 可能在极限状态下造成死锁。14. PV 解题规则这是考试最重要的方法论。第一步：区分互斥和同步问题类型判断方式互斥多个进程不能同时访问某资源同步某动作必须在另一个动作之后发生第二步：设置信号量场景信号量初值互斥资源1同步等待0n 个同类资源n第三步：安排 P/V 顺序最重要规则：如果一个进程中既有同步 P，又有互斥 P： 必须先 P(同步信号量)，再 P(互斥信号量)。错误写法：P(mutex); P(empty);如果缓冲区满，进程拿着 mutex 等 empty，其他进程无法进入临界区释放 empty，就会死锁。正确写法：P(empty); P(mutex);四、条件临界区 CCR条件临界区形式：region r when b do s含义：进程执行 s 必须同时满足两个条件：1. 没有其他进程在 r 的临界区内 2. 条件 b 为 true例如：region buffer when count > 0 do 取出一个元素优点：比 PV 更高级，更接近问题描述。缺点：效率较低。原因是条件表达式 b 可能涉及局部信息，调度程序不能统一判断。被唤醒进程还要重新计算条件，可能再次等待，本质上仍有类似忙等的问题。五、管程 Monitor1. 管程的基本思想管程是一种集中式同步机制。它把：共享变量 访问共享变量的操作 同步控制封装在一个模块中。文件指出，PV 操作是分散式同步机制，灵活高效但容易写错；管程可读性更好、正确性更容易保证、便于维护。2. 管程的特点特点含义模块化共享变量和操作封装在一起信息隐藏外部不能直接访问共享变量自动互斥同一时刻最多一个进程在管程内执行条件变量用 wait/signal 实现等待和唤醒管程中的共享变量在外部不可见，外部只能调用管程提供的过程。3. 条件变量定义：condition c;操作：wait(c) signal(c)wait(c)含义：当前进程进入 c 的等待队列，并释放管程互斥权。signal(c)含义：如果 c 队列中有等待进程，则唤醒一个； 如果没有等待进程，则什么也不做。注意：管程的 signal 不等同于信号量的 V 操作。区别：对比信号量 V管程 signal是否累积信号可以累积 value不累积没人等待时value 增加什么也不做作用对象信号量条件变量队列4. Hoare、Hansen、Java 管程语义当 P 执行 signal(c) 唤醒 Q 时，有三种语义：类型谁继续执行Hoare 管程被唤醒的 Q 立即继续，P 进入紧急等待队列Hansen 管程Q 继续，P 离开管程，signal 必须是最后操作Java 管程P 继续执行，Q 等 P 退出后再竞争锁考试常考 Hoare 管程队列变化。文件例题中，P3 拥有管程，P4 申请进入后进入入口队列；P3 执行 signal(C) 后，条件队列中的 P2 被唤醒运行，P3 进入紧急等待队列，答案为 D。5. 管程实现生产者—消费者管程中定义：B[0..n-1] count in out condition pq, cq生产者调用：pc.put_in(item)消费者调用：pc.get_out(item)管程内部自动互斥，所以外部不需要手写 P(mutex) 和 V(mutex)。6. 管程实现读者—写者使用条件变量：rq wq reading_count write_count写者优先思想：如果有写者等待，新读者等待； 读者全部结束后唤醒写者； 写者结束后，如果还有写者继续唤醒写者，否则唤醒读者。7. 管程和 PV 操作的等价性文件指出：用 PV 操作可以构造管程； 用管程也可以构造 PV 操作。用管程构造信号量时，管程内部保存：count condition cP 操作：count--; if count < 0: wait(c);V 操作：count++; if count <= 0: signal(c);8. Java 中的类似管程机制Java 中：synchronized wait() notify() notifyAll()对应管程思想。重要规则：机制含义synchronized进入对象锁保护的临界区wait()释放锁，进入 wait setnotify()唤醒 wait set 中一个线程到 entry setnotifyAll()唤醒所有等待线程到 entry setJava 属于 signal and continue 语义，所以被唤醒线程不能立即执行，必须重新竞争锁。因此 Java 中等待条件通常写成：while (条件不满足) { wait(); }而不是：if (条件不满足) { wait(); }因为被唤醒后条件可能仍然不满足。六、Windows 互斥同步机制文件介绍了 Windows 中几种常见同步对象：机制作用Mutex互斥对象，可跨进程Semaphore计数信号量，限制并发数量Event事件通知机制CRITICAL_SECTION进程内线程互斥，速度较快区别：Mutex 可以跨进程，但开销较大； CRITICAL_SECTION 只能用于同一进程内线程，但更快； Semaphore 允许多个线程同时访问有限数量资源； Event 用于通知某个事件已经发生。七、4.4 进程高级通讯进程通讯分为：类型含义低级通讯互斥、同步、简单信号高级通讯传递大量信息高级通讯主要有两类：共享内存 消息传递文件将进程高级通讯分为 shared memory vs message passing、direct vs indirect、symmetric vs non-symmetric、buffering vs non-buffering 等类型。1. 共享内存模式多个进程共享同一块内存区域。优点：速度快，数据不需要反复复制。缺点：互斥和同步需要用户自己控制。典型 Linux 系统调用：调用作用shmget创建或获取共享内存shmat将共享内存附接到进程地址空间shmdt分离共享内存shmctl控制或删除共享内存共享内存适合同一台机器上的进程通信。2. 消息传递模式操作系统提供两个基本命令：send receive通信形式分为：直接通信进程直接指定对方。对称形式：send(R, message) receive(S, message)发送者指定接收者，接收者也指定发送者。非对称形式：send(R, message) receive(pid, message)发送者指定接收者，但接收者不指定具体发送者，适合 C/S 模型。3. 有缓冲消息传递发送者把消息复制到内核缓冲区，接收者再从缓冲区取。优点：发送者和接收者不必同时到达，并发性较好。缺点：需要缓冲区空间，且有数据复制开销。通常需要：信号量作用Sb缓冲池空闲缓冲区数量b_mutex缓冲池互斥Sm接收者消息队列中的消息数量m_mutex消息队列互斥4. 无缓冲消息传递没有中间缓冲区。特点：发送者 send 和接收者 receive 必须同时配合； 消息直接从发送进程空间复制到接收进程空间。优点：节省缓冲空间。缺点：并发性差，发送者或接收者可能长时间等待。5. 间接通信：信箱 Mailbox进程不直接指定对方，而是通过信箱通信。send_MB(MB, M) receive_MB(MB, N)信箱可以支持：多发送者—多接收者 多发送者—单接收者信箱本质上类似有界缓冲区。信箱结构通常包括：in, out s1 = k // 空位数 s2 = 0 // 消息数 mutex = 1 // 信箱互斥 letter[0..k-1]发送：P(s1); P(mutex); 放入信件; V(mutex); V(s2);接收：P(s2); P(mutex); 取出信件; V(mutex); V(s1);6. UNIX 管道 pipe管道是 UNIX 中一种高级通信机制。特点：管道是特殊文件； 没有名字； 有两个文件描述符：读端 fd[0]，写端 fd[1]； 通常用于具有共同祖先的进程之间通信。调用：int fd[2]; pipe(fd);使用：fd[0] 读 fd[1] 写优点：实现简单，接口统一，节省内存。缺点：速度较慢。管道写端关闭后，读端读完已有数据后会返回 0，表示文件结束。7. UNIX 信号 Signal信号是一种异步通知机制。常见例子：操作信号默认行为Ctrl+CSIGINT终止进程Ctrl+ZSIGTSTP暂停进程kill -9SIGKILL强制终止信号来源：键盘 终端命令 程序调用 硬件异常或内核信号处理方式：方式含义忽略不处理该信号捕捉执行用户定义处理函数默认行为执行系统默认动作注意：SIGKILL 和 SIGSTOP 不能被捕捉，也不能被忽略。信号和中断的区别：对比信号中断是否异步是是优先级一般无优先级有优先级处理态用户态核态响应速度延迟较大通常及时八、本章考点1. 概念辨析并发 vs 并行 互斥 vs 同步 临界资源 vs 临界区 忙等 vs 阻塞 信号量 vs 条件变量 共享内存 vs 消息传递 直接通信 vs 间接通信 有缓冲 vs 无缓冲2. Bernstein 条件看到读集、写集题，直接判断：读写冲突？ 写读冲突？ 写写冲突？只要三个交集都为空，就可以并发。3. 临界区管理三条件背熟：互斥性 进展性 有限等待判断算法时逐条检查。4. PV 操作题考试常见。通用步骤：1. 找资源 2. 找同步顺序 3. 定义信号量 4. 写每类进程代码 5. 检查 P/V 顺序 6. 检查是否死锁、饥饿、互斥遗漏最重要口诀：互斥信号量初值 1； 同步信号量初值 0； 资源信号量初值为资源数； 先 P 同步，再 P 互斥； 谁等待谁 P，谁完成谁 V。5. 经典问题模板必须掌握重点掌握：生产者—消费者 读者—写者 哲学家就餐 障栅 barrier 多资源管理 寺庙取水问题 单缓冲/双缓冲流水线问题这些题本质都是 PV 操作组合。6. 管程考点重点掌握：管程自动互斥； 共享变量封装在管程内部； wait 会释放管程互斥权； signal 不等于 V； Hoare / Hansen / Java 三种语义区别； Java 中 wait 要放在 while 中。九、小结本章研究并发进程带来的互斥、同步和通信问题。并发执行可以提高系统效率，但由于进程交叉执行并访问共享变量，会产生与时间有关的错误。为保持结果正确，需要对临界资源实施互斥，对合作进程实施同步。互斥要求满足互斥性、进展性和有限等待，可通过软件算法、硬件原子指令、关中断、信号量和管程等机制实现。信号量通过 P/V 操作管理资源、实现互斥和同步，是解决生产者消费者、读者写者、哲学家就餐等经典问题的重要工具。管程将共享变量及其操作封装起来，提供更高级、更安全的同步机制。进程高级通信包括共享内存和消息传递，消息传递又可分为直接/间接、有缓冲/无缓冲等方式。Linux/UNIX 中常见通信机制包括管道、信号和共享内存。死锁与饥饿本章主线：什么是死锁 → 为什么会死锁 → 如何表示死锁 → 如何预防、避免、检测、恢复 → 死锁与饥饿/活锁的区别 → 典型问题分析一、核心框架两个现象：死锁：多个进程因为竞争资源，相互等待对方释放资源，导致谁也无法继续执行。它通常表现为“无限等待”，可以通过算法检测出来。饥饿：资源分配策略不公平，导致某个进程长期得不到资源。饥饿不一定处于阻塞等待状态，也可能处于忙等状态。忙等状态下的饥饿称为活锁。本章目录：部分核心内容5.1死锁概念5.2死锁类型5.3死锁条件5.4资源分配图5.5—5.10死锁处理：预防、避免、检测、恢复、鸵鸟算法5.11若干补充问题5.12饥饿与活锁5.13简单组合资源死锁静态分析5.14同种组合资源死锁的必要条件二、5.1 死锁的概念1. 死锁定义一组进程中的每一个进程，都无限期等待该组中其他进程所占有的资源，导致永远无法继续执行，这种现象叫进程死锁。例如：P1：申请 r1 → 再申请 r2 P2：申请 r2 → 再申请 r1如果 P1 已经拿到 r1，P2 已经拿到 r2，那么 P1 等 r2，P2 等 r1，双方互相等待，就形成死锁。课件第5—6页用“厨房互相等待”“两头牛过桥”的图直观说明：双方都占着一部分条件，同时等待对方先让步，结果谁也走不了。2. 由定义得到的结论死锁有几个重要结论：参与死锁的进程至少有两个。每个参与死锁的进程都在等待资源。参与死锁的进程中至少有两个进程已经占有资源。死锁进程只是系统当前全部进程集合的一个子集，不一定所有进程都死锁。3. 死锁产生原因从两个角度理解：时间上：进程推进==顺序不当==。也就是调度时机不合适，本来不该切换的时候切换，可能导致资源交叉占有。空间上：独占资源==分配不当==。互斥资源一旦被部分分配，又不能被剥夺，就容易形成死锁。三、5.2 死锁类型三类：1. 竞争资源引起的死锁这是最常见的死锁。不同种资源死锁例如道路交叉口、多个方向车辆各自占用某段路，又等待下一段路。资源类型不同，但相互等待。同种资源死锁例如有 4 台打印机，两个进程都需要多台打印机。若资源被分散占有，每个进程都等不到足够数量，也可能死锁。2. 进程通信引起的死锁例如：P1：receive(P2, M1) P2：receive(P3, M2) P3：receive(P1, M3)P1 等 P2，P2 等 P3，P3 等 P1，形成通信等待环。3. 其他原因引起的死锁“After you / after you” 表示：双方都等对方先行动，结果谁也不行动。四、5.3 死锁的四个必要条件：Coffman 条件死锁发生必须同时满足四个条件：条件含义资源独占 mutual exclusion资源一次只能被一个进程使用不可剥夺 non-preemption资源分配后不能强行抢走保持申请 hold-while-applying进程已经占有一些资源，同时又申请新资源循环等待 circular wait存在 P1 等 P2，P2 等 P3，……，Pn 等 P1 的等待环这四个条件是必要条件。如果每类资源只有一个实例，那么它们也是充要条件。也就是说：单实例资源系统中，只要四个条件同时成立，就一定死锁。所以：破坏 Coffman 四个条件中的任意一个，就可以预防死锁。五、5.4 资源分配图资源分配图是判断死锁的重要工具。1. 图的构成系统资源分配图记为：G = (V, E) V = P ∪ R其中：P 是进程集合：P1、P2、……R 是资源集合：R1、R2、……申请边：Pi → Rj，表示进程 Pi 申请资源 Rj。分配边：Rj → Pi，表示资源 Rj 已经分配给进程 Pi。2. 图中环路与死锁的关系重点结论：资源分配图无环路，则一定无死锁。资源分配图有环路，则可能死锁。如果每类资源只有一个实例，有环路就意味着死锁。如果某类资源有多个实例，有环路不一定死锁。判断题：“有环一定死锁”是错的。 正确说法是：无环一定无死锁；有环不一定死锁。3. 资源分配图约简约简算法的思想是：看看系统中有没有某个进程可以顺利完成。如果可以完成，就假设它释放所有资源，然后继续判断其他进程。步骤：找一个非孤立且没有请求边的进程 Pi。删除 Pi 的所有分配边，使它成为孤立点。找所有请求都可以满足的进程，把请求边改为分配边。重复上述过程。如果最后所有节点都变成孤立点，说明图完全可约简，没有死锁。如果不能完全约简，说明有死锁。4. 死锁定理系统 S 为死锁状态的充分必要条件是：S 的资源分配图不可完全约简。六、5.5 死锁处理方法总览死锁处理有四类：方法思想死锁预防静态限制资源申请方式，使死锁不可能发生死锁避免动态检查每次资源请求，避免进入不安全状态死锁检测允许死锁发生，但定期/适时检测死锁恢复检测到死锁后采取措施解除可以这样理解：**预防：事前立规矩。 避免：分配前先算一算。 检测：出事后查一查。 恢复：查出来后救一救。**七、5.6 死锁预防死锁预防的目标是：破坏 Coffman 四个条件中的至少一个。1. 预先分配法进程运行前一次性申请全部资源。系统规则：如果能满足，就一次性全部分配。如果不能满足，一个也不分配。这样进程运行期间不再申请新资源，所以破坏了“保持申请”条件。优点：简单。缺点：资源利用率低。进程很难提前知道自己需要的全部资源。有些资源暂时不用也被提前占有。2. 有序分配法给所有资源编号：F(r1) < F(r2) < ... < F(rn)规定进程必须按照资源编号从小到大的顺序申请资源。例如：scanner = 1 tape = 2 printer = 3进程如果已经占有编号较大的资源，就不能再申请编号较小的资源。它破坏的是：循环等待条件。证明思路常考：反证法。 假设出现循环等待：P1 等 R1，被 P2 占有 P2 等 R2，被 P3 占有 ... Pn 等 Rn，被 P1 占有根据有序申请，应该有：F(R1) < F(R2) < ... < F(Rn) < F(R1)这显然矛盾，所以不会死锁。缺点：资源编号困难。增加程序员负担。为了按顺序申请，可能提前申请暂时不用的资源，降低资源利用率。如果程序不遵守规则，仍可能死锁。八、5.7 死锁避免与银行家算法死锁避免是动态策略： 每次资源请求到来时，系统先判断分配后是否安全，安全才分配，不安全就等待。1. 安全状态如果系统存在一个安全进程序列：<P1, P2, ..., Pn>使得这些进程可以依次完成，那么系统处于安全状态。安全序列的判断条件是：Pi 还需要的资源 ≤ 当前剩余资源 + 前面已完成进程释放的资源要理解三者关系：安全状态 ⊄ 死锁 不安全状态 ≠ 死锁 死锁状态 ⊂ 不安全状态也就是说：安全状态一定不会死锁。不安全状态不一定已经死锁。死锁一定是不安全状态。2. 银行家算法的数据结构银行家算法要求进程提前声明最大资源需求。常用数据结构：名称含义Available系统当前可用资源Claim / Max每个进程最大需求Allocation当前已经分配给每个进程的资源Need每个进程尚需资源Request某个进程当前请求资源Work临时可用资源Finish记录进程是否可完成其中：Need = Claim - Allocation3. 银行家算法的资源请求流程当 Pi 请求资源 $Request[i]$ 时：第一步：Request[i] ≤ Need[i] ?如果不满足，说明请求超过最大声明，出错。第二步：Request[i] ≤ Available ?如果不满足，说明当前资源不够，Pi 等待。第三步：试探性分配：Available = Available - Request[i] Allocation[i] = Allocation[i] + Request[i] Need[i] = Need[i] - Request[i]第四步：做安全性检查。如果安全，正式分配。 如果不安全，撤销试探分配，让 Pi 等待。4. 安全性检测算法核心步骤：Work = Available Finish[i] = false 循环寻找某个进程 i： Finish[i] == false 且 Need[i] ≤ Work 如果找到： Finish[i] = true Work = Work + Allocation[i] 最后如果所有 Finish[i] 都为 true，则安全； 否则不安全。考试做银行家算法题时，一定要写出安全序列，例如：<P1, P3, P4, P2, P0>例子中，P2 请求 (1,0,2) 后仍能找到安全序列，所以可以分配；P4 请求 (3,3,0) 时当前资源不足，等待；P0 请求 (0,2,1) 虽然可试分配，但会导致不安全，所以撤销分配。5. 银行家算法的保守性银行家算法只知道进程的最大需求，不知道进程未来具体的申请和释放顺序。因此它会按最坏情况判断。所以：银行家算法可能拒绝某些实际上不会导致死锁的请求。这就是它的保守性。第62—67页专门举例说明：某些请求被判为“不安全”而不分配，但如果真的分配，进程仍可能通过特定执行顺序全部完成。九、5.8 死锁检测如果系统既没有死锁预防，也没有死锁避免，就可能发生死锁。这时需要死锁检测。1. 检测算法的数据结构和银行家算法相似，但更简单：名称含义Available当前可用资源Allocation当前分配矩阵Request当前请求矩阵Work临时可用资源Finish是否可完成注意： 死锁检测算法没有 Claim / Need，因为它不关心最大需求，只关心当前请求能否满足。2. 检测算法思想Work = Available Finish[i] = false 若某进程 i： Finish[i] == false 且 Request[i] ≤ Work 则说明它可以继续执行并释放资源： Finish[i] = true Work = Work + Allocation[i]最后：如果所有 Finish 都为 true，无死锁。如果某些 Finish 为 false，这些进程就是参与死锁的进程。优化点：如果某进程当前没有占有资源，即：Allocation[i] = 0那么初始化时可令：Finish[i] = true因为它不占有资源，不会阻塞别人，通常不用参与检测。3. 什么时候检测？常见时机：进程等待资源时检测。定时检测。CPU 等资源利用率下降时检测。交互式任务长期无响应时检测。十、5.9 死锁恢复检测到死锁后，需要恢复。方法有四种。1. 重新启动最简单、最常用，但代价很大。2. 终止进程有两种：一次性终止所有死锁进程。逐步终止，根据优先级或代价函数选择牺牲哪个进程。3. 剥夺资源强行从某些进程那里拿回资源。也有两种：一次剥夺。逐步剥夺。4. 进程回退让进程回退到之前某个安全点重新执行。 缺点是系统开销大，需要保存检查点。十一、5.10 鸵鸟算法鸵鸟算法就是：假装看不见死锁。工程观点认为：死锁发生频率可能很低。处理死锁的日常开销可能远大于偶尔死锁造成的损失。用户可能更愿意忍受偶发故障，而不是让系统长期变慢。课件提到 UNIX、Windows 等商用系统在很多情况下采用这种思路。数学观点则认为： 只要理论上会死锁，就必须处理。考试中可以这样答：鸵鸟算法是一种工程折中策略，适用于死锁发生概率低、处理成本高、系统更重视平均性能的场景。十二、5.11 有关问题讨论1. 关于充要性算法死锁预防没有运行时检测开销，但限制多、资源利用率低。死锁避免开销大，而且银行家算法不是必要性算法。它只知道最大资源需求，不知道未来具体资源申请和释放顺序，因此只能保守判断。如果要做真正充要的判断，需要知道每个进程完整的资源命令序列，但由于程序有分支和循环，这通常很困难。2. 消耗型资源消耗型资源也叫生灭资源，例如消息。send 产生消息。receive 消耗消息。消息通信也可能死锁。常见解决方式是：超时技术。3. 可剥夺资源CPU 是可剥夺资源，可以被高优先级进程抢占，因此多个进程一般不会因为竞争 CPU 而死锁。内存也可以通过外存交换缓解。若外存也不足，理论上仍可能死锁，但实际较少见。4. 两阶段锁两阶段封锁协议包括：增长阶段：只加锁，不释放锁。消退阶段：只释放锁，不加锁。死锁可能发生在增长阶段。十三、5.12 饥饿与活锁1. 饥饿和死锁的区别对比点死锁饥饿等待对象等待永远得不到的资源等待可能释放、但一直分不到的资源是否循环等待一定有循环等待不一定进程数量至少两个进程可能只有一个进程状态通常处于等待状态可能等待，也可能忙等能否解除通常需外部干预可能通过公平调度解除2. 饥饿定义如果一个进程等待时间过长，已经明显影响推进和响应，就称为饥饿。如果等待到某种程度，即使任务完成也失去意义，就称为饿死。3. 排队等待与忙式等待排队等待：进程申请不到资源就阻塞，等资源可用后再唤醒。优点：不浪费 CPU。 缺点：需要进程切换，有系统开销。忙式等待：进程不阻塞，而是不断检查资源是否可用。如果忙式等待时间过长，就可能形成活锁。4. 活锁活锁是忙式等待条件下发生的饥饿。活锁和死锁不同：死锁通常完全停住。活锁中进程还在运行，但没有实质进展。活锁可能解除。UNIX v6 中进程表项耗尽的例子说明：进程不断尝试 fork()，失败后睡眠再试，若一直得不到表项，就形成活锁；但如果某些子进程结束，表项释放，活锁可能解除。十四、哲学家就餐问题这是死锁问题的经典例子。1. 问题描述5 个哲学家围坐圆桌，每两人之间有一把叉子。每个哲学家要吃饭，必须同时拿到左、右两把叉子。注意：课件强调，叉子不是“同种组合资源”，因为每把叉子的位置不同。第92—96页图展示了 5 个哲学家和 5 把叉子的环形关系。2. 错误做法每个哲学家都先拿左叉，再拿右叉：P(fork[i]) P(fork[(i+1) mod 5]) eat() V(fork[(i+1) mod 5]) V(fork[i])如果 5 个哲学家同时拿到左叉，再都等待右叉，就死锁。3. 防止死锁的三种方法这里给出三种思路：最多只允许 4 个哲学家同时坐到桌边。只有左右两把叉子都可用时，才允许哲学家拿叉子。奇数号哲学家先拿左叉，偶数号哲学家先拿右叉，破坏循环等待。4. PV 操作解法思想引入：state[i] = thinking / hungry / eating self[i] = 每个哲学家自己的信号量 mutex = 保护 state 的互斥信号量核心测试函数：如果 i 号哲学家 hungry， 且左右邻居都不是 eating， 则让 i 进入 eating，并唤醒 i。注意： 共享变量 state 的修改必须放在临界区中，所以需要 mutex。这个解法可以避免死锁，但课件也指出可能存在饿死问题：某哲学家的左右邻居交替进食，导致它长期无法进食。十五、过河问题过河问题可以看成多个进程竞争石块资源。1. 简单过河问题最简单策略是：同一时刻只允许一个方向过河。这可以预防死锁，但可能导致：饥饿或饿死。并行度低。2. 石块桥过河问题一个更复杂的石块桥问题：西岸到东岸路线：1-2-3-4-6-5东岸到西岸路线：5-6-7-8-2-1每个石块最多容纳一个人。死锁情况包括：两个方向各有 3 人踏上石块时，必发生死锁。两个方向各有 1 人分别踏上 1、2 或 5、6 时，也可能发生死锁。3. 解法思想一种方法是：限制同时过河人数不超过 5。对竞争严重的石块对 1、2 和 5、6 使用有序分配法。用信号量表示石块资源。用额外信号量限制总人数。后面进一步提高并行度，在某些情况下最大并行度可以达到 8，但实现更复杂，需要记录东西方向正在过河人数、等待人数，并配合等待队列。理解：过河者 = 进程；石块 = 独占资源；路线 = 资源申请顺序。 死锁预防的核心是限制并发数量和规定资源申请顺序。十六、5.13 简单组合资源死锁的静态分析这一节比较偏证明和分析题。1. 基本概念可复用资源：一次只能分配给一个进程使用的资源。组合资源：由若干相对独立的子资源构成的资源集合。同种组合资源：由相同类型子资源构成的组合资源。简单组合资源：每类子资源只有一个资源实例。2. 静态分析条件已知每个进程的资源活动序列，判断是否可能死锁。3. 分析步骤第一步：把每个进程“占有资源、申请资源”的情况写成状态：(pi : aj : ak1, ak2, ..., akn)含义：进程 pi 正在请求 aj，同时已经占有 ak1, ak2, ..., akn第二步：每个状态作为一个节点。第三步：如果 P1 申请的资源被 P2 占有，就从 P1 状态节点向 P2 状态节点画有向边。第四步：找所有环路。第五步：判断环路上的状态能否同时到达。不能同时到达的两种情况：环路中有相同进程。环路中有相同被占有资源。如果环路上的状态可以同时到达，则有死锁可能。第137—138页图展示了多个环路的判断：有的环路因为含有相同进程而不可达，有的因为要求同一资源同时被不同状态占有而不可达，有的环路则可以同时到达，因此会产生死锁。十七、5.14 同种组合资源死锁的必要条件这一节给出一个非常重要的判定公式。设：M：资源数量 N：使用该类资源的进程数量 Σ：所有进程需要该类资源的总量结论：如果 Σ < M + N，则一定没有死锁。 如果 Σ ≥ M + N，则至少存在某种交叉执行可能发生死锁。证明思路：假设有 n 个进程参与死锁：2 ≤ n ≤ N参与死锁的进程所需资源总量至少为：M + n未参与死锁的进程至少各需要 1 个资源，所以总需求至少为：N - n因此：Σ ≥ M + n + N - n = M + N所以如果：Σ < M + N就不可能死锁。例子例1：M = 15 N = 4 P1(4), P2(6), P3(1), P4(7) Σ = 4 + 6 + 1 + 7 = 18 M + N = 19 Σ < M + N所以无死锁可能。例2：M = 15 N = 4 P1(5), P2(6), P3(1), P4(7) Σ = 5 + 6 + 1 + 7 = 19 M + N = 19 Σ ≥ M + N所以存在死锁可能。十八、考试重点1. 必背概念死锁定义死锁四个必要条件资源分配图中“无环一定无死锁，有环不一定死锁”死锁定理：资源分配图不可完全约简是死锁的充要条件安全状态、安全序列、不安全状态、死锁状态之间的关系饥饿、活锁与死锁的区别2. 算法资源分配图约简银行家算法安全性检测算法死锁检测算法简单组合资源静态死锁分析同种组合资源公式：Σ < M + N 则无死锁3. 银行家算法模板1. 计算 Need = Max - Allocation 2. 判断 Request ≤ Need 3. 判断 Request ≤ Available 4. 试分配 5. 用安全性算法寻找安全序列 6. 若存在安全序列，则分配；否则撤销分配4. 死锁检测模板1. Work = Available 2. Finish[i] = false 若 Allocation[i] = 0，可令 Finish[i] = true 3. 找 Request[i] ≤ Work 的进程 4. Work = Work + Allocation[i] 5. 重复 6. 若最后有 Finish[i] = false，则这些进程死锁十九、小结死锁是多个进程互相等待对方资源而无限阻塞。死锁产生必须满足资源独占、不可剥夺、保持申请、循环等待四个 Coffman 条件。资源分配图无环一定无死锁，有环可能死锁；不可完全约简是死锁的充要条件。处理死锁有预防、避免、检测、恢复四类方法。预防通过破坏必要条件，避免通过银行家算法保持安全状态，检测通过 Work/Finish 判断当前是否已有死锁，恢复可重启、终止进程、剥夺资源或回退。饥饿是长期得不到资源，忙等形式的饥饿称为活锁。哲学家就餐、过河问题、组合资源分析都是死锁思想的典型应用。存储管理本章主线：操作系统如何管理内存，让多个进程安全、高效、灵活地共享有限的主存空间。第六章 主存管理本章可以分成四大部分：存储管理功能：操作系统为什么要管理内存，要完成哪些任务。内存资源管理：内存空闲区如何记录、如何分配、如何回收。存储管理方式：界地址、分页、分段、段页式四种管理方式。Linux 伙伴堆算法：Linux 如何管理连续页框，如何分配和合并空闲内存块。程序给出的是逻辑地址，CPU真正访问的是物理地址。 主存管理的核心，就是完成“逻辑地址到物理地址的转换”，同时解决分配、回收、共享、保护、扩充和碎片问题。6.1 存储管理功能1. 存储分配和去配操作系统需要决定：给哪个进程分配内存；分配多大；分配在哪里；进程结束后如何回收。分配和去配的对象包括内存和外存，方法上具有相似性。分配和回收通常发生在：进程创建；进程撤销；进程交换；进程长度变化，例如栈溢出或栈扩展。为了管理这些空间，系统需要两类表：表作用分配表记录已经分配出去的区域空闲表记录尚未分配的区域问：操作系统为什么需要空闲表和分配表？ 答：为了知道哪些内存已经被占用，哪些内存还可以继续分配。2. 存储共享存储共享的目的有两个：第一，节省内存。例如多个进程运行同一个程序时，代码段可以只在内存中保存一份。第二，进程间通信。多个进程可以通过共享数据段交换信息。共享内容包括：代码；数据。注意：代码段通常可以共享，因为代码一般是只读的；数据段通常不能随便共享，因为不同进程的数据应该相互隔离。3. 存储保护存储保护主要解决两个问题：问题含义地址越界进程访问了不属于自己的地址操作越权进程对某区域进行了不允许的操作，例如写只读代码段例如，一个进程只能访问自己的内存空间，不能随便读写操作系统内核或其他进程的数据。4. 存储扩充存储扩充指的是把内存和外存结合起来，形成虚拟存储体系。理想目标是：速度接近内存，容量接近外存。基本思想是：正在运行的程序放在内存中；暂时不用的程序或页面放到外存中；需要时再调入内存。这就是后面虚拟存储、换入换出、页面置换等内容的基础。5. 地址映射地址映射是本章最核心的概念之一。程序中使用的地址叫：逻辑地址内存硬件真正访问的地址叫：物理地址地址映射就是：逻辑地址 → 物理地址文件中提到三种地址映射方式：地址映射方式映射时机特点固定地址映射编译时程序必须装入指定位置，容易冲突静态地址映射程序装入时装入后不能移动动态地址映射程序运行时每次访问内存时转换，需要硬件支持固定地址映射在编译时就确定逻辑地址和物理地址的对应关系。缺点很明显：程序必须放在指定内存位置。如果那个位置已经被占用，就会发生冲突。静态地址映射程序装入内存时，由操作系统完成地址映射。公式是：物理地址 = 逻辑地址 + 装入地址特点：程序运行前确定映射关系；程序装入后不能移动；程序必须占用连续内存空间。动态地址映射在程序运行过程中，每次访问内存时才进行地址转换。例如执行：MOV AX, [500]访问逻辑地址 500 时，硬件临时把它转换为物理地址。动态映射需要硬件支持，例如：MMU，内存管理单元；基址寄存器；限长寄存器；快表 TLB。它的优点是灵活，程序在内存中可以移动；缺点是地址转换有额外开销。6. 实际存储体系文件中给出三级存储体系：Cache + 内存 + 辅存层次特点Cache快、小、贵内存速度和容量适中辅存慢、大、便宜核心思想：当内存不够时，用辅存支持内存。暂时不用的程序可以换出到辅存，需要时再换入内存。7. 局部性原理局部性原理是 Cache、虚拟存储、分页管理能够高效工作的基础。分为两种：时间局部性正在访问的存储单元，在不久之后还可能再次被访问。例如循环中的变量反复被使用。空间局部性程序在短时间内访问的地址，往往集中在较小范围内。例如顺序执行数组元素：a[0], a[1], a[2], a[3]这些地址相邻，所以具有空间局部性。6.2 内存资源管理这一节主要讲：空闲内存怎么表示，怎么分配，怎么回收。1. 内存分区方式按照分区时刻，可以分为：类型含义静态分区系统初始化时划分动态分区系统运行过程中按申请划分按照分区大小，可以分为：类型含义等长分区每个分区大小相同异长分区分区大小不固定常见组合：组合典型管理方式静态 + 等长页式、段页式动态 + 异长段式、界地址2. 静态等长分区的分配静态等长分区常用于分页管理。因为每个页框大小相同，所以管理方式比较简单。三种方法：1）位示图 / 字位映象图用一个 bit 表示一个页面状态：bit 值含义0空闲1占用分配时：从头寻找第一个为 0 的 bit，改为 1，返回页号。回收时：把对应页号的 bit 改回 0。优点是结构紧凑，缺点是如果内存很大，查找空闲位可能需要扫描较多位置。2）空闲页面表把若干连续空闲页面作为一组登记在表中。表项包括：字段含义首页号连续空闲页面的起始页号空页数连续空闲页面数量例如：首页号空页数1204表示第 120、121、122、123 页都是空闲的。3）空闲页面链把所有空闲页面连成链表。优点：节省空间。缺点：对外存页面的分配和去配需要执行数据传输，速度较慢。3. 动态异长分区的分配动态异长分区不是一开始固定划分，而是根据进程申请的大小动态切割内存。它使用：空闲区域表表中记录：字段含义空闲区首址空闲区起始地址空闲区长度空闲区大小==动态分区==常用四种分配算法：最先适应 First Fit；下次适应 Next Fit；最佳适应 Best Fit；最坏适应 Worst Fit。4. 最先适应算法 First Fit空闲区按首地址递增排列。申请内存时，从头开始找，找到第一个大小足够的空闲区就分配。优点：尽量使用低地址空间，高地址区可以保留较大的空闲区域。缺点：可能频繁切割前面的空闲区，形成碎片。5. 下次适应算法 Next Fit它和 First Fit 类似，但不是每次从头找，而是：从上次分配位置的下一个位置继续找。优点：减少查找时间，使空闲区域分布更均匀。缺点：也可能切割较大的空闲区。6. 最佳适应算法 Best Fit申请内存时，选择：能满足申请要求的最小空闲区。优点：尽量保留大空闲区。缺点：容易留下很小的碎片。例如申请 30KB，如果有一个 32KB 的空闲区，Best Fit 会使用它，剩下 2KB 碎片。7. 最坏适应算法 Worst Fit申请内存时，选择：最大的空闲区。优点：分割后剩余区域仍然比较大，不容易形成极小碎片。缺点：会破坏大的空闲区域。8. 四种动态分区算法对比算法选择策略优点缺点First Fit第一个满足要求的空闲区简单，速度较快低地址碎片多Next Fit从上次位置继续找查找开销较小可能破坏大空闲区Best Fit最小满足区保留大空闲区容易产生小碎片Worst Fit最大空闲区剩余区较大大空闲区被破坏题目给一串“分配、释放、再分配”的操作，让画内存变化图，问最后最大空闲分区是多少。做题方法：画一条内存条；按顺序执行分配和释放；每次分配时根据算法选择空闲区；注意释放后相邻空闲区要合并；最后看最大空闲分区。9. 碎片与紧凑动态异长分区容易形成很小的空闲区域，这些小区域叫：碎片 fragment碎片太多会造成内存浪费。解决方法是：紧凑 compaction紧凑就是移动已经占用的区域，让所有空闲区合并成一个大的连续空闲区。优点：可以得到大的连续空闲空间。缺点：开销很大，需要移动进程，可能导致系统暂停。紧凑时机包括：系统找不到足够大的空闲区时；定期进行；空闲区管理过于复杂时。6.3 存储管理方式这一节是全章最重要的部分四种方式：管理方式地址维度内存划分进程划分界地址管理一维地址动态异长一个进程一个连续区页式管理一维地址静态等长页框静态等长页面段式管理二维地址动态异长段按程序逻辑划分段段页式管理二维地址静态等长页框先分段，段内分页6.3.1 界地址管理方式1. 基本思想界地址管理是最简单的内存管理方式。它的特点是：一个进程占用一个连续的内存区域。进程逻辑地址从 0 到 l-1。假设进程装入内存的起始地址是 b，进程长度是 l。那么逻辑地址 a 对应的物理地址是：物理地址 = b + a但前提是：0 ≤ a ≤ l - 1如果不满足，就越界。2. 需要的寄存器界地址管理需要两个寄存器：寄存器作用基址寄存器 base保存进程在内存中的起始地址 b限长寄存器 limit保存进程长度 l地址转换过程：CPU 产生逻辑地址 a；判断 a 是否小于 l；如果越界，产生中断；如果合法，物理地址 = b + a。3. 单对界和双对界单对界一个进程只有一对 base 和 limit。特点：整个进程占用一个连续内存区。双对界一个进程可以有两个连续区域：代码空间 I 空间；数据空间 D 空间。代码区可以被多个进程共享，数据区通常由进程独享。4. 交换技术 Swapping当内存不够时，系统可以把暂时不能运行的进程写到磁盘，叫：换出 Swap Out当进程需要运行时，再从磁盘写回内存，叫：换入 Swap In缺点：换入换出是 I/O 操作，开销大；交换单位太大，通常是整个进程；需要考虑外存交换区管理；换入时可能需要重新定位地址。5. 重定位因为进程换出后再次换入内存，位置可能变了，所以需要重定位。相关概念：概念含义可重定位程序程序与具体内存位置无关浮动程序从 0 开始编址的程序静态重定位运行前完成重定位动态重定位运行时完成重定位6. 覆盖技术 Overlay覆盖技术的目的：在较小内存中运行较大程序。它把程序分成多个段：常驻区：放全局代码和数据；覆盖区：多个程序段轮流使用同一块内存。例如一个程序总大小 190KB，但实际内存只有 110KB，可以让不同时执行的模块共用覆盖区。缺点：需要程序员手动划分模块；需要从外存反复装入模块，执行时间变长。6.3.2 页式存储管理 Paging==分页==是本章最重要的内容之一1. 基本思想分页管理中：==内存==被划分成大小相同的块，叫页框 frame；==进程逻辑空间==也被划分成同样大小的块，叫页面 page。页面可以装入任意页框，不需要连续。2. 页式地址结构逻辑地址由两部分组成：逻辑地址 = 逻辑页号 p + 页内地址 d物理地址由两部分组成：物理地址 = 页框号 f + 页内地址 d页内地址 d 不变，只需要把逻辑页号 p 转换成页框号 f。所以分页地址转换的本质是：p → f3. 页表每个进程有一张页表。页表记录：逻辑页号页框号015122216332含义是：逻辑第 0 页在物理第 15 个页框；逻辑第 1 页在物理第 22 个页框；以此类推。4. 页式地址转换步骤逻辑地址为：(p, d)转换过程：CPU 产生逻辑地址 p, d；查快表 TLB，看是否有 p 对应的页框号 f；如果 TLB 命中，直接得到 f；如果没有命中，则查页表；判断页号 p 是否越界；从页表中找到页框号 f；将 p → f 的映射放入快表；得到物理地址 f, d。5. 快表 TLB快表是一组联想寄存器，用来保存当前进程页表中的一部分常用表项。为什么需要快表？因为如果没有快表，每次访问数据都需要：访问页表；再访问真正的数据。也就是一次逻辑访问变成两次内存访问。有快表后，如果命中，就可以少访问一次页表。6. 有效访问时间 EAT文件中给出的单级页表 EAT 公式是：EAT = 命中率 × (快表访问时间 + 内存访问时间) + (1 - 命中率) × (快表访问时间 + 2 × 内存访问时间)例如：命中率 = 98% 快表访问时间 = 20ns 内存访问时间 = 100ns则：EAT = 98% × (20 + 100) + 2% × (20 + 200) = 122ns这个公式考试常考7. 多级页表为什么需要多级页表？因为单级页表可能太大。例如：32 位地址空间是 4GB；页大小 4KB；页内地址占 12 位；页号占 20 位；页表最多有 2²⁰ 个表项；如果每个表项 4 字节，页表大小是 4MB；这 4MB 页表还需要连续的 1K 个页框存放。问题：页表太大；需要连续空间；进程地址空间中有很多空洞，页表项浪费严重。解决方法：二级页表或多级页表。8. 二级页表二级页表把大页表再分页。结构：逻辑地址 = 外层页号 p1 + 内层页号 p2 + 页内地址 d地址转换：用 p1 查页目录表；找到对应页表页；用 p2 查页表页；得到页框号 f；形成物理地址 f, d。特点：页目录表常驻内存；页表页可以按需调入；页表页之间不需要连续；减少了连续内存需求。缺点：如果没有快表，访问数据需要三次内存访问：页目录表、页表页、数据单元。9. 四级页表 EAT文件中给出四级页表的 EAT 公式：EAT = 命中率 × (快表访问时间 + 内存访问时间) + (1 - 命中率) × (快表访问时间 + 5 × 内存访问时间)例如：98% × (20 + 100) + 2% × (20 + 500) = 128ns这里未命中时要访问 5 次内存：四级页表 + 实际数据。10. 反置页表 Inverted Page Table传统页表是：每个进程一个页表，每个逻辑页一个表项。如果虚拟地址空间很大，传统页表会很大。反置页表改为：面向物理内存，每个物理页框一个表项。表项内容是：(pid, p)即：哪个进程；哪个逻辑页；占用了这个物理页框。优点：表大小固定，只和物理页框数有关。缺点：地址转换时查找困难。因为给定 pid 和逻辑页号 p，需要反过来找它在哪个物理页框中。11. 反置页表的 Hash 技术为了加快反置页表查找，可以使用杂凑 Hash 技术。过程：根据 hash(pid, p) 计算入口；从入口开始查找；找到匹配的 pid 和 p；表项位置就是页框号 f；得到物理地址 f, d。为了进一步提高速度，也可以使用 TLB 缓冲。12. 页式存储管理优缺点优点：消除了外部碎片。因为进程的页面可以分散放在任意空闲页框中，不需要连续内存。缺点：会产生页内零头，也就是内部碎片。如果一个进程大小不是页大小的整数倍，最后一页可能用不满。平均来说，一个进程页内零头大约是半页。页式地址转换例题例子：进程大小 8KB；页面大小 2KB；页面依次装入内存第 7、9、2、5 块；求逻辑地址 0AEF 的物理地址。页面大小 2KB = 2048B = 2¹¹，所以页内地址占 11 位。逻辑地址 0AEF 十六进制转换时，可以分为：逻辑页号 + 页内地址根据文件中的结果，逻辑页号为 1，页内地址为 2EF。页表中：逻辑页 1 → 页框 9所以物理地址为：页框号 9 + 页内地址 2EF即：物理地址 = 9 × 2KB + 2EF文件中转换结果为：4AEF你做这类题时只要记住：页内地址不变，只替换页号为页框号。6.3.3 段式存储管理 Segmentation1. 基本思想分页是从物理管理角度划分的，页面大小固定，用户通常感受不到页面的逻辑意义。分段则不同，它按照程序的逻辑结构划分。例如一个程序可以分为：主程序 main；函数 X；函数 Y；数据段 D。每个段是一个逻辑单位。2. 段式地址结构段式逻辑地址是二维地址：逻辑地址 = 段号 s + 段内地址 d物理地址计算：物理地址 = 段首址 b' + 段内地址 d前提是：0 ≤ d ≤ 段长 l' - 1否则段内地址越界。3. 段表每个进程有一张段表。段表表项包括：字段含义段号当前段编号段首址该段在内存中的起始地址段长度该段长度例如：段号段首址段长度0100K40K1200K80K2320K60K3300K20K4. 段式地址转换步骤逻辑地址为：(s, d)转换过程：CPU 产生逻辑地址 s, d；查 TLB，看是否已有段号 s 对应的段首址和段长；如果没有命中，检查段号 s 是否越界；查段表，得到段首址 b' 和段长 l'；检查段内地址 d 是否越界；若合法，物理地址 = b' + d。段式管理中有两次越界判断：判断对象判断条件段号 s0 ≤ s ≤ 段表长度 - 1段内地址 d0 ≤ d ≤ 段长 - 15. 段的共享段式管理天然适合共享。例如多个进程使用同一个代码段，可以让它们的段表指向同一个物理段。文件中讲了共享段表。共享段表记录：字段含义段名共享段名称共享计数当前有多少进程使用段长共享段长度段首址共享段在内存中的起始地址共享过程：第一个进程使用共享段时，将该段登记到共享段表，共享计数为 1；其他进程使用时，共享计数加 1；某进程结束使用时，共享计数减 1；当共享计数为 0 时，释放该段。6. 段的保护段式管理也适合做保护。可以在段表中增加访问权限位：权限含义R可读W可写E可执行例如代码段可以设置为：R = 1, W = 0, E = 1表示可以读、可以执行，但不能写。7. 段式管理优缺点优点：符合程序逻辑结构；方便共享；方便保护；段长可以不同，比较灵活。缺点：段长不固定，需要动态异长分配；会产生外部碎片；需要空闲区管理算法，如 FF、NF、BF、WF。6.3.4 段页式存储管理段页式是分页和分段的结合。段式优于页式：便于共享和保护。 页式优于段式：消除碎片问题。 段页式：结合二者优点。1. 基本思想一个进程先分成若干段，每个段再分成若干页。所以逻辑地址是三部分：逻辑地址 = 段号 s + 逻辑页号 p + 页内地址 d物理地址是：物理地址 = 页框号 f + 页内地址 d2. 需要的表段页式需要两级结构：段表每个进程一张段表。段表中每个表项记录：字段含义页表首址该段对应页表的位置页表长度该段有多少页页表每个段一张页表。页表记录：逻辑页号页框号3. 段页式地址转换步骤逻辑地址：(s, p, d)转换过程：CPU 产生逻辑地址 s, p, d；查快表，看是否有 s, p 对应的页框号 f；若命中，直接形成物理地址 f, d；若未命中，检查段号 s 是否越界；查段表，找到该段页表的首址 b' 和长度 l'；检查页号 p 是否越界；查该段页表，得到页框号 f；把 s, p, f 放入快表；形成物理地址 f, d。4. 段页式优缺点优点：保留分段的逻辑性；方便共享和保护；保留分页的离散分配能力；减少外部碎片。缺点：地址转换过程更复杂；需要段表和页表；未命中快表时访问内存次数更多。5. 代码段共享例子文本编辑器：文本编辑器包括：150KB 代码段；50KB 数据段。如果有 10 个进程并发执行。不共享代码时：10 × (150 + 50) KB = 2000KB = 2MB共享代码段时：150KB + 10 × 50KB = 650KB这说明共享代码段可以大幅节省内存。6.4 Linux 伙伴堆存储分配算法这一部分讲 Linux 如何管理物理页框1. 为什么需要伙伴堆算法？Linux 中 DMA 输入输出不带地址变换机构，因此某些场景下要求进程或设备使用连续的物理页面。伙伴堆算法用于管理内存中的空闲页框，是一种针对碎片问题的稳定、高效分配策略。2. 基本概念页框内存被静态划分成等长区域，每个区域叫页框。例如：1 页框 = 4KB块组Linux 把空闲页面按大小分组。第 i 组中的块大小为：2^i 个页面例如：组号 i块大小01 页12 页24 页38 页9512 页同一组中的所有空闲块用链表连接。3. free_area$[i]$文件中说：free_area[i]表示页框数为 2^i 的块组。它包含两类指针：指针作用空闲块组指针指向该组的空闲块链表块组位图指针指向该组对应的位图4. 什么是伙伴 Buddy？两个块成为伙伴，需要满足三个条件：大小相同；物理地址相邻；按页框编号计算，后一个块最后页框号 + 1 必须是 2b 的整数倍，其中 b 是块大小。简单理解：两个相邻、同大小、由同一个大块拆出来的块，就是伙伴。例如对于块组 1，每个块大小是 2 页：页框 0、1 是一块；页框 2、3 是一块；这两块是一对伙伴。对于块组 2，每个块大小是 4 页：页框 0、1、2、3 是一块；页框 4、5、6、7 是一块；这两块是一对伙伴。5. 伙伴堆分配算法申请 fn 个页框时：找到最小的 i，使得：2^(i-1) < fn ≤ 2^i到第 i 组中找空闲块；如果第 i 组有空闲块，直接分配一个 2^i 页的块；如果没有，就向更大的块组查找；找到更大的块后，把它拆分；分配需要的部分；剩余部分放回对应块组；修改位图。例如申请 3 页：2^1 < 3 ≤ 2^2所以要分配 4 页。这会产生 1 页内部碎片。6. 伙伴堆释放算法释放一个块时，系统会检查它的伙伴是否也空闲。如果伙伴也空闲：合并成更大的块。然后继续检查更大块的伙伴是否空闲，可以继续向上合并。例如释放页框 13 后：12、13 可以构成伙伴；14、15 可以构成伙伴；如果它们都空闲，就能合并成 4 页的大空闲块；加入块组 2。7. 伙伴堆的内部碎片问题伙伴堆按 2 的整数次幂分配。如果申请 17 页：2^4 < 17 ≤ 2^5系统会分配 32 页。浪费：32 - 17 = 15 页这就是内部碎片。解决方向：方法思想second memory allocator把多余空间按 2 的整数次幂切分，由二级分配器管理third memory allocator如果物理空间不要求连续，则由三级分配器完成分配本章核心1. 分页与分段对比对比项分页分段划分依据系统固定大小划分按程序逻辑单位划分地址结构一维地址：页号 + 页内地址二维地址：段号 + 段内地址大小页面大小固定段长不固定内存分配静态等长动态异长碎片有内部碎片，无外部碎片有外部碎片共享保护不直观更方便用户是否可见通常不可见用户逻辑上可见2. 四种存储管理方式对比方式地址结构优点缺点界地址逻辑地址 a简单，硬件支持少必须连续，容易碎片页式p, d消除外部碎片有内部碎片，页表开销段式s, d便于共享和保护有外部碎片段页式s, p, d兼具分页和分段优点地址转换复杂易考题型题型一：动态分区分配算法给一组空闲区，再给一串申请和释放，让用 FF、NF、BF、WF 计算结果。做题关键：First Fit：找第一个满足的；Next Fit：从上次位置后继续找；Best Fit：找最小满足的；Worst Fit：找最大满足的；释放时相邻空闲区要合并。题型二：页式地址转换做题步骤：根据页面大小确定页内地址位数；把逻辑地址拆成页号 p 和页内地址 d；查页表得到页框号 f；物理地址 = f 和 d 拼接，或者 f × 页面大小 + d。最重要口诀：页内地址不变，页号换成页框号。题型三：EAT 有效访问时间单级页表常见公式：EAT = α × (TLB时间 + 内存时间) + (1 - α) × (TLB时间 + 2 × 内存时间)多级页表公式要根据未命中时访问几级页表来改。例如四级页表：EAT = α × (TLB时间 + 内存时间) + (1 - α) × (TLB时间 + 5 × 内存时间)题型四：段式地址转换做题步骤：拆出段号 s 和段内地址 d；判断 s 是否超过段表长度；查段表得到段首址和段长；判断 d 是否超过段长；如果合法，物理地址 = 段首址 + d。注意段式有两次越界判断。题型五：段页式地址转换做题步骤：拆成 s, p, d；查段表，找到该段页表首址和页表长度；判断段号是否越界；判断页号是否越界；查页表得到页框号；物理地址 = f, d。题型六：伙伴堆算法做题步骤：申请 fn 页；找最小 $2^i$，使 $2^(i-1) < fn ≤ 2^i$；到第 i 组找空闲块；没有则向更大组找并拆分；释放时检查伙伴是否空闲；空闲则合并，继续向上合并。口诀：分配向上找，拆大成小；释放找伙伴，能合就合。梳理存储管理五大功能：分配去配、共享、保护、扩充、地址映射。地址映射三种方式：固定、静态、动态。动态分区四种算法：FF、NF、BF、WF。碎片问题：分页有内部碎片，分段有外部碎片。界地址管理：base + limit，物理地址 = 基址 + 逻辑地址。分页管理：页号变页框号，页内地址不变。TLB 和 EAT 计算：命中和未命中分别计算。多级页表：解决页表过大和连续空间需求问题。反置页表：面向物理页框，系统一张表。分段管理：便于共享和保护，但有外部碎片。段页式管理：先分段，段内分页，结合二者优点。伙伴堆算法：按 2 的幂分配，释放时按伙伴合并。小结主存管理就是操作系统在有限内存中，通过分配、回收、保护、共享和地址转换，让多个进程安全高效运行；其中分页解决连续分配问题，分段解决逻辑共享保护问题，段页式结合两者优点，而伙伴堆算法则用于高效管理连续物理页框。虚拟存储管理主线：外存如何管理 → 为什么需要虚拟存储 → 虚拟页式/段式/段页式如何实现 → 页面置换算法如何计算 → Linux 伙伴堆如何分配页框总体框架本章分为两大部分：7.1 外存空间管理讲外存如何划分、如何记录空闲块、进程在外存中如何存放。7.2 虚拟存储系统讲虚拟存储的基本思想，以及三种实现方式：虚拟页式存储管理虚拟段式存储管理虚拟段页式存储管理后面还补充了 Linux 的 Buddy Heap 伙伴堆分配算法，这是管理内存空闲页框的一种方法。7.1 外存空间管理1. 外存空间的类型文件中把外存空间分成几类：Swap 空间：用于进程页面或段的换入换出。File 空间：用于存放普通文件。输入井：用于输入缓冲。输出井：用于输出缓冲。在虚拟存储系统中，外存不是单纯存文件，还承担了“临时放置暂时不在内存中的程序页/段”的作用。2. 外存空间划分外存空间通常被划分成固定大小的块，称为 block。特点：块是外存分配的基本单位。块也是 I/O 传输的基本单位。块大小通常是 2 的整数次幂。如果实际需求不足一个块，会产生块内零头，但由于外存容量较大，通常可以忽略。这里和内存页框类似： 内存按页框分配，外存按块分配。3. 外存空闲空间管理方法文件中提到三种外存空闲块管理方式。方式一：空闲块链把所有空闲块串成一个链表。优点：结构简单。 缺点：查找较慢，尤其是需要连续空闲块时效率低。方式二：空闲块表把连续的空闲块记录为同一个表项。例如：起始块号空闲块数1002025010优点：适合记录连续空闲区域。 缺点：表项可能增加，也需要维护合并和分裂。方式三：字位映像图用 1 个二进制位表示一个外存块的状态。例如：0 表示空闲1 表示占用优点：空间利用率高，适合快速判断某块是否空闲。 缺点：查找连续空闲块时可能需要扫描位图。4. 进程和外存的对应关系不同存储管理方式下，进程在外存中的组织方式不同。1. 界地址方式每个进程占一组连续外存块，或者占两组连续外存块。这种方式比较简单，但不够灵活。2. 页式方式内存的一页对应外存的一块。要求：内存页大小 = 外存块大小进程的页面可以分散存放在外存不同块中。3. 段式方式每个段在外存中占若干连续块。一个进程有多个段，不同段之间可以不连续。4. 段页式方式段内再分页。特点：内存一页对应外存一块。一个段内部的多个页对应的外存块可以不连续。一个进程的多个段也可以分散在外存不同区域。7.2 虚拟存储系统1. 为什么需要虚拟存储？如果没有虚拟存储，会有两个大问题：问题一：不能运行比内存大的程序如果程序大小超过物理内存，就无法全部装入内存运行。问题二：进程全部装入内存会浪费空间程序运行具有 局部性原理。所谓局部性，就是：在一段时间内，程序访问的代码和数据往往集中在一个较小的地址范围内。例如，一个程序虽然有 1GB，但当前几秒钟可能只用其中几十 MB。所以没有必要把整个程序一次性全部装入内存。2. 虚拟存储的基本思想虚拟存储的思想是：程序运行时，只把当前必要的一小部分代码和数据装入内存，其余部分放在外存。当访问到不在内存中的部分时，再动态调入。如果内存不够，就淘汰暂时不用的部分。一句话理解：用外存扩展内存，让程序感觉自己拥有很大的内存空间。3. 虚拟存储的目标虚拟存储主要有四个目标：让大程序能在小内存中运行。让多个程序能在小内存中并发运行。让多个程序的地址空间互不冲突。提高内存利用率。7.2.1 虚拟页式存储管理虚拟页式存储管理是本章最重要的内容，也是考试最常考的部分。1. 基本原理进程运行前：一部分页面装入内存。另一部分页面装入外存。甚至可以全部先放在外存，运行时再调入。进程运行时：如果访问的页在内存中，正常访问。如果访问的页不在内存中，发生 缺页中断。2. 缺页中断处理过程缺页中断发生后，操作系统大致做这些事情：保存现场。根据页表找到所需页在外存中的位置。在内存中寻找空闲页框。如果有空闲页框，直接调入。如果没有空闲页框，根据页面置换算法选择一个页面淘汰。如果被淘汰页面被修改过，需要写回外存。把所需页面从外存读入内存。修改页表和快表。恢复现场。重新执行导致缺页中断的那条指令。注意： 缺页中断不是错误，而是虚拟存储系统的正常机制。3. 虚拟页式地址变换逻辑地址形式为：逻辑地址 = 页号 p + 页内位移 d地址变换过程：CPU 给出逻辑地址 (p, d)。先查快表 TLB。如果快表命中，得到页框号 f。物理地址 = (f, d)。如果快表未命中，查页表。如果页在内存中，得到页框号，更新快表。如果页不在内存中，发生缺页中断。缺页处理后，再重新执行地址变换。还要注意越界检查：如果页号 p 超出进程页表范围，发生越界中断。如果访问权限不合法，发生越权中断。4. 页表和快表的改进普通页表需要增加一些字段：字段作用逻辑页号标识虚拟页页框号如果在内存中，对应物理页框外存块号如果不在内存中，对应外存位置内外标识表示该页是否在内存访问权限如读、写、执行修改标志判断页面是否被修改过快表中通常保存：字段作用逻辑页号用于匹配页框号用于形成物理地址访问权限判断是否合法修改标志判断是否被写过修改标志很重要，因为页面淘汰时：如果没修改过，可以直接丢弃。如果修改过，必须写回外存。7.2.1.2 内存页框分配策略在内存容量和进程数量确定时，需要决定每个进程分多少页框。四种静态分配方法。1. 平均分配把所有物理页框平均分给进程。例如：内存 128 个页框，进程 25 个，则每个进程大约分 5 个页框。优点：简单。 缺点：不考虑进程大小和优先级。2. 按进程长度比例分配设：进程 pi 有 si 个页面。所有进程页面总数为 S = Σsi。内存共有 m 个页框。则：ai = (si / S) × mai 表示分给进程 pi 的页框数。例如：内存 62 个页框，两个进程：p1 有 127 页p2 有 10 页则 p1 分得 57 个页框，p2 分得 5 个页框。3. 按优先级比例分配设进程 pi 的优先级为 ri，总优先级为：S = Σri则：ai = (ri / S) × m优先级越高，分到的页框越多。4. 按长度和优先级综合分配综合考虑页面数和优先级：ai = ((si + ri) / S) × m其中：S = Σ(si + ri)5. 静态分配策略的问题这些方法都是静态的，缺点是没有反映：程序结构。程序在不同时刻的行为特性。例如，一个程序虽然很大，但当前只频繁访问少量页面，那么给它太多页框可能浪费。7.2.1.3 外存块分配策略1. 静态分配外存始终保存进程的全部页面。即使某个页面被调入内存，它在外存中的副本也不释放。优点：速度快。页面淘汰时，如果没有修改，可以不必写回。缺点：浪费外存。2. 动态分配外存只保存当前不在内存中的页面。当某个外存页面被调入内存后，它原来占用的外存块释放。优点：节省外存空间。缺点：页面淘汰时通常必须写回外存，速度较慢。7.2.1.4 页面调入时机1. 请求调页 Demand Paging当发生缺页中断时，才把所需页面调入内存。优点：不浪费调页。只调真正需要的页。缺点：发生缺页时进程必须等待，影响执行速度。2. 预调页 Prepaging在页面真正被访问之前，提前调入。依据通常是程序顺序执行特征。优点：可以减少缺页等待时间。缺点：预测不一定准确。如果预调入的页后面没有用到，会浪费内存和 I/O。注意：预调页必须配合请求调页使用，因为预调不可能完全准确。7.2.1.5 页面置换算法页面置换算法用于决定：当内存没有空闲页框时，应该淘汰哪个页面？目标是：尽可能降低页故障率。页故障率公式：f = F / A其中：F 是页故障次数。A 是总访问次数。1. 最佳置换算法 OPT思想：淘汰以后不再使用，或者最长时间以后才会再次使用的页面。优点：页故障率最低。理论上最优。缺点：无法真正实现，因为操作系统无法准确知道未来页面访问序列。考试中常用 OPT 作为比较标准。2. FIFO 先进先出算法思想：淘汰最早进入内存的页面。实现方式：用队列维护页面进入内存的顺序。队头是最早进入的页面。淘汰队头页面。优点：简单。缺点：可能淘汰常用页面。会出现 Belady 异常。3. Belady 异常Belady 异常指：增加分配给进程的页框数，缺页次数反而可能增加。文件中的例子说明：访问序列：1, 2, 3, 4, 1, 2, 5, 1, 2, 3, 4, 5FIFO 下：3 个页框时，页故障率为 9/12。4 个页框时，页故障率为 10/12。页框更多，缺页反而更多，这就是 Belady 异常。考试要记住：FIFO 可能出现 Belady 异常，LRU 和 OPT 不会。4. LRU 最近最少使用算法思想：淘汰最近最长时间没有被访问的页面。依据：如果一个页面很久没有被访问，未来短期内也可能不会被访问。实现方法：可以用栈。每访问一个页面，就把它移到栈顶。栈底页面就是最近最少使用页面。优点：性能通常较好。符合局部性原理。缺点：实现开销大。需要硬件支持。如果完全靠软件实现，速度会严重下降。5. NUR / NRU 最近不用算法NUR，也叫 NRU，Not Used Recently。思想：淘汰最近一段时间没有使用过的页面。每个页面增加两个硬件位：标志位含义引用位 r是否被访问过修改位 m是否被修改过每隔一段时间，把所有引用位清 0。淘汰优先级如下：r = 0, m = 0：最近没访问，也没修改，最好淘汰。r = 0, m = 1：最近没访问，但修改过，淘汰前要写回。r = 1, m = 0：最近访问过，但没修改。r = 1, m = 1：最近访问过，也修改过，最不适合淘汰。核心记忆：优先淘汰没访问过的；在没访问过中，优先淘汰没修改过的。6. LFU 最不经常使用算法思想：淘汰访问次数最少的页面。实现：每个页面设置计数器。页面调入时计数器清 0。每访问一次，计数器加 1。淘汰计数器最小的页面。缺点：前期访问很多、后期不用的页面可能难以淘汰。刚调入的页面计数少，可能马上被淘汰。7. MFU 最频繁使用算法思想与 LFU 相反：淘汰访问次数最多的页面。理由：访问次数少的页面可能刚调入，未来可能还要使用；访问次数多的页面可能已经使用完了。缺点：有些页面确实会贯穿整个程序运行过程，频繁使用，不应该淘汰。实现开销也很大。8. 二次机会算法 Second Chance思想：FIFO 的改进版。先看最老页面，如果它最近被访问过，就给它第二次机会。每个页面有引用位 r。过程：检查最早进入内存的页面。如果 r = 0，淘汰。如果 r = 1，把 r 置 0，并把该页面移动到队尾。继续检查下一个页面。一句话理解：老页面不一定马上淘汰，如果最近访问过，就再给一次机会。9. 时钟算法 Clock Algorithm时钟算法是二次机会算法的环形实现。页面组织成环形队列，有一个指针指向当前位置。过程：从指针当前位置开始检查。如果当前页面 r = 0，淘汰它。如果当前页面 r = 1，把 r 清 0，指针顺时针移动。重复直到找到 r = 0 的页面。考试记忆：Clock 算法本质上是“环形二次机会算法”。10. 改进的时钟算法普通 Clock 只考虑引用位 r。 改进 Clock 同时考虑：引用位 r修改位 m优先级：r = 0, m = 0：最佳，直接淘汰。r = 0, m = 1：次佳，淘汰前写回。r = 1, m = 0：再次。r = 1, m = 1：最差，淘汰前写回。步骤：第一轮找 r = 0, m = 0，不修改 r 位。如果找不到，第二轮找 r = 0, m = 1，同时把扫描过的 r 清 0。如果还找不到，再重新执行第一轮和第二轮，此时一定能找到。核心理解：普通 Clock 尽量淘汰没访问过的页；改进 Clock 尽量淘汰没访问过且没修改过的页。2010 年考研题讲解题目给出：虚拟页式系统。进程空间和内存空间都是 64K。页长 1K。某进程 6 个页。分配 4 个页框。访问逻辑地址 13B7H。因为页长 1K = 2¹⁰，所以：低 10 位是页内地址。高位是逻辑页号。13B7H 对应的逻辑页号是 4。4 号页不在内存，发生缺页中断。FIFO 情况FIFO 淘汰最早装入的页面。题中最早装入的是第 5 页，对应页框号 3。所以 4 号页调入页框 3。物理地址为：0FB7HCLOCK 情况Clock 算法从指针位置开始检查引用位。根据题目给出的 Clock 结构，最终淘汰第 0 页，对应页框号 5。所以物理地址为：17B7H这个题的考点是：根据页大小拆分逻辑地址。判断是否缺页。根据不同置换算法找被淘汰页。用页框号 + 页内位移形成物理地址。7.2.1.6 颠簸 Thrashing1. 什么是颠簸？颠簸指：页面在内存和外存之间频繁换入换出，系统大部分时间都在做换页，而不是执行程序。现象：缺页率很高。CPU 利用率下降。系统响应变慢。2. 颠簸的原因文件中总结了三个原因：分给进程的物理页框太少。页面淘汰算法不合理。程序结构不好，局部性差。3. 颠簸的处理方法对应处理：增加分给进程的页框数。改进页面置换算法。改善程序结构，提高局部性。4. 程序结构对缺页的影响文件中用二维数组举例：int a[1024][1024]; for(i = 0; i < 1024; i++) for(j = 0; j < 1024; j++) a[i][j] = 0;和：for(i = 0; i < 1024; i++) for(j = 0; j < 1024; j++) a[j][i] = 0;在 C 语言中，二维数组按行连续存储。所以：a[i][j] 按行访问，局部性好。a[j][i] 按列访问，可能频繁跨页，缺页率更高。考试经常考这个思想：程序访问顺序会影响缺页次数。7.2.1.7 工作集模型1. 工作集定义工作集 Working Set 指：进程在某一段时间内访问过的页面集合。记作：WS(t, Δ)其中：t 是当前时刻。Δ 是窗口尺寸。WS(t, Δ) 表示从 t - Δ 到 t 这段时间内访问过的页面集合。2. 工作集的意义为了让程序有效运行：进程的工作集应该能放入内存。如果工作集放不进内存，就会频繁缺页，可能发生颠簸。3. 工作集和窗口尺寸的关系窗口 Δ 太小：不能覆盖进程真正活跃的页面。页故障率高。窗口 Δ 太大：包含太多暂时不用的页面。浪费内存。所以工作集大小与时间和窗口尺寸都有关。4. 工作集实现方法可以在页表中增加访问位。过程：每个窗口开始时，把访问位全部清 0。页面被访问时，访问位置 1。窗口结束时，访问位为 1 的页面就是该窗口的工作集。5. 工作集估计公式文件给出估计公式：τn+1 = αwn + (1 - α)τn其中：wn 是第 n 个窗口的实际工作集大小。τn 是第 n 个窗口的估计工作集大小。α 是 0 到 1 之间的权重。这个公式本质是“加权平均”：α 越大，越重视最近一次实际值。α 越小，越重视过去估计值。7.2.1.8 页故障率反馈模型 PFFBPFFB 全称：Page Fault Feed Back思想：根据页故障率动态调整分给进程的页框数。规则：页故障率太高：说明页框太少，增加页框。页故障率太低：说明页框可能太多，减少页框。它比静态分配更灵活，因为它根据程序运行时行为调整内存分配。7.2.1.11 非均匀存储器访问 NUMA在单处理机系统中，访问所有内存的速度大致相同。但在多处理机系统中，每个 CPU 可能有自己的局部存储器。特点：访问本 CPU 的局部内存较快。访问其他 CPU 附近的非局部内存较慢。这称为：NUMA，Non-Uniform Memory Access，非均匀存储器访问。调度和分配原则：CPU 给进程分配页框时，尽量分配本 CPU 的局部内存。CPU 空闲时，尽量调度上次在本 CPU 上运行过的进程。原因是：这个进程拥有的页面更可能位于该 CPU 的局部内存中。页面大小的选择页面大小不能太大，也不能太小。页面太大缺点：页内碎片增大。内存浪费。极端情况下退化为分区存储。页面太小缺点：页面数量变多。页表长度增加。页表本身占用更多内存。换页更频繁，系统效率降低。常见页面大小：1KB2KB4KB通常是 2 的整数次幂。影响缺页次数的因素文件总结了几个因素：页面置换算法 算法越合理，缺页次数越少。分配给进程的页框数 页框数越少，越容易缺页。页面大小 页面太小，可能更容易缺页。程序编制方法 局部性越好，越不容易缺页；跳转、分支越多，可能越容易缺页。页式系统的不足虚拟页式存储管理有三个主要不足：页面划分没有逻辑意义。 页只是固定大小的块，不对应函数、模块、数组等逻辑单位。页的共享不灵活。 程序共享通常以模块为单位，但页不一定刚好对应模块。存在页内碎片。 一个页面最后可能没有完全用满。7.2.2 虚拟段式存储系统虚拟段式存储管理以“段”为单位。段通常对应程序的逻辑单位，例如：主程序段子程序段数据段栈段共享库段1. 基本思想进程运行前：整个进程装入外存。部分段装入内存。进程运行时：如果访问的段在内存中，正常访问。如果访问的段不在内存中，发生 缺段中断。如果内存空间不够，有两种办法：紧凑：移动内存中的段，把分散的空闲区合并。淘汰：把某个段移到外存。2. 虚拟段式地址变换逻辑地址形式：(s, d)其中：s 是段号。d 是段内位移。地址变换过程：CPU 给出逻辑地址 (s, d)。先查快表。如果快表命中，得到段内存首址 b' 和段长 l'。检查 d 是否小于段长。如果合法，物理地址 = b' + d。如果快表未命中，查段表。如果段不在内存，发生缺段中断。如果段号或段内位移越界，发生越界中断。3. 段表字段段表通常包括：字段含义段号标识段段长用于越界检查内存首址段在内存中的起始地址外存首址段在外存中的位置权限读、写、执行内外标识是否在内存修改标志是否被修改过7.2.2.2 段的动态连接这是虚拟段式存储管理的重要内容。1. 静态连接静态连接发生在程序运行前，由链接程序 link 完成。特点：程序运行前所有需要的段都确定。每个段提前分配段号。目标程序较大。连接时间较长。有些段可能根本不会执行，但也被连接进来了。2. 动态连接动态连接发生在程序运行时，由操作系统完成。特点：程序运行过程中，需要某个段时才连接。一个程序到底会连接多少段，运行前不一定知道。段名到段号的转换需要操作系统完成。优点：减少初始装入时间。减少目标代码长度。没用到的段可以不连接。3. 动态连接的实现思想系统维护几个数据结构：每进程一个段名-段号对照表记录当前进程已经连接的段。每段一个符号表记录符号名和段内位移。例如：符号名段内位移func1504. 连接中断编译时，如果遇到访问外段的指令，会采用间接寻址。间接字中有一个连接标志 L：L = 1：未连接。L = 0：已连接。执行时：遇到间接指令。如果 L = 1，说明外段尚未连接。发生连接中断。操作系统根据段名找到段。如果段已经连接过，取出段号。如果段没有连接过，从文件中读入，分配段号，填写段表和段名-段号表。根据符号表找到段内地址。把符号地址改成逻辑地址。把 L 置 0。重新执行指令。5. 动态连接与共享的矛盾动态连接需要修改连接字，也就是修改代码中的某些内容。但是段共享通常要求代码是：pure code，纯代码纯代码不能被修改，否则多个进程共享时会相互影响。解决方法：把共享代码分为纯段和杂段。纯段可以共享。杂段每个进程私用。7.2.3 虚拟段页式存储系统虚拟段页式存储管理结合了页式和段式的优点。1. 为什么需要段页式？页式优点存储分配简单。没有外部碎片。页式缺点地址是一维的。页面没有逻辑意义。不方便动态连接和动态扩充。共享和保护不够自然。段式优点段有逻辑意义。地址是二维的。方便共享和保护。可以动态扩充。可以动态连接。段式缺点存储分配复杂。可能产生外部碎片。需要紧凑。段页式思想段页式：先分段，再把每个段分页。这样：对用户来说，看到的是段。对内存分配来说，使用页。既保留了段的逻辑意义，又保留了页的分配便利。2. 段页式需要的数据结构1. 段表每个进程一个段表。每连接一个新段，增加一个段表项。段表包括：页表长度页表首址访问权限扩展标志共享段入口2. 页表每段一个页表。进程开始时，只为主程序段建立页表。 其他段的页表在段连接时建立。页表包括：页框号外存块号内外标志修改标志3. 总页表内存和外存各一个，通常用位示图表示空闲/占用情况。4. 共享段表系统一个，记录所有共享段。包括：段名页表长度页表首址扩充标志共享计数5. 段名-段号对照表每个进程一个。用于动态连接时，把段名转换为段号。3. 段页式地址格式逻辑地址形式：(s, p, d)其中：s 是段号。p 是段内页号。d 是页内位移。地址映射为：(s, p, d) → (f, d)其中 f 是物理页框号。4. 段页式地址变换过程根据 (s, p) 查快表。如果快表命中，得到页框号 f。检查访问权限。形成物理地址 (f, d)。如果快表未命中，先查段表。检查段号是否越界。根据段表找到该段的页表。检查页号是否越界。查页表，看该页是否在内存。如果不在内存，发生缺页中断。如果是间接地址且有障碍位，可能发生连接中断。更新快表。形成物理地址。5. 段页式中断处理1. 连接中断分三种情况：情况一：所有进程都没连接过该段操作系统需要：查文件目录找到该段。为该段建立页表。从文件读入外存 swap。部分页读入内存。填写页表。分配段号。填写段名-段号表。如果是共享段，填写共享段表。填写段表。查符号表得到 (p, d)。情况二：其他进程连接过，本进程没连接过说明共享段表中已有该段，但本进程的段名-段号表中没有。操作系统需要：给本进程分配段号。填写本进程段名-段号表。填写本进程段表。共享计数加 1。查符号表得到 (p, d)。情况三：本进程已经连接过直接查符号表，得到 (p, d)。2. 缺页中断处理方法：分配页框。调入所需页面。更新页表。更新总页表。3. 越界中断分两种：段号越界访问不存在的段，属于地址错误，程序终止。页号越界访问段内不存在的页。此时要看段是否允许扩展：如果允许扩展，增加页，修改页表和段表。如果不允许扩展，地址错误，程序终止。4. 越权中断违反段的访问权限，例如只读段被写入。结果：程序终止。6. 三种虚拟存储管理方式对比管理方式地址空间存储分配碎片共享保护动态扩充动态连接虚拟页式一维简单无外碎片，有页内碎片不方便不可不可虚拟段式二维复杂有外碎片方便可以可以虚拟段页式二维简单无外碎片方便可以可以重要结论：分页对用户透明，分段对用户可见。Linux 伙伴堆 Buddy Heap 分配算法这是本章后半部分的重要补充内容。1. 为什么需要伙伴堆？Linux 中需要管理大量空闲页框。有些场景需要连续的物理页框，例如 DMA 操作。伙伴堆算法用于：管理内存中的空闲页框，减少外部碎片，提高分配和释放效率。2. 基本概念页框内存被划分成固定大小的页框，例如 4KB。块组Linux 把空闲页框按大小分组。第 i 组保存大小为：2^i 个连续页框的空闲块。例如：块组块大小free_area[0]1 页free_area[1]2 页free_area[2]4 页free_area[3]8 页free_area[9]512 页同一组中的空闲块大小相同，并通过链表组织。3. free_area 结构每个 free_area[i] 包含：空闲块链表指针。块组位图指针。空闲链表用于记录当前有哪些大小为 2^i 页的空闲块。位图用于辅助判断伙伴块状态。4. 什么是伙伴？两个块是伙伴，需要满足三个条件：大小相同。物理地址连续。按页框编号计算，它们的位置满足伙伴对齐关系。通俗理解：一个大块被一分为二之后，这两个小块互为伙伴。例如：页框 0、1 和页框 2、3 可以是一对大小为 2 页的伙伴。页框 0、1、2、3 和页框 4、5、6、7 可以是一对大小为 4 页的伙伴。5. 分配过程假设申请 fn 个页框。第一步：找到最小的 2 的幂找到满足：2^(i-1) < fn ≤ 2^i的 i。也就是说，申请 fn 页时，至少要分配 2^i 页。例如：申请 3 页：2^1 < 3 ≤ 2^2所以要分配 4 页，从 free_area[2] 中分配。第二步：查找对应块组如果 free_area[i] 有空闲块，直接分配。如果没有，就向更大的块组查找。第三步：分裂大块如果找到更大的块，比如 2^j 页，且 j > i，就把大块分裂。例如申请 128 页：应从第 7 组取，因为 2^7 = 128。如果第 7 组没有空闲块，就找第 8 组。如果第 8 组有 256 页块，则分成两个 128 页块：一个分配出去。另一个放回第 7 组。如果第 8 组也没有，就找第 9 组，继续分裂。6. 释放过程释放 2^i 个页框时：找到它的伙伴块。如果伙伴块也是空闲的，就合并成更大的块。合并后的块继续尝试和自己的伙伴合并。直到不能合并为止。把最终得到的空闲块加入对应块组链表。修改位图。分配时，大块不断拆小；释放时，小块尽量合大。7. 伙伴堆的内部碎片问题伙伴堆以 2 的幂为单位分配，会产生内部碎片。例如申请 17 个页框：因为：2^4 < 17 ≤ 2^5所以必须分配 32 个页框。实际只需要 17 个，浪费了 15 个页框。这就是内部碎片。8. 解决方法second memory allocator当实际申请页框数小于分配块大小时，把剩余空间按 2 的幂继续切分，由第二级分配器管理。third memory allocator如果进程物理空间不要求连续，可以由第三级分配器完成更灵活的分配。2015 年期末题思路页式系统地址转换。页表中引用位、修改位、内外标志的变化。空闲页框链表 head 的变化。页面调入和页面淘汰。关键点：页面大小 2KB = 2¹¹，所以逻辑地址低 11 位是页内偏移。高位是页号。如果访问页不在内存中，发生缺页中断。如果进程还能分配页框，就从空闲页框链表取一个。如果页框已达上限，就根据时钟算法淘汰页面。修改后的页面如果被淘汰，需要写回外存。页表中的引用位、修改位、内外标志要随访问变化。易错点：页号和页内位移拆错。十六进制和二进制转换错。缺页后忘记重新执行指令。忘记更新引用位。写操作后忘记设置修改位。淘汰修改页时忘记写回。章末作业涉及的考点作业 1：伙伴堆算法要求根据主存映像图写出：free_area 各表项的空闲块链表。块组位图内容。申请 3 页后的内存映像图。分配后的伙伴堆数据结构。考点：会判断哪些页框空闲。会判断哪些空闲页框能组成 1 页、2 页、4 页块。会判断伙伴关系。会根据申请页数向上取 2 的幂。会更新链表和位图。作业 2：页式地址转换题目给出：主存 10KB。页面大小 1024B。J2 有 4 个页面，分别装入内存第 3、4、6、8 块。要求：写出 J2 页表。计算 MOV 指令中两个操作数的物理地址。考点：页大小 1024B = 2¹⁰，所以低 10 位是页内位移。逻辑地址除以 1024 得页号。逻辑地址对 1024 取余得页内位移。查页表得到物理块号。物理地址 = 物理块号 × 页面大小 + 页内位移。作业 3：请求分页 + TLB + LRU题目涉及：页表有效位。TLB 初始为空。访问 TLB 时间 10ns。访问内存时间 100ns。缺页平均处理时间 10⁸ns。驻留集大小固定为 2。LRU 局部淘汰。虚地址访问序列：2362H、1565H、25A5H。考点：地址拆分。TLB 命中和未命中时间计算。页表访问。缺页中断处理。缺页后重新执行指令。LRU 淘汰顺序。有效位变化。物理地址形成。特别注意：缺页处理后，导致缺页的那条指令要重新执行，所以时间计算通常包括缺页处理时间以及重新访问 TLB/页表/内存的时间。考试重点缺页中断处理流程 从访问页不在内存，到调入页面、更新页表、重新执行指令。逻辑地址到物理地址转换 页式：页号 + 页内位移。 段式：段号 + 段内位移。 段页式：段号 + 页号 + 页内位移。页面置换算法 OPT、FIFO、LRU、NUR、Second Chance、Clock、改进 Clock。Belady 异常 FIFO 可能出现，页框数增加缺页次数反而增加。Clock 算法和改进 Clock 算法计算 这是考试非常常见的题型。工作集模型和 PFFB 都是为了防止颠簸，但思路不同：工作集：根据最近访问页面集合分配页框。PFFB：根据缺页率动态调整页框数。颠簸原因和解决方法 页框少、算法差、程序局部性差。页式、段式、段页式对比 表格题、简答题常考。动态连接 理解连接中断、段名-段号表、符号表、连接字 L。伙伴堆算法 会按 2 的幂分配和合并，会判断内部碎片。总结虚拟存储利用局部性原理，只把当前需要的页或段放入内存；当访问内容不在内存时，通过缺页或缺段中断调入；内存不够时用置换算法淘汰页面；为了避免频繁换入换出的颠簸，需要合理分配页框、选择置换算法，并利用工作集或缺页率反馈模型动态调整；页式简单但逻辑性差，段式逻辑性强但分配复杂，段页式结合二者优点；Linux 伙伴堆则用 2 的幂大小管理连续空闲页框。文件系统主线： 文件系统 = 操作系统把“用户看到的文件名、目录、读写操作”转换成“磁盘上的块、inode/FCB、缓冲区、I/O 操作”的机制。用户关心“文件名和操作”，系统关心“空间分配、目录查找、权限、安全、效率”。一、8.1 文件与文件系统1. 什么是文件？具有符号名，并且在逻辑上具有完整意义的信息项序列。文件不是一堆杂乱数据，而是操作系统管理的一段有名字、有属性、有结构的数据。文件内部有一个读写指针，表示当前读写位置。例如打开一个文件后，连续 read() 会从上一次读到的位置继续读。2. 什么是文件系统？文件系统是：文件 + 管理文件的程序集合。它的目标分两个角度：角度目标用户角度按名存取，即通过文件名访问文件系统角度管理外存空间、组织文件、分配磁盘块、保护文件、检索文件文件系统的主要功能包括：按名存取、目录维护、文件查找定位、外存空间分配、提供访问方式、共享保护保密、提供文件操作命令、统一设备接口等。3. 文件分类常见分类方式：分类标准类型按所有者系统文件、用户文件按保存期限临时文件、永久文件按访问类型只读、只写、读写按设备类型磁盘文件、磁带文件、磁鼓文件按用途目录文件、普通文件按内容程序文件、数据文件UNIX 中常见三类文件：类型含义普通文件程序、数据、图片、音频等目录文件保存“文件名 + 文件号”的表特殊文件设备文件，把设备也当作文件管理重点理解：为什么 UNIX 把设备也看成文件？因为这样可以统一接口。 比如磁盘文件和设备都可以用：open() read() write() close()这就是“一切皆文件”的思想。4. 文件属性文件属性通常包括：文件名、文件标识、文件类型、文件位置、文件大小、文件权限、创建时间、修改时间、访问时间等。常考： 文件名不等于文件本身，文件名只是目录中的一个入口，真正描述文件的是 FCB 或 inode。二、8.2 文件的访问方式1. 顺序访问按照文件内容的先后顺序访问。例如：read(fd, buf, 100); read(fd, buf, 100);第二次读会接着第一次读的位置继续读。适合：文本文件、日志文件、磁带文件。2. 随机访问可以直接访问文件中的某个位置。分为：类型含义按记录编号访问直接访问第 n 条记录按关键字访问根据 key 查找记录随机访问常依赖 seek() 或索引结构。三、8.3 文件的组织文件组织分为两层：层次含义逻辑组织用户看到的文件结构物理组织文件在磁盘块上的存放方式1. 文件的逻辑组织记录式文件文件由一条条记录组成。分为：类型优点缺点等长记录处理方便，速度快浪费空间不等长记录节省空间处理不方便，速度慢流式文件文件是字节序列。现代操作系统基本采用流式文件，例如 UNIX、Windows。可以理解为：文件 = byte[0], byte[1], byte[2], ...这也是为什么文件偏移量通常以字节为单位。2. 文件的物理组织物理组织重点考五种：结构基本思想优点缺点顺序结构文件占连续磁盘块顺序访问快，节省空间文件增长困难，容易产生外部碎片链接结构文件块分散存放，块之间用指针连接文件增长方便，空间利用率高随机访问慢，指针占空间索引结构文件块号集中放在索引块中随机访问快，支持动态增长索引块占空间散列结构hash(key) 得到记录地址按关键字查找快有冲突，需要冲突处理倒排结构建立“键值 → 记录地址”的索引多种查找方式快索引开销大课件中的第 18、20、22 页分别用图展示了顺序结构、链接结构、索引结构；第 28 页展示了倒排结构的例子。3. 顺序结构顺序结构就是：文件 F = 块18 + 块19 + 块20 + 块21 + 块22FCB 只需要记录：首块号 + 块数优点： 顺序读写速度快，定位简单。缺点： 如果文件要变长，后面可能没有连续空闲块，就很麻烦。4. 链接结构链接结构中，文件块可以不连续：块18 -> 块80 -> 块92FCB 记录首块号和块数，块之间靠指针连接。优点： 文件增长方便，不要求连续空间。缺点： 想访问第 100 块，必须从第 1 块一路跟指针走过去，所以随机访问很慢。5. 索引结构索引结构中，文件的数据块号集中存放在一个索引块里。例如索引块里保存：9, 16, 1, 10, 25说明文件内容分别在这些磁盘块中。优点： 既可以离散存放，又能较快随机访问。缺点： 索引块本身要占空间。6. UNIX 文件物理结构：索引 + 链接==本章计算题重点==课件中 UNIX inode 有 13 个地址项：i_addr[0] ~ i_addr[9] 直接地址 i_addr[10] 一级间接地址 i_addr[11] 二级间接地址 i_addr[12] 三级间接地址如果磁盘块大小为 1KB，盘块号占 4B，则一个索引块能放：1024 / 4 = 256 个盘块号所以最大可寻址块数为：10 + 256 + 256² + 256³最大文件大小为：(10 + 256 + 256² + 256³) × 块大小7. 偏移量转换物理地址常考：给出文件字节偏移量 f_offset，求物理块号和块内偏移。通用步骤：逻辑块号 = f_offset ÷ 块大小 块内偏移 = f_offset % 块大小然后根据逻辑块号判断它属于：范围地址类型0 ~ 9直接地址10 ~ 10+255一级间接再往后二级间接更大三级间接课件例题中：8000 ÷ 1024 = 7 余 832逻辑块号 7 属于直接地址，所以查 i_addr[7]。13000 ÷ 1024 = 12 余 712逻辑块号 12 属于一级间接地址。350000 ÷ 1024 = 341 余 816逻辑块号 341 要扣掉 10 个直接块和 256 个一级间接块，再进入二级间接计算。四、8.4 文件目录1. FCB 与目录项FCB，File Control Block，文件控制块。它保存系统管理文件所需的全部信息。典型内容包括：文件名、文件号、文件主、文件类型、文件属性、共享说明、文件长度、文件地址、创建时间、修改时间、访问时间、口令等。目录项是目录文件中的一项，内容通常就是 FCB 或者 FCB 的一部分。2. 文件目录与目录文件概念含义文件目录目录项构成的有序序列，用于查找文件目录文件内容是目录项的文件目录的核心作用：把文件名转换为文件号，再通过文件号找到文件控制信息，最后找到文件数据块。3. 目录结构单级目录整个系统只有一个目录。缺点：命名冲突无法分组保护困难二级目录每个用户有自己的目录。优点：允许不同用户有同名文件检索速度提高缺点：不能很好地按项目、类型继续分组。多级目录目录形成树形结构。特点：节点类型含义根节点根目录非叶节点目录文件叶节点普通文件或目录文件优点：便于分类查找速度快可以实现文件连接 LinkUNIX、Linux、Windows 都使用多级目录结构。4. 路径名路径名分为：类型含义例子绝对路径从根目录开始/usr/wm4/.profile相对路径从当前目录开始wm4/.profile根目录唯一，当前目录是进程当前工作目录。5. 目录查找过程以 /usr/ast/mbox 为例：根目录 / 找到 usr 的 inode 读入 usr 目录块 找到 ast 的 inode 读入 ast 目录块 找到 mbox 的 inode也就是：路径名 -> 目录项 -> i_number -> inode -> 数据块这是理解 Linux 文件访问过程的关键。6. 目录项改进：文件名和 inode 分离传统 FCB 如果全部放在目录里，目录会很大，查找慢。UNIX 的改进：部分内容存放位置FCB 次部文件名 + 文件号目录文件中FCB 主部文件属性、地址、链接计数等inode 区好处：提高目录查找速度支持文件链接五、8.5 文件共享文件共享的目的：节省存储空间支持进程通信，例如 UNIX pipe共享方式有：方式含义公共目录共享文件登记在公共目录中共享说明文件创建者指定谁能访问连接 Link一个文件拥有多个名字重点：硬链接连接的本质是：多个文件名指向同一个 inode。例如：f1 -> inode 15 f2 -> inode 15这时 f1 和 f2 是同一个文件的两个名字。执行：link(old_name, new_name)会让 inode 的链接计数 i_nlink++。执行：unlink(path_name)会让 i_nlink--。只有当：i_nlink == 0文件占用的磁盘块才会真正释放。六、8.6 文件的保护、保密与安全这三个词容易混：概念目的保护防止非授权访问保密防止内容泄露安全防止文件被破坏1. 文件保护访问类型包括：Read Write Execute Append Delete Modify存取控制矩阵用一个矩阵记录每个用户对每个文件的权限。优点：权限非常细。 缺点：太复杂，占空间大。UNIX 权限说明UNIX 把用户分为三类：文件属主 owner 同组用户 group 其他用户 others每类用户有三种权限：R W E 读 写 执行所以常见权限结构是：rwx rwx rwx 属主 同组 其他判断规则：访问进程 u_uid == 文件 i_uid -> 文件主权限 访问进程 u_gid == 文件 i_gid -> 同组权限 否则 -> 其他用户权限2. 文件保密两种方式：方式特点口令简单，但保密性不强密码/加密安全性好，但速度慢口令是访问前验证。 加密是对文件内容本身进行变换。课件中提到一种用随机数序列加密的方法：保存时：文件内容 + 随机数序列读取时：密文 - 同样的随机数序列关键在于使用同一个 key 生成同样的伪随机序列。3. 文件系统安全：备份备份分三类：类型备份内容恢复方式优点缺点完全转储每次备份全部文件用最后一次完整备份恢复恢复简单数据冗余大增量转储备份上次转储以来修改的数据从完整备份开始，逐次恢复所有增量备份每次备份少恢复麻烦，中间坏一盘就麻烦差分转储每次备份相对第一次完整备份变化的数据第一次完整备份 + 最后一次差分备份恢复简单后期备份量变大课件例题答案： 如果进行了 3 次转储后磁盘丢失，只需要 2 盘磁带恢复的是差分转储，答案 C。七、8.7 文件系统的实现==易出大题==文件系统实现主要包含两方面：外存空间管理 内存表目管理1. 外存空间管理常见方法：方法思想位示图每个磁盘块用 1 bit 表示空闲/占用空闲块链空闲块用链表连接空闲块表用表记录空闲块成组连接UNIX 使用，空闲块链 + 空闲块表结合2. UNIX 成组连接这是考试重点。UNIX 把空闲块按组管理，每组最多 100 个空闲块号。超级块中有：s_nfree // 当前空闲块数 s_free[100] // 空闲块号表 s_flock // 空闲块表锁关键思想：超级块中保存当前组的空闲块号，其中 s_free[0] 既是一个空闲块，又可能是下一组空闲块的链接块。3. 成组连接的申请规则申请空闲块时：情况一：s_nfree > 1直接分配：s_free[--s_nfree]情况二：s_nfree == 1说明超级块中只剩下链接块。此时：读出 s_free[0] 指向的连接块把连接块中的下一组空闲块信息读入超级块分配原来的 s_free[0]4. 成组连接的释放规则释放磁盘块时：情况一：s_nfree < 100直接加入当前超级块空闲表：s_free[s_nfree++] = 释放块号情况二：s_nfree == 100当前组满了：把当前超级块中的 s_nfree 和 s_free[] 写入刚释放的块这个释放块变成新的连接块设置：s_free[0] = 释放块号 s_nfree = 1一句话记忆：申请时从超级块拿；拿到最后一个链接块时，读入下一组。释放时往超级块放；放满时，把旧组写进释放块，让释放块成为新组头。5. 空闲 inode 管理超级块也缓存空闲 inode：s_ninode s_inode[100]申请 inode：情况操作s_ninode > 0直接取 s_inode[--s_ninode]s_ninode == 0从磁盘 inode 区扫描 100 个空闲 inode释放 inode：情况操作s_ninode < 100放入 s_inode[s_ninode++]s_ninode == 100丢弃该 inode 编号注意： “丢弃 inode 编号”不是丢弃 inode 本身，而是暂时不缓存它，以后扫描 inode 区还能找到。6. 内存所需表目文件打开后，操作系统不能每次都从磁盘查 FCB/inode，所以要在内存维护表。主要有两类：表数量作用系统打开文件表整个系统一个保存已打开文件的共享信息用户打开文件表每个进程一个保存本进程打开文件的信息7. UNIX 三张表：u_ofile、file、inodeUNIX 中更具体：表数量内容u_ofile每进程一个文件描述符 fd 指向 file 表项file全系统一个打开方式、共享计数、读写指针、inode 指针inode全系统一个文件属性、权限、地址、大小等表之间关系：u_ofile -> file -> inode -> 磁盘块多个进程可以通过不同 fd 指向同一个 file，也可以通过不同 file 指向同一个 inode。重点区分：字段含义f_count有多少个 fd/进程共享这个 file 表项i_count有多少个 file 表项指向这个 inodei_nlink磁盘目录中有多少个文件名链接到这个 inode这三个计数容易混，一定要分清：f_count：打开表级别 i_count：内存 inode 级别 i_nlink：磁盘目录链接级别八、8.8 文件系统的界面常见系统调用：creat() open() close() seek() read() write() link() unlink() pipe() mknode() mount() umount() chmod() chown() stat() fstat() chdir()1. creat：创建文件fd = creat(pathname, mode);执行过程：分配 inode / FCB 主部初始化文件主、权限、长度、创建时间等在目录中填入 (文件名, 文件号)以写方式打开返回文件描述符 fd2. open：打开文件fd = open(pathname, mode);执行过程：根据路径名查目录，找到文件号根据文件号找到 inode / FCB检查权限是否合法分配或复用系统打开文件表项在用户打开文件表中分配表项返回 fdfd 本质上是：当前进程用户打开文件表的下标。3. close：关闭文件close(fd);执行过程：由 fd 查用户打开文件表找到系统打开文件表项共享计数减 1如果计数为 0 且文件被修改，则写回 inode / FCB释放用户打开文件表项4. seek：文件指针定位seek(fd, offset);作用：修改读写指针的位置。后续 read() 或 write() 会从新位置开始。5. read：读文件read(fd, nrd, buf);基本流程：fd -> 用户打开文件表 -> 系统打开文件表 -> inode -> 文件地址然后：根据 offset 计算逻辑块号和块内偏移根据文件物理结构找到物理块号如果块在缓冲区，直接复制否则发起磁盘 I/O，读入缓冲区复制到用户 buf更新 offset返回实际读取字节数6. write：写文件write(fd, nwt, buf);基本流程和 read 类似。区别是：如果写的位置已经有物理块，则修改原块。 如果写的位置超过原文件长度，则要分配新磁盘块。写完后要更新文件长度和 inode 修改标志。7. link 和 unlinklinklink(old_name, new_name);作用：给同一个文件增加一个新名字。执行过程：找到 old_name 的 inode在 new_name 所在目录中增加目录项inode 的 i_nlink++unlinkunlink(pathname);作用：删除一个文件名。执行过程：找到 pathname 的 inodei_nlink--如果 i_nlink == 0，释放文件数据块删除目录项注意：unlink 删除的是“名字”；只有链接计数为 0 时，文件内容才真正删除。8. pipe：管道文件pipe(fd);返回两个文件描述符：fd[0]：读端 fd[1]：写端管道用于相关进程通信，通常是父子进程。管道同步规则：情况结果管道满写者等待管道空读者等待所有读者关闭写入无意义，写返回错误所有写者关闭读者立即返回管道局限：只能用于相关进程无名管道没有路径名所有进程关闭后自动撤销9. FIFO：命名管道命名管道是有名字的管道文件。特点：有路径名不同祖先进程也能通信可用 open/read/write/close 操作不支持 lseek遵循 FIFO 先进先出原则创建方式：mkfifo(pathname, mode);九、8.9 日志结构文件系统这一节的背景：CPU 和内存速度越来越快，但磁盘机械访问速度提升慢，磁盘 I/O 成为瓶颈。传统文件系统中，创建一个文件可能需要多次写磁盘：写 inode写目录项更新目录 inode写文件内容小量写的问题是： 真正传输数据时间很短，但寻道和旋转延迟很长，效率低。日志结构文件系统思想日志结构文件系统把整个磁盘看成一个日志。写操作不是立即分散写到磁盘各处，而是：先暂存在内存缓冲区积累到一定数量形成一个 segment顺序追加到日志末尾好处：把随机写变成顺序写提高磁盘写效率利用日志提高故障恢复能力segment、inode map、cleaner结构作用segment日志追加的基本单位summary记录 segment 中有什么inode map记录 inode 编号到 inode 实际位置的映射cleaner清理旧 segment，回收无效空间因为 inode 位置会随着日志追加不断变化，所以不能再简单通过 inode 编号算出 inode 位置，需要 inode map。cleaner 的作用是：扫描旧 segment，丢弃过时数据，把仍然有效的数据重新写入新 segment，然后释放旧 segment。十、8.10 内存映射文件内存映射文件的思想是：把文件的一部分映射到进程虚拟地址空间中，让程序像访问内存一样访问文件。传统文件访问：open -> read/write -> close内存映射方式：open -> mmap -> 像访问数组一样访问 -> msync -> munmap -> close1. mmapvoid *mmap(void *addr, size_t len, int prot, int flag, int fd, off_t off);参数含义：参数含义addr映射首地址，一般填 NULLlen映射长度prot访问权限，如 PROT_READ、PROT_WRITE、PROT_EXECflagMAP_SHARED 或 MAP_PRIVATEfd文件描述符off文件偏移量2. MAP_SHARED 和 MAP_PRIVATE类型含义MAP_SHARED修改会反映到文件，多个进程共享MAP_PRIVATE私有映射，修改不直接影响原文件3. msyncmsync(addr, len, flags);作用：把映射区内容同步回文件。flags含义MS_ASYNC异步写，不等待完成MS_SYNC同步写，等待写完4. munmapmunmap(addr, len);作用：解除映射。5. 内存映射文件适用场景适合：多次随机访问文件多进程共享同一文件处理大文件避免频繁 read/write 系统调用十一、8.11 文件系统效率主要方法有三个：1. 磁盘高速缓存利用内存暂存磁盘块。两种形式：内存中开辟固定大小缓存区把未使用内存作为共享缓冲池核心思想：尽量减少真正访问磁盘的次数。2. 提前读如果系统发现进程在顺序读文件，就提前把下一个磁盘块读入缓存。例如 UNIX 中的：breada(dev, blkno, rablkno)适合顺序访问。3. 延迟写当缓冲块尚未写满时，暂时不立即写回磁盘。例如 UNIX 中的：bdwrite(bp)优点： 减少磁盘写次数。缺点： 系统崩溃时可能造成数据不一致。这也是日志文件系统出现的重要原因之一。十二、8.12 Linux 文件系统1. VFS：虚拟文件系统VFS 不是具体文件系统，而是统一接口层。它的作用是：为不同文件系统和设备提供统一访问接口。例如：应用程序 ↓ VFS ↓ Ext2 / FAT / 网络文件系统 / 设备这样上层程序不用关心底层到底是 Ext2、FAT，还是网络文件系统。2. Ext2fsExt2fs 来源于 FFS 的扩展。它把磁盘划分为多个块组：引导块 | 块组 | 块组 | 块组 ...每个块组类似一个小型文件系统，包含：超级块 组描述符 位示图 inode 文件块设计目标：尽量让同一个文件的数据块位于同一个块组中，从而提高访问速度。3. Ext2fs 与 FFS 的区别项目FFSExt2fs存储块大块 + 碎片统一长度块块大小可到 8KB1KB、2KB、4KB空闲块管理相关结构位图 bitmap分配策略追求局部性尽量连续或邻近，预分配Ext2fs 会预先分配一些块，文件关闭时释放没用完的块。4. FAT 文件系统FAT 是 File Allocation Table，文件分配表。它本质上是链接结构的一种改进：文件目录中记录起始块号 FAT 表中记录每个块的下一块号例如：FileA 起始块 0002 FAT[2] = 3 FAT[3] = 4 FAT[4] = FFFF表示文件块链：2 -> 3 -> 4 -> 结束缺点：读文件前要查 FAT，影响效率FAT 本身占存储空间磁盘越大，块越多，FAT 越大十三、本章高频考试点总结1. 概念题重点背：文件 文件系统 FCB 目录项 目录文件 inode 文件描述符 fd 系统打开文件表 用户打开文件表 VFS mmap2. 对比题高频对比：对比重点顺序结构 vs 链接结构 vs 索引结构优缺点、适合访问方式记录式文件 vs 流式文件现代 OS 多用流式单级目录 vs 二级目录 vs 多级目录命名、分组、保护保护 vs 保密 vs 安全访问控制、内容泄露、破坏恢复完全转储 vs 增量转储 vs 差分转储恢复需要哪些备份管道 pipe vs FIFO是否有名字、是否限于亲缘进程MAP_SHARED vs MAP_PRIVATE修改是否写回/共享3. 计算题重点掌握三类：第一类：偏移量转物理地址公式：逻辑块号 = offset / block_size 块内偏移 = offset % block_size再判断直接、一级间接、二级间接、三级间接。第二类：最大文件长度通用公式：假设：块大小 = B 盘块号大小 = p 一个索引块能放 N = B / p 个盘块号 直接地址项个数 = d如果有一级、二级、三级索引，则最大文件块数：d + N + N² + N³最大文件长度：(d + N + N² + N³) × B课件例题中，块大小 4KB，盘块号 4B：N = 4KB / 4B = 1024若 3 个直接地址、1 个一级索引、1 个二级索引：最大长度 = 3×4KB + 1024×4KB + 1024²×4KB = 12KB + 4MB + 4GB第三类：成组连接记住两条：申请： s_nfree > 1：直接分配 s_free[--s_nfree] s_nfree = 1：读入下一组，再分配链接块 释放： s_nfree < 100：直接放入 s_free[s_nfree++] s_nfree = 100：把当前组写进释放块，释放块成为新组头十四、全章逻辑用户输入文件名 ↓ 目录查找 ↓ 找到文件号 i_number ↓ 找到 inode / FCB ↓ 根据 inode 中的地址项找到磁盘块 ↓ 通过缓冲区进行 read/write ↓ 更新读写指针、打开文件表、inode、目录项同时，操作系统还要解决：文件怎么存？ 顺序 / 链接 / 索引 / Hash / 倒排 目录怎么查？ 单级 / 二级 / 多级目录 空间怎么分？ 位示图 / 空闲链 / 成组连接 文件怎么共享？ link / pipe / FIFO 文件怎么保护？ 权限 / 访问控制矩阵 / chmod 文件怎么恢复？ 完全转储 / 增量转储 / 差分转储 / 日志 文件怎么加速？ 缓存 / 提前读 / 延迟写 / mmap Linux 怎么统一？ VFS + Ext2fs + FAT 等具体文件系统十五、总结文件从名字到磁盘块的全过程 重点是目录、FCB、inode、路径查找。文件在磁盘上怎么组织 顺序、链接、索引、UNIX 多级索引计算题。文件打开后系统内部怎么管理 fd、u_ofile、file、inode 三张表。文件系统如何保证可靠性和效率 权限、备份、日志、缓存、提前读、延迟写、mmap。易出大题：UNIX inode 地址转换 文件最大长度计算 成组连接空闲块分配/释放 open/read/write 的执行过程 link/unlink 的 inode 链接计数变化 备份方式比较设备与输入输出管理本章主线：操作系统通过设备分配、驱动程序、中断、缓冲、调度、虚拟设备等机制，把复杂、低速、种类繁多的外设，统一、高效、安全地提供给用户进程使用。一、全章知识框架主线对应内容核心问题设备是什么设备分类、物理特性外设有哪些类型？特点是什么？I/O 怎么传查询、中断、DMA、通道CPU 和设备如何配合传数据？I/O 软件怎么分层用户 I/O、设备无关层、驱动、中断处理一次 read/write 到底经过哪些层？设备怎么分配独占设备、共享设备、UCB/SDT 等多进程竞争设备时如何管理？如何提高效率磁盘调度、缓冲、缓存、RAID如何减少等待、减少寻道、提高可靠性？如何抽象设备虚拟设备、SPOOLing、稳定存储器如何把独占设备变成共享设备？如何保证数据可靠？二、9.1 设备管理的功能和目标1. 设备管理要解决什么问题？I/O 设备种类很多，例如磁盘、磁带、键盘、打印机、显示器、扫描仪等。它们的速度、访问方式、数据单位都不同。操作系统必须统一管理这些设备。设备管理的主要功能有：设备分配与去配 当进程需要设备时，系统决定把哪台设备分给它；用完后再收回。设备处理，也就是设备驱动 设备驱动程序负责把系统的 I/O 请求转化为具体硬件操作。中断处理 当设备完成操作或发生错误时，通过中断通知 CPU。缓冲管理 由于 CPU 很快、设备很慢，需要缓冲区来缓和速度差异，提高并行程度，减少中断次数。虚拟设备管理 用软件方法把少量、低速、独占的实设备变成多个逻辑上的虚拟设备。2. 设备管理的目标本章给出的目标主要有四个：目标含义方便性用户不需要了解硬件细节，只需调用 read/write/open/close 等接口并行性让 CPU、通道、设备尽可能同时工作均衡性多个进程竞争设备时，系统要有调度策略，避免混乱独立性用户使用逻辑设备名，由系统映射到具体物理设备这里最重要的是设备独立性：用户程序不应该关心“到底是哪一台打印机、哪一块磁盘”，只需要使用逻辑设备名。三、9.2 设备的分类常考概念题。1. 按交互对象分类类型例子人机交互设备键盘、鼠标、显示器、打印机与 CPU/系统交互的设备磁盘、磁带、传感器、控制器计算机间通信设备网卡、调制解调器2. 按交互方向分类类型例子输入设备键盘、扫描仪输出设备显示器、打印机双向设备硬盘、网卡3. 按数据传输基本单位分类这是重点。类型数据单位特点例子字符设备字符低速、不可寻址、常用中断方式键盘、鼠标、打印机块设备块高速、可寻址、可随机访问磁盘、光盘、U 盘网络设备数据包/帧面向通信网卡4. 从资源管理角度分类类型特点例子共享型设备多个进程的 I/O 可以交叉进行磁盘独占型块设备多个进程不能交叉使用磁带机独占型字符设备使用时需要申请和释放打印机、终端类设备理解重点：磁盘通常是共享型块设备；磁带是独占型块设备；打印机常作为独占型字符设备。四、9.3 设备的物理特性1. 字符设备字符设备以字符为单位传输数据。每传送一个字符，可能产生一次中断。特点：传输速率低；不可寻址；常采用中断驱动方式；多数属于独占设备；需要 open/close 操作实现互斥。例如键盘输入一个字符，系统就可能通过中断通知 CPU。2. 块设备 / 存储设备块设备以块为单位传输数据。磁盘是典型块设备。特点：传输速率高；可寻址；可以随机读写；常采用 DMA 方式；磁盘通常按块编号，把复杂的柱面、盘面、扇区结构抽象成线性块号。3. 磁带磁带是顺序访问设备。特点：地址是一维的；文件一般顺序存放；需要反绕、正向查找、反向查找、读、写等操作；适合备份，不适合频繁随机访问。4. 磁盘结构与地址转换磁盘有三个重要物理概念：概念含义柱面多个盘面上半径相同的磁道组成一个柱面盘面磁盘的一个表面扇区磁道上的最小物理数据单位磁盘物理地址可以表示为：柱面号 i、盘面号 j、扇区号 k为了方便管理，系统通常把它转换成线性块号 b。设：l：柱面数；m：盘面数；n：每个盘面上的扇区数；i：柱面号；j：盘面号；k：扇区号。三维地址转一维地址：b = i × m × n + j × n + k一维地址转三维地址：i = b / (m × n) j = (b mod (m × n)) / n k = (b mod (m × n)) mod n这个公式非常容易考计算题。5. 扇区交错编号磁盘连续读扇区时，如果 CPU 或控制器处理数据需要时间，磁盘已经转过了下一个扇区，就会错过它，需要再转一圈。所以有：不交错编号；单交错编号；双交错编号。交错编号的目的：减少因为处理延迟导致的旋转等待，提高连续读写效率。五、9.4 I/O 传输方式这是本章核心之一。I/O 控制方式按发展顺序是：程序查询方式 → 中断方式 → DMA 方式 → 通道方式1. 程序查询方式又叫 polling，轮询。过程：CPU 启动设备；CPU 不断检查设备状态；直到设备完成 I/O。缺点很明显：CPU 一直等设备，不能去做别的事情，效率低。适合非常简单、低速或特殊场景。2. 中断驱动方式中断方式的基本思想：CPU 启动设备；CPU 去执行其他程序；设备完成后发中断；CPU 转去执行中断处理程序。优点：CPU 和设备可以并行工作；比轮询效率高。缺点：如果每传一个字节就中断一次，中断次数太多；CPU 仍然需要参与数据传输过程。3. 内存映射 I/O内存映射 I/O 是把设备寄存器映射到内存地址空间。也就是说，CPU 不一定用专门的 IN/OUT 指令，而是用普通的内存读写指令访问设备。例如课件中提到类似：MOV 6, R0 MOV R0, 6这里的思想是：把设备端口当成内存地址来访问。4. DMA 方式DMA 是 Direct Memory Access，直接内存访问。核心思想：数据传输不再由 CPU 一个字节一个字节搬运，而是由 DMA 控制器直接在设备和内存之间传送数据。DMA 的特点：基本单位是数据块；数据在设备和内存之间传输；需要 DMA 控制器；一整块数据传完后才中断 CPU；CPU 只负责设置传输方向、内存地址、数据长度等参数。DMA 过程大致是：CPU 把内存起始地址、传输长度等参数交给 DMA 控制器；CPU 写入操作码，启动 DMA；DMA 控制器控制设备和内存之间传输；每传一块或一批数据更新计数器；全部完成后 DMA 控制器中断 CPU；CPU 检查 DMA 状态寄存器。5. 中断方式和 DMA 方式的区别对比项中断方式DMA 方式中断频率每个字节/字符可能中断一次一块数据完成后中断一次数据传输者CPU 在中断处理中完成DMA 控制器完成CPU 负担较重较轻适用场景低速设备高速块设备一句话记忆：中断方式是“设备提醒 CPU 来搬”；DMA 是“DMA 控制器帮 CPU 搬”。6. 通道方式通道方式是 DMA 的进一步发展。通道可以理解为：专门负责 I/O 的小处理机。通道有自己的指令系统，可以执行通道程序。CPU 不再细管每个数据块，而是把一个通道程序交给通道，由通道负责多个数据块甚至多个设备的 I/O 操作。通道方式的优点：进一步减少 CPU 干预；一个通道可以控制多个设备；可以实现 CPU、通道、I/O 设备三者并行；适合大型机或复杂 I/O 系统。7. DMA 和通道的区别对比项DMA通道控制粒度一个数据块一组数据块或一个通道程序控制者DMA 控制器通道CPU 参与CPU 设置地址、长度、方向CPU 只需启动通道程序控制设备数量一个 DMA 控制器通常对应一台设备一个通道可控制多台设备智能程度较低较高，有指令系统一句话记忆：DMA 是“搬运工”，通道是“会执行 I/O 程序的专职管家”。8. 通道类型通道类型特点适用设备字节多路通道按字节分时复用，多个子通道轮流服务低速设备数组选择通道一次只服务一个高速设备，传完再释放高速设备数组多路通道多个高速设备之间分时执行通道程序高速设备9. I/O 接口 / 设备控制器设备控制器位于 CPU 和设备之间。功能包括：接收 CPU 命令；数据交换；报告设备状态；地址识别；数据缓冲；差错控制。I/O 端口通常包括三类寄存器：寄存器作用数据寄存器暂存 CPU 与设备交换的数据状态寄存器表示设备是否就绪、是否出错控制寄存器CPU 写入命令，用来启动设备或设置模式六、9.5 I/O 软件的层次I/O 软件分层是为了降低复杂度、提高可移植性。从上到下：用户 I/O 软件 ↓ 与设备无关的系统软件 ↓ 设备驱动程序 ↓ 中断处理程序 ↓ 硬件1. 用户 I/O 软件这一层提供给用户程序使用的接口。例如 Linux 中：open() read() write() close()用户程序调用这些接口，不需要知道具体硬件细节。2. 与设备无关的系统软件这一层在操作系统内核中。主要作用：提供统一系统调用接口；把逻辑设备名映射为物理设备名；实现设备保护；管理缓冲；提供统一块大小；报告错误；完成设备分配与去配。关键点：设备无关性就是让用户程序不依赖具体物理设备。3. 设备驱动程序设备驱动程序是与硬件最密切相关的软件。它的作用是：把上层抽象 I/O 请求转成具体硬件命令；检查请求合法性；设置设备工作方式；启动设备；构造通道程序；响应设备或通道中断；向上层返回状态信息。例如磁盘驱动程序需要计算请求块的实际位置、检查磁头位置、向控制器发命令、检查错误等。4. 中断处理程序中断处理程序负责：保存被中断进程现场；转入中断处理；检查中断原因；唤醒等待 I/O 完成的进程；恢复现场并返回。5. 一次 read 的完整流程以读文件为例：用户程序调用 read → 用户 I/O 软件解释请求 → 与设备无关层先查缓存 → 如果缓存没有，调用设备驱动 → 设备驱动启动磁盘 → 进程阻塞等待 → 磁盘完成后产生中断 → 中断处理程序唤醒进程 → 用户进程继续执行这个流程非常适合出简答题。七、9.6 同步 I/O 与异步 I/O1. 同步 I/O同步 I/O 的特点：发出 I/O 请求后，进程必须等待 I/O 完成才能继续执行。也就是：请求 I/O → 阻塞等待 → I/O 完成 → 继续执行同步 I/O 编程简单，但并行性较差。2. 异步 I/O异步 I/O 的特点：发出 I/O 请求后，进程不必等待，可以继续执行；I/O 完成后由系统通知。也就是：请求 I/O → 立即返回 → 进程继续执行 → I/O 完成后通知进程异步 I/O 能提高并行性，但实现复杂，需要中断、回调、系统线程等机制配合。八、9.7 设备的分配与去配1. 基本概念设备分配就是系统根据设备类型和调度算法，把设备分给请求进程。设备去配就是进程用完设备后，系统把设备收回。2. 相关数据结构课件中出现了几个重要控制块：数据结构含义内容UCB设备控制块设备标识、设备状态、相连通道、占有进程CUCB控制器控制块控制器标志、状态、相连通道、占有进程CCB通道控制块通道标识、状态、类型、占有进程SDT系统设备表设备类、设备总数、等待队列、UCB 指针这些结构本质上是：操作系统用来记录“设备是谁、忙不忙、被谁占用、谁在等待”的表。3. 独占型设备的分配过程独占型设备使用过程：申请 → 使用 → 使用 → …… → 释放申请过程：根据设备类查 SDT；执行 P(Sm)，判断是否有可用设备；查 UCB 表，找到空闲设备并分配。使用过程：分配通道；分配控制器；形成通道程序；启动通道；等待中断处理；去配通道。释放过程：查 SDT；查 UCB；收回设备；执行 V(Sm)，释放资源。这里的 P/V 操作就是信号量机制，P 表示申请资源，V 表示释放资源。4. 共享型设备的分配共享型设备通常是块设备，比如磁盘。特点：不需要长期申请和释放整个设备；每次读写一个块；请求来自文件系统或虚拟存储系统；通常需要缓冲；需要进行请求队列调度。九、9.8 设备驱动设备驱动程序具有以下特点：与硬件密切相关；每类设备都需要对应驱动；驱动程序通常由设备厂商按照操作系统接口要求编写；操作系统主要规定驱动接口。通道程序与设备启动在有通道的系统中，设备驱动需要：形成通道程序，也就是 CCW 指令序列；把通道程序起始地址放入指定位置；CPU 执行启动通道指令；通道依次执行通道指令；I/O 完成后通道中断 CPU。核心关系：CPU 启动通道 通道执行通道程序 控制器控制设备 设备完成 I/O 通道发中断 CPU 处理中断十、9.9 设备调度：磁盘调度是重点==易考计算题。==1. 磁盘访问时间读写一个磁盘块的时间由三部分组成：磁盘访问时间 = 寻道时间 + 旋转延迟 + 传输时间2. 寻道时间寻道时间是磁头移动到目标柱面所需时间。公式：Ts = m × n + s其中：n：跨越的磁道数；m：跨越一个磁道所需时间；s：启动时间。3. 平均旋转延迟磁盘转一圈的时间是 1/r。平均等待半圈，所以：Tr = 1 / (2r)注意：如果题目给的是 rpm，即每分钟转数，要先换算成每秒转数。例如 6000 r/min：6000 r/min = 100 r/s Tr = 1 / (2 × 100) = 0.005s = 5ms4. 传输时间公式：Tt = b / (rN)其中：b：读写字节数；r：磁盘转速；N：每条磁道上的字节数。如果每个磁道有 M 个扇区，读写一个扇区：Tt = 1 / (rM)5. 磁盘调度算法1）FCFS：先来先服务按照请求到达顺序服务。优点：公平；简单。缺点：磁头移动距离可能很大；效率低。2）SSTF：最短寻道时间优先每次选择离当前磁头最近的请求。优点：平均寻道时间较短；效率较高。缺点：可能产生“磁道歧视”；远处请求可能长期等待，甚至饥饿。3）SCAN：扫描算法 / 电梯算法磁头沿一个方向移动，路过请求就服务；到达端点或无请求后反向。优点：比 FCFS 高效；比 SSTF 更公平。缺点：中间磁道可能比边缘磁道更容易得到服务，存在地域差别。4）C-SCAN / CLOOK：循环扫描磁头只在一个方向上服务请求，到一端后快速回到另一端继续扫描。特点：所有磁道地位更接近；最长等待时间相对均衡。课件中的例题使用 CLOOK。5）N-step SCAN把请求队列分成长度为 N 的子队列，每个子队列内部用 SCAN。特点：N 很大时接近 SCAN；N = 1 时退化为 FCFS。6）FSCANFSCAN 把请求分成两个队列：服务队列；请求队列。当前只扫描服务队列，新来的请求进入请求队列。服务队列完成后，两队列交换。优点：克服磁头粘性；防止饥饿。6. 磁盘调度计算题方法做题步骤固定：画出磁道编号；标出当前磁头位置；判断当前移动方向；根据算法写出服务顺序；计算磁头总移动量；计算寻道时间；加上旋转延迟；加上传输时间。例如课件例题：当前磁头在 100，方向由外向内，请求为 120、85、70、30，采用 CLOOK。移动序列：100 → 120 → 30 → 70 → 85跨越磁道数：20 + 90 + 40 + 15 = 165若每移动一个磁道 1ms，则寻道时间为：165ms转速 6000 r/min = 100 r/s：平均旋转延迟 = 1/(2×100) = 5ms4 次访问：4 × 5ms = 20ms每磁道 100 个扇区：单扇区传输时间 = 1/(100×100) = 0.1ms 4 次 = 0.4ms总时间：165 + 20 + 0.4 = 185.4ms十一、9.10 缓冲与缓存这是本章另一个核心。1. 为什么需要缓冲？引入缓冲的原因：缓和 CPU 与 I/O 设备速度不匹配；减少 CPU 中断频率；放宽对中断响应时间的限制；提高 CPU 和设备之间的并行性。2. Buffering 和 Caching 的区别概念中文作用Buffering缓冲解决速度不匹配Caching高速缓存把慢速设备上的活跃数据放到快速存储器中一句话区分：缓冲解决“速度不一致”；缓存解决“重复访问太慢”。例如：打印输出前先放到缓冲区，这是 buffering；磁盘数据块读入内存后，下次直接从内存取，这是 caching。3. 缓冲分类按位置分类型位置硬缓冲设备内部软缓冲内存的系统区，由操作系统管理按使用方式分类型特点私用缓冲一个缓冲区固定服务一个设备，利用率低公用缓冲系统统一管理，按需分配，利用率高4. 单缓冲只有一个缓冲区。输入时：设备 → 缓冲区 → 用户进程缺点：设备和 CPU 对缓冲区的操作基本串行，并行性有限。5. 双缓冲设置两个缓冲区。一个缓冲区由设备写入，另一个缓冲区由 CPU 复制到用户空间，两者可以交替进行。优点：提高设备和 CPU 的并行性。缺点：仍然比较简单；实际系统中常用更复杂的多缓冲或缓冲池。6. 循环缓冲设置多个缓冲区，连接成环。两个重要指针：指针含义in指向当前输入位置out指向当前取出位置通过增加缓冲区数量，可以进一步提高并行程度。7. 缓冲池缓冲池由多个系统公用缓冲区组成。缓冲区按状态可分为：空缓冲队列；输入队列；输出队列。申请缓冲区：P(buf_num) P(mutex) 取链头空缓冲 V(mutex)释放缓冲区：P(mutex) 空缓冲入链尾 V(mutex) V(buf_num)这里体现了两个同步问题：buf_num 控制空缓冲数量；mutex 保证缓冲队列互斥访问。十二、UNIX 缓冲机制课件后半部分详细讲了 UNIX 缓冲，这是偏难但很重要的内容。1. UNIX 缓冲的目的UNIX 缓冲既可以作为：缓冲区；外存块在内存中的高速副本。也就是说，它同时具有 buffering 和 caching 的功能。2. 字符型缓冲字符型设备缓冲区结构大致是：struct cblock { struct cblock *c_next; char info[6]; }字符设备控制结构：struct clist { int c_cc; // 字符计数 int c_cf; // 链头 int c_cl; // 链尾 }理解即可，不必死背代码细节。3. 块型缓冲块型缓冲区包括：缓冲区头部 struct buf；缓冲区体 buffersNBUF；空闲缓冲区链 bfreelist；设备表 devtab。4. b 链和 d 链这是 UNIX 缓冲的重点。链作用b 链高速缓存链，记录缓冲区和磁盘块的对应关系d 链真正的 I/O 请求队列一个块缓冲区可以同时在：bfreelist 空闲链；某设备的 b 链。原因是：即使这个缓冲区暂时空闲，它里面的数据仍然可能是某个磁盘块的有效缓存，将来还能复用。但是：一个缓冲区不能同时属于 d 链和 bfreelist 链。因为 d 链表示它正在等待或进行真正 I/O。5. 常见函数函数作用getblk(dev, blkno)为指定磁盘块分配缓冲区bread(dev, blkno)读一个块，必要时发起磁盘 I/Obreada(dev, blkno, rablkno)读当前块，同时预读下一块bwrite(bp)同步写，等待写完bawrite(bp)异步写，不等待完成bdwrite(bp)延迟写，先标记，暂不立即写磁盘brelse(bp)释放缓冲区6. getblk 的核心逻辑getblk 的作用是：为某个设备块找到或分配一个缓冲区。几种情况：块在 b 链中，且空闲 直接取出，标记 BUSY。块在 b 链中，但 BUSY 等待该缓冲区空闲。块正在 I/O 队列中 等待 I/O 完成。块不在 b 链中，但取到的是延迟写缓冲区 先把旧内容写回磁盘，再分配下一个。空闲链为空 等待任意缓冲区变空闲。找到普通空闲缓冲区 从旧 b 链摘除，加入新 b 链，填写头部。7. bread 的核心逻辑bp = getblk(dev, blkno) 如果 B_DONE 为真，说明缓存中已有有效数据，直接返回 否则启动磁盘读 进程 sleep 等待读盘完成 中断到来后标记 B_DONE 唤醒进程 返回 bp8. 预读 breada预读的思想：如果系统预测进程会顺序访问后续块，就提前把下一个块读入缓冲区。好处：真正访问该块时可以直接从缓存获得；减少 I/O 等待。风险：预测可能不准；所以预读块也可以放入空闲链，资源紧张时被回收。9. 延迟写 bdwrite延迟写的思想：写操作不一定立刻写回磁盘，而是先放在缓冲区中，等以后合适时机再写。好处：如果之后继续写同一块，不必反复读写磁盘；如果之后读该块，可以直接从缓冲区读；减少磁盘 I/O 次数。风险：系统崩溃时可能丢失尚未写回磁盘的数据。十三、9.11 输入输出进程输入输出进程是专门负责 I/O 传输的进程。这是一种服务模式，也就是 C/S Model：用户进程 → 发送 I/O 请求消息 → I/O 进程 → 完成 I/O优点：界面清晰；使用方便；多个进程的 I/O 请求可以统一排队调度。缺点：需要进程间通信；通常会产生两次进程切换，速度可能受影响。十四、9.12 RAID 技术RAID 是 Redundant Array of Independent Disks，也可以称为廉价磁盘冗余阵列。核心思想：把多个物理磁盘组织成一个逻辑磁盘，通过并行和冗余提高速度、容量和可靠性。RAID 目标：增大容量；提高性能；提高可靠性。1. RAID 0：数据分条特点：按块把数据分散到多个磁盘；读写速度快；空间利用率 100%；没有冗余；任一磁盘坏了都可能导致数据丢失。关键词：速度快，成本低，无容错。2. RAID 1：镜像特点：每份数据保存两份；可靠性高；读性能好；写操作需要写两个盘；空间利用率 50%。关键词：可靠性强，成本高。3. RAID 2：位级汉明码校验特点：位级分条；使用汉明码纠错；可以发现 2 位错误，纠正 1 位错误；需要多个校验盘；成本高；要求磁盘同步，不能很好并行服务多个请求。关键词：纠错能力强，但复杂且成本高。4. RAID 3：位级单奇偶校验特点：位级分条；使用一个奇偶校验盘；有一定容错能力；成本比 RAID 2 低；读写需要访问所有盘；多个请求不容易并行。关键词：一个校验盘，位级分条。5. RAID 4：块级异或校验特点：块级分条；使用独立校验盘；读操作可以并行；写操作都要更新校验盘，所以校验盘容易成为瓶颈。校验恢复公式思想：block7 = P4~7 XOR block4 XOR block5 XOR block6关键词：块级分条，单独校验盘，写瓶颈。6. RAID 5：块级分布式异或校验特点：块级分条；校验信息分布在各个磁盘上；避免 RAID 4 的单校验盘瓶颈；读操作可并行；不涉及相同数据盘和校验盘的写操作也可并行；至少需要 3 个磁盘；任意一个磁盘故障都可恢复。若有 N 个磁盘，每个容量 S：有效容量 = S × (N - 1) 磁盘利用率 = (N - 1) / N关键词：RAID 5 是常考重点：分布式校验，容量损失一个盘。7. RAID 对比表RAID分条粒度读并行写并行冗余/容错RAID 0块支持支持无RAID 1块支持支持镜像，强RAID 2位不支持不支持汉明码，强RAID 3位不支持不支持单奇偶校验RAID 4块支持不支持独立校验盘RAID 5块支持支持分布式校验十五、9.13 虚拟设备与 SPOOLing1. 虚拟设备的概念虚拟设备是：在一类物理设备上模拟另一类物理设备的技术。更具体地说：用高速共享设备，例如磁盘，来模拟低速独占设备，例如打印机，使多个进程感觉自己独占了设备。虚拟设备的目的：提高设备利用率；缓和 CPU 与设备速度不匹配；把独占设备改造为共享设备；提高用户使用方便性。2. 为什么需要虚拟设备？独占型设备的使用过程是：申请 → 使用 → 使用 → …… → 释放问题：进程占有设备期间，不一定一直使用；CPU 很快，设备很慢；设备利用率低；其他进程只能等待。解决方法：在进程和独占设备之间增加共享设备缓冲，比如磁盘。3. 输入型虚拟设备流程：申请虚设备 申请实设备 实设备 → 虚设备，连续传输 释放实设备 之后进程从虚设备间断读取 最后释放虚设备例子：作业预输入：输入机先把作业读入输入井，进程之后从输入井读取。4. 输出型虚拟设备流程：申请虚设备 进程 → 虚设备，间断传输 释放时申请实设备 虚设备 → 实设备，连续传输 释放实设备 释放虚设备例子：打印输出：进程先把输出写到输出井，打印机之后连续打印。5. SPOOLing 系统SPOOLing 是典型虚拟设备技术。SPOOLing 输入：输入机 → 输入井也叫作业预输入。SPOOLing 输出：输出井 → 输出机也叫作业缓输出。SPOOLing 的特点：提高 I/O 速度；缓和 CPU 与 I/O 设备速度矛盾；把独占设备改造成共享设备；实现虚拟设备功能；用户感觉自己独占了一台设备。十六、9.14 稳定存储器稳定存储器是指：不丢失信息的存储器。但现实中不存在绝对可靠的存储介质，所以稳定存储器通过冗余实现。1. 保存策略在两个失效独立的介质上保存同一份数据。保存过程：先写第一个存储块 成功后再写第二个存储块 两次都成功，才认为保存完成2. 恢复策略恢复时比较两个块：情况处理两块内容相同且无错误正常一块检测到错误用另一块替换它两块都没检测到错误但内容不同用第二块内容替换第一块这个地方常考简答题：稳定存储器不是因为硬件绝对可靠，而是通过冗余和恢复协议让它“表现得可靠”。十七、9.15 系统举例最后几页补充 DLL、NTFS、IRP、APC、Windows I/O 子系统。主要内容包括：设备驱动；设备缓冲；文件管理；动态添加或删除设备；驱动程序动态加载；I/O 请求包 IRP；I/O 管理器协调各组成部分；通过系统线程提供异步 I/O；异步过程调用 APC。Windows I/O 子系统大致结构：应用程序 ↓ 系统服务 ↓ I/O 管理程序 ↓ 文件系统 ↓ 磁盘驱动 / 光盘驱动 / 磁带驱动等十八、梳理I/O 控制方式比较 查询、中断、DMA、通道的区别。DMA 与中断方式区别 中断方式每次传输由 CPU 处理；DMA 方式整块传完才中断。DMA 与通道区别 DMA 控制一个块，通道执行通道程序，可控制多设备。I/O 软件层次 用户 I/O 软件、设备无关层、驱动程序、中断处理程序。磁盘访问时间公式访问时间 = 寻道时间 + 旋转延迟 + 传输时间磁盘调度算法 FCFS、SSTF、SCAN、C-SCAN/CLOOK、N-step SCAN、FSCAN。缓冲与缓存区别 缓冲解决速度不匹配；缓存解决重复访问慢。RAID 0/1/5 RAID 0 速度快无容错；RAID 1 镜像可靠但成本高；RAID 5 分布式校验，容量损失一个盘。SPOOLing 把独占设备虚拟成共享设备。稳定存储器 通过冗余和恢复策略保证数据可靠。十九、简答题模板1. 简述设备管理的目标设备管理的目标包括方便性、并行性、均衡性和独立性。方便性是指为用户提供统一接口，减轻编程负担；并行性是指充分发挥 CPU 与设备、设备与设备之间并行工作的能力；均衡性是指多个进程竞争设备时系统能合理分配和调度；独立性是指用户使用逻辑设备名，由系统完成逻辑设备到物理设备的映射。2. 简述 DMA 方式的工作过程DMA 方式下，CPU 首先把传输方向、内存起始地址、传输长度等参数交给 DMA 控制器，并启动设备。之后 CPU 可以继续执行其他程序。DMA 控制器直接控制设备和内存之间的数据传输，传输过程中不需要 CPU 逐字节参与。当整个数据块传输完成后，DMA 控制器向 CPU 发出中断，CPU 再检查传输状态。3. 简述 I/O 软件层次I/O 软件从上到下分为用户 I/O 软件、与设备无关的系统软件、设备驱动程序和中断处理程序。用户 I/O 软件提供 open/read/write/close 等接口；设备无关层实现统一系统调用、设备保护、缓冲、逻辑设备到物理设备的映射；设备驱动程序把抽象 I/O 请求转化为具体硬件操作；中断处理程序负责响应设备完成或出错中断，并唤醒等待进程。4. 简述 SPOOLing 技术SPOOLing 是一种虚拟设备技术，它利用磁盘等高速共享设备作为输入井或输出井，把低速独占设备改造成逻辑上的共享设备。输入时，作业先由输入设备连续送入输入井；输出时，进程先把结果写入输出井，再由输出设备连续输出。它能提高 I/O 速度，缓和 CPU 与设备速度不匹配，并使多个用户感觉自己独占了设备。5. 简述 RAID 5RAID 5 采用块级数据分条，并把异或校验信息分布存放在各个磁盘上。它避免了 RAID 4 中单独校验盘成为写瓶颈的问题。RAID 5 至少需要 3 个磁盘，任意一个磁盘故障时，可以利用其他磁盘的数据和校验信息恢复。若有 N 个容量为 S 的磁盘，则有效容量为 S×(N−1)，磁盘利用率为 (N−1)/N。二十、辨析容易混淆点正确理解缓冲 vs 缓存缓冲解决速度不匹配；缓存解决重复访问慢中断 vs DMA中断方式 CPU 参与搬数据；DMA 控制器搬数据DMA vs 通道DMA 管一块传输；通道执行通道程序，控制更复杂 I/O字符设备 vs 块设备字符设备低速不可寻址；块设备高速可寻址独占设备 vs 共享设备独占设备使用时不能交叉；共享设备可以按块交叉服务RAID 0 vs RAID 1RAID 0 快但无容错；RAID 1 镜像可靠但浪费空间RAID 4 vs RAID 5RAID 4 单独校验盘；RAID 5 校验分布到各盘同步 I/O vs 异步 I/O同步要等待；异步发起后可继续执行二十一、总结1. 先学 I/O 传输方式：查询、中断、DMA、通道 2. 再学 I/O 软件层次：用户层、设备无关层、驱动、中断 3. 然后攻克磁盘调度计算题 4. 接着理解缓冲、缓存、UNIX 缓冲 5. 最后背 RAID、SPOOLing、稳定存储器CPU 很快，设备很慢；设备种类复杂，用户希望统一使用。所以操作系统要通过中断、DMA、通道、缓冲、缓存、调度、驱动和虚拟设备来提高效率、屏蔽差异、保证可靠性。
Good First IssueGood = 友好的、适合新手的 First = 给第一次做开源贡献的人 good-first-issue ： 适合新手第一次贡献的优质入门任务。它是开源社区吸引新成员、降低入门门槛的重要机制，也是开启开源贡献之旅的最佳起点。声明这里首先演示 HTTPS 方式master / main 请根据实际情况选择不同环境或系统的命令不一定相同，本文仅作为参考。基础准备环境配置# 安装 git、vim sudo dnf install -y git vim git-lfs # 配置Git全局信息（与 AtomGit/GitHub 账号一致） git config --global user.name "用户名" git config --global user.email "注册邮箱" # 查看配置是否生效 git config --list 访问令牌在个人设置→安全设置之类的地方找到访问令牌，生成新令牌（勾选 repo 权限），复制并保存生成的令牌字符串（该字符串只显示一次，务必保存好）ForkFork 相当于「把官方仓库复制一份到自己的账号下」，是参与项目的第一步。 页面右上角的 Fork 按钮，选择 Fork 到自己的账号下。Clone克隆 = 「把远程仓库下载到本地」，必须克隆「自己 Fork 后的仓库」，而非官方仓库。打开本地终端；进入想存放代码的文件夹（比如 cd ~/code，表示进入用户目录下的 code 文件夹）；执行克隆命令（替换成自己 Fork 后的仓库地址）git clone https://******.git 进入克隆后的仓库目录cd 仓库名配置上游官方仓库（该动作仅需 1 次）目的是让本地仓库「知道官方仓库的地址」，方便后续同步。确保终端在仓库目录下（已执行 cd 仓库名 ）；执行添加命令git remote add upstream 官方仓库地址验证是否添加成功git remote -v 正常的输出大概是： origin https://***.com/我的账号/***.git (fetch) origin https://***.com/我的账号/***.git (push) upstream https://***.com/官方账号/***.git (fetch) upstream https://***.com/官方账号/***.git (push)同步官方仓库的最新代码官方仓库会不断更新，每次本地开发前，先同步上游代码，避免本地的修改和官方代码冲突。确保当前在仓库根目录；拉取官方仓库的最新代码（只下载，不修改本地文件）git fetch upstream 切换到本地主分支（master/main）git checkout master合并官方最新代码到本地主分支（最终同步）git merge upstream/master验证同步成功git log --oneline # 同步上游代码后，验证本地是否是官方最新版 创建并切换到开发分支严禁在主分支（master/main）修改代码，因为这会导致代码混乱。 所以需要新建一个专属分支用来修改/更新，任务完成后直接提交这个分支即可。分支命名规范格式：issue-编号-简单描述示例：修复文档：issue-1234-fix-docs修复错别字：issue-5678-fix-typo完善注释：issue-9012-add-commentIssue 数字编号方便之后关联任务创建 + 切换分支git checkout -b issue-1234-fix-docs checkout：切换分支-b：create branch（创建新分支）自定义分支名# 分开的命令 git branch 分支名 # 创建分支 git checkout 分支名 # 切换分支验证分支切换成功 执行命令查看当前所在分支：git branch 示例效果：* issue-1234-fix-docs # 前面带*的分支，就是当前正在使用的开发分支 master 开发# 打开需要修改的文件（根据Issue要求，例：修改README.md） vim README.md # Vim 基础操作（新手必看） # 按 i → 进入编辑模式 # 修改内容（修复错别字、补全注释、调整格式等） # 按 Esc → 退出编辑模式 # 输入 :wq → 保存并退出 # 输入 :q! → 不保存退出（放弃修改） # 查看修改内容（确认修改正确） git diff 提交与推送当我们完成本地开发后，需要把修改推到自己 Fork 的仓库（ origin 仓库）。# 1. 添加修改的文件到暂存区 git add 文件名 # 例：git add README.md # 或 git add . （添加所有修改文件） # 2. 提交修改（注意社区规范） # 提交单行msg git commit -m "docs: fix typo in README (fix #1234)" # 提交多行msg git commit # 自动弹出vim编辑 # 想要修改msg git commit --amend # 验证提交的信息 git log -1 # 3. 推送到自己的 Fork 仓库（origin） git push origin 本地分支名 如果已经push，想要修改msg：# 先修改 msg（同上） git commit --amend # Vim 修改 → :wq 保存 # 强制同步到远程 git push origin 本地分支名 --force-with-lease 推送时需要认证个人访问令牌（GitHub也可以采用账号密码的方式认证），终端会提示：Username for 'https://github.com': 输入github用户名 Password for 'https://xxx@github.com': 输入之前保存的个人访问令牌 提交 PR在网页打开自己 Fork 的仓库：https://github.com/你的用户名/仓库名Pull Requests → 新建 Pull Request配置参数：目标仓库：官方账户/官方仓库（官方）目标分支：master源仓库：你的个人 Fork源分支：你创建的分支（例如issue-1234-fix-docs）填写 PR 标题 / 描述 （注意社区规范）点击 创建 Pull Request，完成提交。PR 审查与修改维护者会审核提交的代码，若需要修改已经提交pr的内容：# 1. 切换回到本地开发分支 git checkout issue-1234-fix-docs # 2. Vim修改文件 vim README.md # 3. 提交并推送 git add README.md git commit --amend --no-edit git push origin issue-1234-fix-docs --force-with-lease解释：git commit --amend --no-edit # 把新修改的代码，合并到上一次的提交里，不新增任何提交记录，保持代码历史干净整洁。 # git commit：提交代码 # --amend：修改/追加到「最后一次提交」（不新建提交） # --no-edit：不修改提交说明，直接沿用原来的备注git push origin issue-1234-fix-docs --force-with-lease # 安全地把修改后的代码，强制同步到远程的分支上（因为修改了历史提交，普通 git push 会报错） # git push：推送代码 # origin：自己的个人 Fork 仓库 # issue-1234-fix-docs：自己的开发分支名 # --force-with-lease：安全强制推送 PR 合并后的清理贡献完成后，清理本地分支：# 切换回主分支 git checkout master # 同步官方最终代码 git fetch upstream git rebase upstream/master # 删除本地开发分支 git branch -d issue-1234-fix-docs FAQ同步时提示 “有冲突” 怎么办？ 先执行 git stash 暂存自己的修改，同步完上游代码后，再执行 git stash pop 恢复自己的修改，手动解决冲突（冲突文件里会有 <<<<< ===== >>>>> 标记，保留需要的代码即可）。如果不小心加错 upstream 地址？ 执行 git remote remove upstream 删除错误的，再重新执行 git remote add upstream 正确地址 即可。提示令牌权限不足怎么办？ 重新生成令牌，务必勾选 repo 全权限。修改PR后，需要重新「新建 Pull Request」吗？ 不用。修改代码后，推送的是同一个分支，原来创建的 PR，会自动同步最新代码，维护者能直接看到修改后的内容。解决 VMware 中 openEuler 24.03 无法粘贴外部内容的问题# 安装 / 修复 open-vm-tools # 1. 更新系统 sudo dnf update -y # 2. 卸载旧版本（如有） sudo dnf remove open-vm-tools* -y # 3. 安装完整组件 sudo dnf install open-vm-tools open-vm-tools-desktop -y # 4. 重启服务 sudo systemctl restart vmtoolsd # 5. 重启系统（确保生效） sudo reboot理解 Fork 与 PR 的关联PR 不存储代码，只指向代码：PR 里的所有修改、文件、提交记录，全部来自 Fork 里的对应分支；两者是「指针关系」，不是「复制关系」。在提交 PR 后、合并前，修改自己的 Fork，PR 会变吗？会。PR 会自动同步更新，无需重新提交 PR。一旦原项目管理员合并了 PR，或者关闭了 PR，两者的绑定就断开了。再改 Fork 也不会影响原项目。文章小结本文梳理了Good First Issue 贡献全流程： 「 Fork 克隆→配上游→建分支→改代码→推送到自己仓库→提 PR 」 包括部分 Git 命令、开源规范、令牌使用、冲突解决以及FAQ。
这次寒假我又来到了深圳，而上一次来深圳是三年前。 三年前，随着乘坐的大巴车缓缓驶入市中心，一座座高楼大厦鳞次栉比，若非抬头仰望极致，便无法看到楼顶。如同走进一片大森林，只不过这里的树木是从钢筋混凝土中生长出来的，身披耀眼夺目、闪闪发光的时装，巍然挺立，直至云霄。听说，在多云的天气里，平安金融中心的中国第二高楼能够在飞云之上，如果这时能在顶楼观赏一番天庭的风起云涌，那将是多么奇妙的体验啊。 灰青色西装的颜色，在我眼中就是这个城市的主题色。这种颜色，带着理性的冷静，让着南方的炙烤不再那样令人焦灼。灰青色不像黑色那样单调，但又比天青色更多一些沉稳，手提公文包的青年，步履迅捷，目光如炬，在快节奏的世界中保持着自在的节奏与从容。 三年前的我看到此般景象，兴奋极了。这就是我想要的生活，能在时代的潮流与科技的前沿行走，本身就是件令人兴奋的事情。那次的游览，就像一道光，照亮那段充满焦灼与挣扎却又无法选择的路。 上次是直接乘坐大巴车进了市中心，而这次我选择从高铁站缓缓走进这座城市。街边的手机店让我想起千禧年盛行的小灵通；上世纪八十年代的居民楼虽然已经有了明显的历史痕迹，但仍然宣示着它的摩登与骄傲；小吃和饭店传承着广州和佛山的烟火气，又有着现代的快捷和工整。 纵然已经有了心理预期，但是当我再次站在摩天大楼的脚下，仍然感受得到震撼与惊叹。一座座连绵的巨厦连接成钢铁城墙，每当走过一幢高楼，以为其后仅为平坦的空旷时，没想到又一幢更高的楼宇闪现而出，于是更加觉得自己的渺小以及在楼宇间穿行的畅快。在一处连红绿灯都没有的小小的十字路口，四周却矗立着不知几百米高的壮阔玻璃巨柱，在这种对比下，形成了强烈的视觉冲击。 再登莲花山，我阴差阳错地选择了与上次相反的路，从下山的路上山，从上山的路下山。这颇有一番寓意，刚好可以与三年前的自己在山顶迎面相遇，那时，他一定想不到我会在这个时间再次来这里。1从山顶眺望，整个深圳的高楼像一幅对称的画卷徐徐展开。山顶才一百米左右，可是从这里看五百多米的高楼，感觉也没比自己高多少，只能说这个观景台的设计恰到好处，精妙绝伦！我们还遇到了一位伯伯，想要让我帮他拍照，我拍完之后，他又在旁边等人流过去帮我们拍照。他还非常细心地提醒旁边的游客避让一下，以便镜头中没有其它的人。这对于我这个深度社恐来说简直是壮举，真的不胜感激。这是这次旅行为数不多的与陌生人交流的体验，愈发感觉到，人与人之间的善意真的很珍贵，更有一种奇妙的魔力。 从山顶眺望 欣赏过后山顶的景色，我们返程下山。刚好走到一片树荫下，积蓄已久的乌云突然下起了大雨，春雨贵如油，停留片刻，再次走出树荫时，这春雨便停了，甚至连雨伞都没有派上用场。我赞叹，这春雨来得刚刚好，只需“嘭嘣”两滴，就滋润了这片神州大地，趁着回南天还没有到来，顿觉雨后的空气清爽宜人，万物澄澈明净，毓秀园的树木🌳更加绿意盎然。 下山后，我们去了梅拉尼亚特色街区。这里的墙壁五彩斑斓，宛若童话世界，是个“出片”的好去处。看到了盛大的喷泉有花团锦簇围绕，小蜜蜂也来观赏啦~（还看到有一家外国人来玩，父母带着四个小孩，两个大点的八九岁，两个小点的一两岁，看着都有点累啊😅） 世界之窗 花丛中的小蜜蜂 回来时，恰逢蓝调时刻，拍下这壮观的楼群。 蓝调时刻的楼群 看到动作敏捷的城市美容师在深圳的通道间穿行。高楼在身后生长，把平凡日子走成向上的模样。2 邓小平同志塑像 城市美容师 蓝调浸透了整片钢铁森林，皎白的灯光次第亮起。怀揣着这座城的温柔与热烈，相信来日再见，后会有期。 为什么博主喜欢这么「玄幻」的解释啊喂！😑 ↩ 这篇文章的图片排版也是值得以后复用的！😛 ↩
嗨嗨嗨~时间过得真快，已经开学一周了。欧布，根据多年经验，开学的前两周和后两周是过得最慢的，前者是因为状态的改变和新的课表，后者当然是因为要考试了。而中间的几周是过得最快的，因为身心已经麻木，这学期，我将尝试对抗一下这种麻木感，看看在学期中还能不能感觉到自己仍是个“活人”。升级的教学楼某个教学楼曾经以奢华的厕所系统，带领四季温水和吹干机占有一席之地，却因破败与古老的座椅无人问津，偶尔也只能用来作为考古的感慨。没想到在寒假期间竟然默默发力，以独立桌面供电的强大攻势席卷而来，而且量大管饱。再配备上还能用的热水器，如今，这个教学楼已经成为我的首要栖息地了。事项List我总是喜欢给自己列一些待办事项List之类的，比如： 完成打✔读书 读博客 看“稍后再看” 打卡背单词 练习听力 课内网课 备考竞赛 投简历 写作业 然后，我就会忘记一些我要做的事情，尽管这些事情是相当重要的。最怕在做一件事的时候，突然想起来忘记的那件事，然后“失控感”、“焦虑感”、“迷茫感”席卷而来。在这种无序中，我的时间效率低下，精力分配不协调。接下来我将尝试按照紧急和重要分类，设定更加具体的项目，并且添加预期完成时间，希望有所改善。之前在找Obsidian插件的时候，发现了一位宝藏作者，也许能够从其中找到一些灵感：我的2025效率工具全景图：从知识管理到内容创作一些青春疼痛闲话长大后，觉得时间过得好快啊，一抬头，两个月过去了，一转眼，一个学期过去了，一恍惚，一年就过去了。 与我个人而言，高能量时间仅占有十分之一。在大部分的时间里，都类似于荒原上枯黄的野草，病怏怏地野蛮生长。 当我不再幻想未来，那我该怎样面对过去。 突然意识到，我已知的知识已经无法用来缓解内心的焦虑了。
终于把博客给复活了！ 上学期期末的时候，突然觉得满目灰暗、世事苍凉、寰宇沉郁，然后我就删库跑路了…… 不幸中的万幸，删库之前，从宝塔面板中备份了“文件”-“域名”和“数据库”的内容，下文将记录一下怎么用这两个备份恢复整个网站。购买服务器，选择宝塔面板镜像，登录进宝塔面板。网站-添加站点，在传统项目下填写“域名”，“备注”和“根目录”会自动生成，FTP可以选择“不创建”，数据库选择“创建”，PHP版本选择7.4+（若仅显示“纯静态”，不用担心，稍后需要安装PHP）。然后点击确定。进入数据库，导入之前备份的数据库。进入文件，导入之前备份的文件。注意：将“config.inc.php”中的数据库的名字和密码更新。来到域名提供商，将域名解析到服务器的IP地址。（如果购买了其它平台的云解析DNS，记得更新一下）至此，网站应该已经恢复了。 我在此过程中也遇到了一些问题，记录一下。 完成上述步骤之后，输入域名之后，会自动下载.bin文件，而不是进入网页。原因大概是“当 Web 服务器 (Nginx/Apache) 无法识别 PHP 文件并将其传递给 PHP-FPM 处理时，会默认返回application/octet-stream(二进制流) 类型，浏览器因此触发下载。” 解决方法：将PHP从“纯静态”切换至“7.4+”。 其实，我这次本想换个域名（换个便宜的），但因为我之间设置过伪静态，给予了每篇文章唯一的永久链接，所以当我用新的域名之后，出现了无法正常打开某些页面的情况。只能继续用这个域名了，刚好这也是一个劝说自己继续使用本域名的一个“借口”吧。 经历了这次失而复得，希望自己更懂得珍惜。往后好好写、好好更，好好守护这片小小的自留地。 这里是 Gokong Space，装的不只是文章和代码，更是走过的思绪、留下的痕迹。
我喜欢这一刻的美好。 今天逛了巴洛克式的街区，十一国庆，哪怕是在早上，也处处洋溢着欢快的空气。 一整个上午，简简单单地缓缓散步，顺便买了城市文旅局新出的一款限量文创帆布包，很幸运买到了。 接近中午，去往了一处室内的小吃街。食物很朴素，吃着很畅快舒服。 出了商场，太阳逐渐升起，但是风还是清爽的，就像这里的人们和农作物，不急不躁，不卑不亢，就这样悠闲地在空气中流动。 我又回到了巴洛克式的街区，在老式的建筑群里充满现代的浪漫主义与简约的抽象艺术，尽管这种“街区”式的商业风格在全国已经流行甚至同质化，但是我在那段困顿，不知何以解答难题的时光里第一次遇见了你，也只有你，能消化我那复杂和无目的的心思。我喜欢这里，不仅是回忆，也是期望，更是新的开始。所以我想一整天都闲散地、漫无目的地在这附近散步。 我想找一家咖啡店，坐下来敲敲日记，梳理一下这一段时间的思绪和生活。找了几处，不是过于喧嚣，就是地方狭小，或者桌椅低矮，都不适宜停留。当然我也并没有焦急，因为在这清爽的秋风里，任何焦躁的人都能被轻松安闲直爽畅快的风浸透。正打算返回时，意外在随意的一个拐角处，遇见了一家三楼靠窗，室内静谧，也能透过窗户听闻街区上的热闹的咖啡馆。我点了一杯少冰美式咖啡，找了一处靠窗的位置坐下，拿出笔记本电脑，开始写这段流水账式没有什么目的的日记。 室内有声音，但是很安静。有咖啡机的声音和人们不紧不燥的轻声交谈，也许是因为这里没有广播喇叭式地播放流行歌曲，所以显得很安静吧。室外有街头乐队的古典摇滚，有小孩子的吵闹，有人们的兴奋和放松的笑。室外的热闹与室内的安静并不会产生矛盾和冲突，就像番茄炒蛋的糖和盐一般调和中道，更显鲜美。这里是盛世，所以有欢声笑语，所以也有安静和悠然。 我选择一个人来这里。本来在这热闹的节日里，与亲人朋友逛街是一件非常快活的事，但是我一个人来也挺好。有些记忆，只属于一个人，有些感受，只属于一个人，有些追寻，亦只属于一个人。追寻那些未完成的梦想，追寻那些没有正确答案的记忆，追寻曾经的幻想中的遗憾，追寻少年时的风和阳光，然后学会放下，轻松的去面对过去也好、未来也罢。追寻曾经的幻象，并不一定需要达成，而是仅仅声明：我来过，我知道。我轻轻地走过，我向往美好，但我并不留恋。 一缕阳光洒在咖啡杯与笔记本电脑的夹缝中，清爽跳跃的秋风阵阵从穿过窗户，悠闲缓慢的古典摇滚轻柔的填满每一处的空气，树影斑驳，丛林摇曳。过去有不甘，过去有遗憾，过于有不如意和许多的不完美。 再看看当下吧。至少这一刻的风景和阳光是真实的，咖啡的回甘是真实的，时间的悠闲也是真实的，今天我坐在一处椅子上，什么都不想，什么都不打算，什么都不担忧，就这样静静地坐着。听风呢喃，观光影灿烂，阅壮丽河山，行走从容稳健。喧嚣过耳，仅留乐音。我在一处喧闹的城市深处，找到了安静。 今天的天气真好呀。在十一的快乐节日里，晴朗微云，温和的风，还有可爱的人。 萨克斯，循环明亮的艺术走廊，红砖青瓦，壁画，几处拐角，阳光和秋风的打闹，咖啡的芳香和奶油的清甜，稀疏的人流，相机咔嚓定格的瞬间，就是这里的风景。那么，最后，让我带着从容释然的笑说：一杯美式，少冰。
Rule 90.数组的异或规则是对应分量两两异或，得到一个新的数组。 Solutionmodule top_module( input clk, input load, input [511:0] data, output [511:0] q ); always @(posedge clk) begin if (load) //别忘了先加载 q <= data; else q <= {1'b0,q[511:1]} ^ {q[510:0],1'b0}; end endmodule Rule 110将左、中、右状态组合为 3 位索引，查表（Rule 110 的转移表）得到下一状态。将左、中、右分别作为三个变量（含有512个分量的数组），作卡诺图：L\C R000111100011110101于是，$C^{(n+1)} = \bar{C}R +\bar{L}C+C\bar{R}$ Solutionmodule top_module( input clk, input load, input [511:0] data, output [511:0] q ); wire [511:0] L,C,R; assign L = {1'b0,q[511:1]}; assign C = q; assign R = {q[510:0],1'b0}; always @(posedge clk) begin if(load) q <= data; else q <= (~C & R) | (~L & C) | (C & ~R); end endmodule Conway's Game of Life 16x16题意生命游戏是二维元胞自动机，每个细胞（cell）状态为1（活）或0（死），下一状态由8 个邻居的存活数决定：邻居存活数细胞下一状态0-1 个死亡（变 0）2 个保持当前状态3 个复活（变 1，即使之前是死的）≥4 个死亡（变 0）索引转换由一维索引转换到二维索引：行号 i：用idx 除以 16取整（舍去余数）；等效于： idx >> 4（右移 4 位 = 除以 2⁴）。列号 j：j = idx % 16（取模运算，即余数）；等效于按位与：idx & 15（15 的二进制是 1111，按位与之后达到保留低 4 位（即余数）的效果）。由二维索引转换到一维索引： (i, j) 的一维索引为 i * 16 + j.右移位运算符>> 是 右移位运算符：逻辑右移（>>）将二进制数向右移动，左侧空出的位用 0 填充，不考虑符号位。 语法：A >> N（将 A 右移 N 位）。算术右移（>>>）将二进制数向右移动，左侧空出的位用符号位填充（即保持负数的符号）。 语法：A >>> N（将 A 右移 N 位）。Solutionmodule top_module( input clk, input load, input [255:0] data, output [255:0] q ); reg [255:0] q_reg; // 模块级寄存器存储当前状态 assign q = q_reg; // 输出直接映射到寄存器 always @(posedge clk) begin // ------- 步骤1：声明过程块内的变量（必须放在块的开头）--------- reg [255:0] next_q; //存储下一状态（局部reg） integer idx; //当前细胞的一维索引 integer dr; //相对于当前细胞的行偏移（Delta Row） integer dc; //相对于当前细胞的列偏移（Delta Column） integer i; //当前细胞的二维行索引 integer j; //当前细胞的二维列索引 integer r; //邻居的绝对坐标的行索引 integer c; //邻居的绝对坐标的列索引 integer neighbor_idx; //邻居的一维索引 integer cnt; //邻居的存活数目 if (load) begin // 加载模式：直接赋值 q_reg <= data; end else begin // --------- 步骤2：遍历每个细胞，计算下一状态 ------------ for (idx = 0; idx < 256; idx = idx + 1) begin i = idx >> 4; // 计算当前细胞的行号（0~15） j = idx & 15; // 计算当前细胞的列号（0~15） cnt = 0; // 邻居存活数初始化 // 遍历8个邻居（dr, dc ∈ {-1, 0, 1}，左右位移只能取1或-1，所以待会需要跳过自身） for (dr = -1; dr <= 1; dr = dr + 1) begin for (dc = -1; dc <= 1; dc = dc + 1) begin if (dr == 0 && dc == 0) continue; // 跳过当前细胞 // 处理环面边界（确保行/列在0~15范围内） r = (i + dr + 16) % 16; // +16避免负数，再取模 c = (j + dc + 16) % 16; neighbor_idx = r * 16 + c; // 邻居的一维索引 // 统计存活邻居 if (q_reg[neighbor_idx]) begin cnt = cnt + 1; end end end // --------- 步骤3：根据规则更新下一状态 ------------- if (cnt < 2 || cnt >= 4) begin next_q[idx] = 1'b0; end else if (cnt == 2) begin next_q[idx] = q_reg[idx]; // 保持当前状态 end else begin // cnt == 3 next_q[idx] = 1'b1; // 复活 end end // 统一更新状态（时序安全，避免竞争） q_reg <= next_q; end end endmodule本题是硬件实现元胞自动机的经典方法！
4-bit shift register异步复位的优先级：areset为高时，立即复位（不管时钟和其他信号）。加载 vs 移位的优先级：若load为 1，无论和ena为何值，加载优先（先判断load）。右移操作的行为：右移时，q[3]补 0，q[0]被移出（例如：q=1010右移后变为0101）。Solutionmodule top_module( input clk, input areset, // async active-high reset to zero input load, input ena, input [3:0] data, output reg [3:0] q); always @(posedge clk or posedge areset) begin if (areset) begin q <= 4'b0000; end else begin if (load) begin q <= data; end else if(ena) begin q <= {1'b0,q[3:1]}; end end end endmodule Left/right rotatorSolutionmodule top_module( input clk, input load, input [1:0] ena, input [99:0] data, output reg [99:0] q); always @(posedge clk) begin if (load) // load q <= data; else if (ena) begin case(ena) 2'b00 : q <= q; // 其实这一行永远不会被执行！所以else分支可以被简化，见下方。 2'b01 : q <= {q[0],q[99:1]}; // 右旋转 （采用分块组合要比使用循环更简便） 2'b10 : q <= {q[98:0],q[99]};// 左旋转 2'b11 : q <= q; default q <= q; endcase end else begin // 当ena为0时执行 保持不变 q <= q; end end endmodule 简化else之后：always @(posedge clk) begin if (load) q <= data; else if (ena == 2'b01) q <= {q[0],q[99:1]}; // 右旋转 else if (ena == 2'b10) q <= {q[98:0],q[99]}; // 左旋转 // 其他情况（ena=2'b00或2'b11）保持不变 endLeft/right arithmetic shift by 1 or 8A 5-bit number 11000 arithmetic right-shifted by 1 is 11100, while a logical right shift would produce 01100. However, there is no difference between logical and arithmetic left shifts. Solutionmodule top_module( input clk, input load, input ena, input [1:0] amount, input [63:0] data, output reg [63:0] q); always @(posedge clk) begin if (load) begin q <= data; end else if (ena) begin case (amount) 2'b00: q <= {q[62:0],1'b0}; 2'b01: q <= {q[55:0],8'b0}; 2'b10: q <= {q[63],q[63:1]}; 2'b11: q <= {{8{q[63]}},q[63:8]}; //注意：位拼接语法 default: q <= q; endcase end end endmodule 5-bit LFSR线性反馈移位寄存器（LFSR） linear feedback shift register LFSR 是一种通过移位寄存器和反馈逻辑生成伪随机序列的电路。 Galois 型 LFSR 的特点：反馈逻辑通过 异或（XOR） 连接特定 “抽头”（tap）位，生成下一个状态的最低位（或最高位，依结构而定）。最大长度 LFSR（maximal-length）的抽头位置经过精心选择，可遍历 $2^n-1$ 个状态（n为位数，全零状态永远不会出现）。在本题中：位数：5 位（输出q[4:0]，q[4]是最高位，q[0]是最低位）抽头位置：题目指定抽头在位5和位3（编对应q[4]和q[2]）特殊说明：“抽头 5 的异或输入为 0” → 实际反馈逻辑为 q[4] ^ q[2]（异或 0 不改变值，等价于仅q[2]参与，但题目明确抽头是 5 和 3，故需保留异或）复位：同步高电平复位（reset为高时，q被置为 5'h1，即二进制 00001）移位方向：右移。其实，若只需简单解答此题，并不需要知道什么是tap。图中是一个由D触发器构成的阵列，按照图中连线即可画出。 Solutionmodule top_module( input clk, input reset, // Active-high synchronous reset to 5'h1 output [4:0] q ); always @(posedge clk) begin if (reset) // Load q <= 4'd1; else begin q[4] <= 1'b0 ^ q[0]; q[3] <= q[4]; q[2] <= q[3] ^ q[0]; q[1] <= q[2]; q[0] <= q[1]; end end endmodule下面是基于原理的官方Solution： module top_module( input clk, input reset, output reg [4:0] q); reg [4:0] q_next; // q_next is not a register always @(*) begin q_next = q[4:1]; // Shift all the bits. This is incorrect for q_next[4] and q_next[2] //q_next = q[4:1] 将当前状态q的高四位（q[4]~q[1]）赋值给 q_next 的低四位（q_next[3]~q_next[0]），实现了大部分位的右移。 q_next[4] = q[0]; // Give q_next[4] and q_next[2] their correct assignments //修正最高位 q_next[2] = q[0] ^ q[3]; //修正抽头位：该操作确保 LFSR 遍历 2^5 - 1=31 个状态（最大长度特性）。 end always @(posedge clk) begin if (reset) q <= 5'h1; else q <= q_next; end endmodule 3-bit LFSR二选一选择器： 本题仍是看图连线。 Solutionmodule top_module ( input [2:0] SW, // R input [1:0] KEY, // L and clk output [2:0] LEDR); // Q always @(posedge KEY[0]) begin LEDR[0] <= KEY[1] ? SW[0] : LEDR[2]; LEDR[1] <= KEY[1] ? SW[1] : LEDR[0]; LEDR[2] <= KEY[1] ? SW[2] : (LEDR[2] ^ LEDR[1]); end endmodule32-bit LFSR本题是 5-bit LFSR 的扩展，若实例化32个DDF显然不是很方便。 Solutionmodule top_module( input clk, input reset, // Active-high synchronous reset to 32'h1 output [31:0] q ); reg [31:0] q_next; always @(*) begin // 组合逻辑块（敏感列表为*） q_next = q[31:1]; q_next[31] = q[0]; q_next[21] = q[0] ^ q[22]; q_next[1] = q[0] ^ q[2]; q_next[0] = q[0] ^ q[1]; end always @(posedge clk) begin if (reset) q <= 32'h1; else q <= q_next; end endmodule Shift register (1)Solutionmodule top_module ( input clk, input resetn, // synchronous reset input in, output out); reg [3:0] Q; //记录每个DDF输出的Q always @(posedge clk ) begin if (!resetn) begin Q <= 0; end else begin Q[0] <= in; Q[1] <= Q[0]; Q[2] <= Q[1]; Q[3] <= Q[2]; end end assign out = Q[3]; endmodule Official Solutionmodule top_module ( input clk, input resetn, input in, output out ); reg [3:0] sr; // Create a shift register named sr. It shifts in "in". always @(posedge clk) begin if (~resetn) // Synchronous active-low reset sr <= 0; else sr <= {sr[2:0], in}; end assign out = sr[3]; // Output the final bit (sr[3]) endmodule Shift register (2)Solutionmodule top_module ( input [3:0] SW, input [3:0] KEY, output [3:0] LEDR ); MUXDFF u1 ( .clk(KEY[0]), .w(KEY[3]), .R(SW[3]), .E(KEY[1]), .L(KEY[2]), .Q(LEDR[3]) ); MUXDFF u2 ( .clk(KEY[0]), .w(LEDR[3]), //易疏忽：不再是KEY[3]了 .R(SW[2]), .E(KEY[1]), .L(KEY[2]), .Q(LEDR[2]) ); MUXDFF u3 ( .clk(KEY[0]), .w(LEDR[2]), .R(SW[1]), .E(KEY[1]), .L(KEY[2]), .Q(LEDR[1]) ); MUXDFF u4 ( .clk(KEY[0]), .w(LEDR[1]), .R(SW[0]), .E(KEY[1]), .L(KEY[2]), .Q(LEDR[0]) ); endmodule //引用自 Latches and Flip-Flops 的 Mux and DFF module MUXDFF ( input clk, input w, R, E, L, output Q ); wire mid1 = E ? w : Q; wire mid2 = L ? R : mid1; always @(posedge clk) begin Q <= mid2; end endmodule3-input LUT “移位寄存器设计” + “3 输入查找表（LUT）实现”8 位移位寄存器：输入 S 逐位移入，MSB（ Most Significant Bit） 先移入，enable 控制是否移位。8 个 D 触发器的输出标记为 Q[0] 到 Q[7]（移位顺序决定了 Q[0] 是最新移入的位，Q[7] 是最早移入的位）。S 连接到第一个触发器（Q[0]）的输入，每个触发器的输出连接到下一个触发器的输入。当 enable=1 时，每个时钟沿 clk 到来时：Q[0] 接收新输入 S；Q[1] 接收原来的 Q[0]；Q[2] 接收原来的 Q[1]；……Q[7] 接收原来的 Q[6]。 （效果：数据右移，Q[0] 是最新位，Q[7] 是最旧位，MSB 先存入 Q[7]。）3 输入查找表（LUT）：输入 A、B、C 作为地址（3 位，共 8 种组合：000~111）。输出 Z 根据地址选择移位寄存器的对应位：ABC=000 选 Q[0]，ABC=001 选 Q[1]，…，ABC=111 选 Q[7]。Solutionmodule top_module ( input clk, input enable, // 移位使能（高有效） input S, // 移位输入（MSB先移入） input A, B, C, // 3位地址输入 output Z ); reg [7:0] q; // 移位寄存器逻辑： always @(posedge clk) begin if (enable) begin q <= {q[6:0], S}; // 右移操作（左低位右高位，低位变到高位。但是目前索引中这样写是左高右低）：S→q[0]，q[0]→q[1]，…，q[6]→q[7] end end // 用A、B、C作为地址，直接用{A,B,C}制作索引 assign Z = q[{A, B, C}]; endmodule
Four-bit binary counterSolutionmodule top_module ( input clk, input reset, // Synchronous active-high reset output [3:0] q); always @(posedge clk) begin if(reset == 1) q <= 4'b0000; else if(q == 4'b1111) q <= 4'b0000; else q <= q + 1; end endmodule Decade counterSolutionmodule top_module ( input clk, input reset, // Synchronous active-high reset output [3:0] q); always @(posedge clk) begin if(reset == 1) q <= 4'b0000; else if(q == 4'b1001) q <= 4'b0000; else q <= q + 1; end endmodule Decade counter againSolutionmodule top_module ( input clk, input reset, output [3:0] q); always @(posedge clk) begin if(reset == 1) q <= 4'b0001; else if(q == 4'b1010) q <= 4'b0001; else q <= q + 1; end endmodule Slow decade counter暂停功能：仅当 slowena 为高电平时，计数器才会在时钟上升沿递增；否则保持当前值。 Solutionmodule top_module ( input clk, input slowena, input reset, output [3:0] q); always @(posedge clk) begin if(reset == 1) q <= 4'd0; else if(slowena) begin if(q == 4'd9) q <= 4'd0; else q = q + 1; end else q <= q; end endmodule Counter 1-12同步复位：reset 高电平时，时钟上升沿将计数器同步复位到 1。使能控制：enable 高电平时，计数器运行（计数）；enable 低电平时，计数器保持当前值（暂停）。**load 的核心作用:- **立即覆盖当前值**：当 `load=1` 时，无论计数器当前状态如何，都会在下一个时钟上升沿将 `d` 端口的值加载到计数器输出 `Q`。 - **优先级高于 `enable`**：即使 `enable=0`（计数器暂停），只要 `load=1`，计数器仍会加载 `d` 的值。 计数器的四种状态：loadenable计数器行为1X加载 d 的值（例如复位到 1 或回绕到 1）01正常计数（Q = Q + 1）00保持当前值（暂停）10仍加载 d 的值（load 优先级更高）已知：module count4( input clk, input enable, input load, input [3:0] d, output reg [3:0] Q ); Solutionmodule top_module ( input clk, input reset, input enable, output [3:0] Q, output c_enable, output c_load, output [3:0] c_d ); assign c_enable = enable; assign c_load = reset | (Q == 4'd12 && enable); assign c_d = c_load ? 4'b1 : 4'b0; count4 the_counter ( .clk(clk), .enable(c_enable), .load(c_load), .d(c_d), .Q(Q) ); endmoduleCounter 1000题中给出了一个十进制的模块，所以需要使用三次以达到把1000Hz降到1Hz. 已知：module bcdcount ( input clk, input reset, input enable, output reg [3:0] Q );Solutionmodule top_module ( input clk, input reset, output OneHertz, output [2:0] c_enable ); wire [3:0] one, ten, hundred; // 实例化三个 bcdcount 模块，分别表示个位、十位、百位 bcdcount counter0 (clk, reset, c_enable[0], one); // 个位 bcdcount counter1 (clk, reset, c_enable[1], ten); // 十位 bcdcount counter2 (clk, reset, c_enable[2], hundred); // 百位 // 使能信号控制逻辑 assign c_enable[0] = 1'b1; // 个位始终使能（持续计数） assign c_enable[1] = (one == 4'd9); // 十位仅在个位达到9时使能 assign c_enable[2] = (ten == 4'd9) && (one == 4'd9); // 百位仅在个位和十位都为9时使能 // 生成1Hz脉冲：当且仅当计数器达到999时，OneHertz输出高电平 assign OneHertz = (hundred == 4'd9) && (ten == 4'd9) && (one == 4'd9); endmodule4-digit decimal counter题目要求：构建一个 4 位 BCD（二进制编码十进制）计数器位分配：每个十进制位用 4 位二进制编码：q[3:0] → 个位（0~9），q[7:4] → 十位（0~9），q[11:8] → 百位（0~9），q[15:12] → 千位（0~9）（共 4 位，构成 0000~9999 的循环）。使能信号：对于 十位、百位、千位（即高位的三位），需输出对应的使能信号（如 ena[1] 对应十位、ena[2] 对应百位、ena[3] 对应千位），指示该高位何时应递增（例如：个位计满 9 时，触发十位的使能信号）。实现方式：可通过 实例化或修改 1 位十进制计数器（如 “十进制计数器模块”，实现 0~9 循环）来级联构建。信号解读：clk：时钟信号（上升沿触发计数）。reset：同步复位信号（高电平时，计数器复位到 0）。q[3:0]：个位的 BCD 值（如波形中依次为 7→8→9→0→1…，实现 0~9 循环）。ena[1]：十位的使能信号（仅当个位计满 9 时，ena[1] 置高，触发十位递增）。q[7:4]：十位的 BCD 值（如波形中 5→6→0…，受 ena[1] 控制，计满 9 后回 0）。Solutionmodule top_module ( input clk, input reset, // Synchronous active-high reset output [3:1] ena, output [15:0] q ); //实例化四个1位十进制计数器，分别表示个位、十位、百位、千位 count1bit u1 (clk,reset,enas,q[3:0]); count1bit u2 (clk,reset,ena[1],q[7:4]); count1bit u3 (clk,reset,ena[2],q[11:8]); count1bit u4 (clk,reset,ena[3],q[15:12]); //其中： wire enas; assign enas = 1'b1; //个位始终使能 // 生成十位的使能信号：当个位达到9时，ena[1]置高，触发十位计数 assign ena[1] = (q[3:0] == 4'b1001) ? 1'b1 : 1'b0; // 生成百位的使能信号：当十位和个位都达到9时，ena[2]置高，触发百位计数 assign ena[2] = ((q[7:4] == 4'b1001) && (q[3:0] == 4'b1001)) ? 1'b1 : 1'b0; assign ena[3] = ((q[11:8] == 4'b1001) && (q[7:4] == 4'b1001) && (q[3:0] == 4'b1001)) ? 1'b1 : 1'b0; endmodule //自主构建 1位十进制计数器 module count1bit ( input clk, input reset, input ena, // 使能信号：若高电平，则允许该计数器计数 output [3:0] q //4为BCD输出 ); always @(posedge clk) begin if(reset) q <= 4'b0000; else if(ena) begin if(q == 4'b1001) q <= 4'b0000; else q <= q + 1; end else q <= q; //使能无效时，保持当前值 end endmodule12-hour clockSolutionmodule top_module( input clk, input reset, input ena, output pm, output [7:0] hh, output [7:0] mm, output [7:0] ss); //------------------- 秒计数器（00-59） --------------------- always @(posedge clk) begin if (reset) ss <= 8'd0; else if (ena) begin if (ss[3:0] == 4'd9) begin //低位到9 ss[3:0] <= 4'd0; //清零低位 if (ss[7:4] == 4'd5) //低位到9并且高位到5 ss[7:4] <= 4'd0; //清零高位 else ss[7:4] <= ss[7:4] + 4'd1; end else ss[3:0] <= ss[3:0] + 4'd1; end end //------------------- 分计数器（00-59） --------------------- always @(posedge clk) begin if (reset) mm <= 8'd0; else if (ena && ss[7:4] == 4'd5 && ss[3:0] == 4'd9) begin //ena有效并且秒数为59时触发使能 if (mm[3:0] == 4'd9)begin //低位到9 mm[3:0] <= 4'd0; //清零低位 if (mm[7:4] == 4'd5) //低位到9并且高位到5 mm[7:4] <= 4'd0; //清零高位 else mm[7:4] <= mm[7:4] + 4'd1; end else mm[3:0] <= mm[3:0] + 4'd1; end end //------------------- 时计数器（01-12） --------------------- always @(posedge clk) begin if (reset) begin hh[7:4] <= 4'd1; //若直接写hh <= 8'd12; 是错误的，因为这是BCD码。 hh[3:0] <= 4'd2; pm <= 1'd0; end else if (ena && mm[7:4] == 4'd5 && mm[3:0] == 4'd9 && ss[7:4] == 4'd5 && ss[3:0] == 4'd9) begin //ena有效，并且秒数和分钟都为59时，触发使能 //低位到9并且高位为0 (09:59:59->10:00:00) if (hh[3:0] == 4'd9 && hh[7:4] == 4'd0) begin hh[3:0] <= 4'd0; hh[7:4] <= 4'd1; end //低位到2并且高位到1 (12:59:59->01:00:00) ，回绕回01 else if (hh[3:0] == 4'd2 && hh[7:4] == 4'd1) begin hh[7:4] <= 4'd0; hh[3:0] <= 4'd1; end //低位到1并且高位到1 (11:59:59->12:00:00) ，pm需要转换 else if (hh[3:0] == 4'd1 && hh[7:4] == 4'd1) begin hh[7:4] <= 4'd1; hh[3:0] <= 4'd2; pm <= ~pm; end else hh[3:0] <= hh[3:0] + 4'd1; end end endmodule一些小问题： 「在 Verilog 中，在always块中赋值的信号必须声明为reg型。你的代码中，pm、hh、mm、ss均在always块中被赋值，但声明时使用了默认的wire型（output pm、output [7:0] hh等），这会导致编译错误（“非法在 always 块中赋值 wire 型信号”）。」但是，在本问题中，直接赋值依然可以Success，这是因为本网站对语法检查不够严格.