二、操作系统
2.1、操作系统概述
2.1.1、操作系统分层结构图
用户层
应用层
支撑软件层
系统软件层
核心层
使用
管理
支持
计算机硬件 / 裸机
操作系统
语言处理程序
应用程序
人
2.1.2、核心定义
操作系统(OS)是计算机系统中最基本的系统软件。
它是控制 和管理计算机软硬件资源的核心程序。
2.1.3、三大关键接口
1)人机之间的接口 :用户通过OS使用计算机。接口方式包括 命令行 (如Linux Bash)、图形界面 (GUI)和 系统调用(System Call,程序员使用的接口)。
2)应用软件与硬件之间的接口:应用软件不需要直接操作硬件(如直接读写硬盘扇区),而是通过OS提供的API进行操作,实现了硬件无关性。
3)资源的管理者:管理对象包括:CPU(进程)、内存(存储)、外设(设备)、数据(文件)。
2.1.4、五大管理职能
2.1.4.1、进程管理
核心任务:管理处理器(CPU)。
关键考点 :进程的状态转换、PV操作 (同步与互斥)、死锁问题、线程概念。这是计算题最多的地方。
2.1.4.2、存储管理
核心任务:管理主存储器(内存)。
关键考点 :页式存储(逻辑地址转物理地址)、段式存储、页面置换算法(LRU/FIFO)。
2.1.4.3、设备管理
核心任务:管理辅助存储器及输入输出设备。
关键考点 :I/O控制方式(DMA)、Spooling技术(假脱机)、磁盘调度算法(移臂调度)。
2.1.4.4、文件管理
核心任务:管理信息/数据资源。
关键考点 :索引文件结构(直接/一级/二级索引)、位示图(空闲块管理)。
2.1.4.5、作业管理
核心任务:用户提交任务的管理。
关键考点:作业调度算法(先来先服务、短作业优先等),通常与进程调度结合考察。
2.2、特殊的操作系统
2.2.1、分类图谱
特定平台
网络与分布
基本运行机制
操作系统分类
批处理 OS
分时 OS
实时 OS
网络 OS
分布式 OS
微机 OS
嵌入式 OS
多道: 宏观并行/微观串行
提高资源利用率
时间片轮转
交互性/独占性
高可靠性
规定时间内响应
资源共享
透明性
统一管理
Windows/Linux
多任务/多线程
微型化/可定制
HAL与BSP支持
2.2.2、三大基本操作系统类型
2.2.2.1、批处理操作系统
单道批处理:一次一个作业,内存中始终只保持一道作业。
多道批处理 :一次将多个作业调入内存,让它们竞争CPU资源。多道批处理的主要目的是提高CPU和外部设备的利用率 以及增加系统吞吐量,但不具备交互能力(用户一旦提交作业就无法干预)。
多道批处理特点:
1)多道:内存中同时存放多个作业。
2)宏观上并行:多个作业都在运行中。
3)微观上串行:单核CPU在每一瞬间只能执行一个作业。
2.2.2.2、分时操作系统
工作方式 :采用时间片轮转 (Round Robin) 的方式为多个用户服务。
四大特点(必背口诀:多独交及):
1)多路性:同时有多个用户通过终端连接。
2)独立性 :每个用户感觉像是独占系统,互不干扰。
3)交互性:用户可以与系统进行人机对话。
4)及时性:系统能在极短时间内响应用户请求。
2.2.2.3、实时操作系统
分类:实时控制系统(如飞行控制)、实时信息系统(如订票系统)。
核心要求:
1)高可靠性:系统绝对不能崩溃。
2)时限性 :必须在规定时间内响应并处理。
实时系统的交互能力不如分时系统强。
实时系统的设计目标是"在严格的时间限制内完成任务",而分时系统的目标是"公平地分配CPU"。
2.2.3、网络、分布式与特定平台操作系统
2.2.3.1、网络操作系统
核心功能 :方便有效地共享网络资源(硬件、软件、数据)。
典型代表:Unix, Linux, Windows Server。
局限性:用户必须知道资源在哪台机器上(不透明)。
2.2.3.2、分布式操作系统
定义:是网络操作系统的更高级形式。
核心特性:
1)透明性 (Transparency):这是与网络OS最大的区别。用户不知道也不需要知道资源在网络中的具体位置,整个系统看起来像是一台巨大的、统一的计算机。
2)可靠性:某台机器宕机,任务会自动转移到其他机器,不影响整体服务。
3)高性能:并行处理能力。
2.2.3.3、微机操作系统
Windows:单用户多任务(早期),现代版本为多用户多任务。特点是图形界面(GUI)。
Linux :类Unix系统,特点是源代码公开 、多用户 、多任务 、多线程 、多CPU支持。
2.2.3.4、嵌入式操作系统
运行环境:智能芯片(如ARM, MIPS)。
核心特点:
1)微型化:内核小,节省存储。
2)可定制:针对不同的硬件变化进行配置(剪裁)。
3)高可靠性 & 实时性。
4)易移植性 : HAL (硬件抽象层) 和 BSP (板级支持包)。
BSP (Board Support Package):介于硬件和操作系统内核之间的一层,它的作用类似于PC机上的BIOS,主要负责硬件初始化。
EOS 初始化过程通常是:片级初始化 -> 板级初始化 -> 系统级初始化。
2.3、进程
2.3.1、进程结构图
进程的组成
PCB 的核心内容
进程标识符
状态
位置信息
控制信息
队列指针
优先级
现场保护区
进程 Process
程序块
数据块
进程控制块 PCB
2.3.2、进程的定义
1)动态性:进程是程序在"一个数据集合上运行的过程"。
程序(Program)是静态的代码文件,存放在硬盘上;进程(Process)是动态的执行实例,存放在内存中。
2)独立性 :进程是系统进行资源分配 和调度的一个独立单位。
这是传统操作系统的定义。在现代操作系统中,资源分配 的单位是进程,而调度 (CPU执行)的基本单位通常是线程。但在软考的基础题中,如果未提及线程,默认沿用此定义。
2.3.3、进程的组成
2.3.3.1、程序块
描述进程要完成的功能,是进程执行的代码实体。
特点:多个进程可以共享同一个程序块(例如多个用户同时使用同一个文本编辑器)。
2.3.3.2、数据块
存放进程执行过程中所需的变量、工作区和堆栈。
2.3.3.3、进程控制块 (PCB)
地位:明确指出 PCB 是**"进程存在的唯一标志"**。系统通过 PCB 来感知和管理进程。PCB 创建,进程就创建;PCB 撤销,进程就消亡。
包含信息:
1)标识符:唯一标记一个进程(PID)。
2)状态:记录当前是运行、就绪还是阻塞。
3)现场保护区:当进程被切换走(中断)时,CPU 的寄存器内容(断点、上下文)就保存在这里,以便下次回来能接着算。
4)队列指针:用于将处于相同状态(如都在排队)的进程连成链表。
2.3.4、进程与线程结构图
进程: 资源分配单位
线程2: 调度单位
程序计数器
寄存器
栈 Stack
所有线程共享的资源
内存地址空间
代码 Code
数据 Data
文件 Files
线程1: 调度单位
程序计数器
寄存器
栈 Stack
2.3.5、进程与线程的定义演变
1)程序 -> 进程 -> 线程
这是计算机执行模型的进化路线。
引入进程:为了实现多道程序并发执行(解决CPU利用率问题)。
引入线程 :为了减少 进程并发执行时的时空开销(创建、撤销、切换),进一步提高并发粒度。
2)两大基本属性的分离
在引入线程之前,进程既是"资源分配单位"也是"调度执行单位"。
引入线程后,这两个职能分家了:
进程 (Process) :是拥有资源的独立单位。
线程 (Thread) :是独立调度(CPU执行)的基本单位。
2.3.6、线程间的"共享"与"独占"
1)线程私有(独占)资源
为什么需要私有?因为每个线程要独立执行指令,必须记录自己执行到哪了,计算结果暂存在哪。
程序计数器 (PC):记录下一条要执行的指令地址。
寄存器 (Registers):保存当前的中间计算结果。
栈 (Stack):保存函数调用链和局部变量。
2)线程共享资源
因为线程都属于同一个进程,所以它们共享进程的大部分"家产"。
内存地址空间:所有线程看到的内存视图是一样的。
代码 (Code):执行同一套逻辑(或不同函数)。
数据 (Data):全局变量是共享的(这也是多线程需要同步锁的原因)。
文件 (Files):进程打开的文件,所有线程都能读写。
2.3.7、用户级线程 vs 内核级线程
内核空间 Kernel Space
用户空间 User Space
进程/线程表
线程库 Runtime
看似是一个单线程进程
只能在一个核上跑
独立调度
独立调度
进程 A - 使用用户级线程
用户线程1
用户线程2
用户线程3
用户线程表
进程 B - 使用内核级线程
线程1存根
线程2存根
OS 调度器
进程A PCB
内核线程 TCB1
内核线程 TCB2
CPU Core 1
CPU Core 2
2.3.7.1、用户级线程
谁来管理 :由应用程序内部的线程库 (Run-time System)管理。操作系统内核根本不知道这些线程的存在。
内核视角:内核只看到一个单独的进程(图中的进程 A)。
切换快 :线程切换只需要在用户态通过简单的指针调整即可,不需要陷入内核态(Mode Switch),开销极小。
跨平台:线程库可以运行在任何操作系统上,不依赖底层OS。
调度灵活:应用程序可以根据自己的需求写调度算法(比如数据库系统常偏爱这种)。
一堵全堵:如果其中一个用户线程发起系统调用(如读文件)被阻塞,内核会认为整个进程被阻塞,导致该进程内其他本来能运行的线程也全部暂停。
无法利用多核 :因为内核只把它当成一个进程,所以一次只能分配给一个CPU核心。哪怕你有100个用户线程,也只能挤在一个核上跑(多对一模型)。
2.3.7.2、内核级线程
谁来管理 :由操作系统内核直接管理。内核里有专门的线程控制块(TCB)列表。
内核视角:内核明确知道进程B里有2个线程。
真正的并发 :内核可以将进程B的不同线程分配给不同的CPU核心同时执行(一对一模型)。
不连坐:如果线程1阻塞了(比如等IO),线程2可以继续在另一个核上跑,不受影响。
切换慢:线程切换需要从用户态切换到内核态(System Call),开销比较大。
2.3.7.3、三种映射模型
| 模型 | 描述 | 典型代表 | 架构评价 |
|---|---|---|---|
| 多对一 (M:1) | 多个用户线程映射到1个内核线程 | 早期的 Java (Green Threads), 传统的 Unix | 优点 :切换极快。 缺点:无法利用多核,一堵全堵。 |
| 一对一 (1:1) | 1个用户线程对应1个内核线程 | Windows , Linux (NPTL), 现代 Java (HotSpot) | 优点 :真并行。 缺点:创建线程开销大,线程数有限制。 |
| 多对多 (M:N) | M个用户线程动态映射到N个内核线程 | Go 语言 (Goroutines), Erlang | 优点 :集大成者 。既有ULT的轻量级切换,又有KLT的多核并发能力。 难点:实现极其复杂。 |
2.3.7.4、对比
| 特性 | 用户级线程 (User-Level Thread) | 内核级线程 (Kernel-Level Thread) |
|---|---|---|
| 管理者 | 应用程序(线程库) | 操作系统内核 |
| 可见性 | 内核不知道它的存在(以为还是一个进程) | 内核全权管理 |
| 切换开销 | 极小 (不需要切入内核态) | 较大 (需要切入内核态) |
| 并发性 | 一个线程阻塞,整个进程都会被阻塞 | 一个线程阻塞,其他线程继续跑 |
| CPU利用 | 只能利用一个CPU核心 | 可以利用多核CPU并行执行 |
2.3.8、进程的三态模型
2.3.8.1、进程三态转换模型
进程调度
时间片到
等待某个事件发生
如请求I/O
等待的事件已发生
如I/O完成
X 不可直接转换
X 不可直接转换
运行态
就绪态
阻塞态/等待态
2.3.8.2、运行态
定义:进程正在 CPU 上执行。
数量限制 :在单处理机(Single CPU)系统中,同一时刻只能有 1 个进程处于运行态。
多核情况:如果是多处理机,则可以有多个进程同时处于运行态。
2.3.8.3、就绪态
定义:万事俱备,只欠东风(CPU)。
资源情况:进程已经获得了除 CPU 以外的所有所需资源(如内存、文件句柄等),只要调度程序把 CPU 分配给它,它就能立刻运行。
排队:通常系统会将这些进程排成一个"就绪队列"。
2.3.8.4、阻塞态
定义:进程正在等待某个事件发生(如等待用户输入、等待磁盘读取完成、等待打印机空闲)。
资源情况:此时即使给它 CPU,它也无法运行,因为它缺的是数据或外设响应,而不是计算能力。
2.3.8.5、运行 →\to→ 就绪
原因 :时间片到。
在分时系统中,为了公平,每个进程每次只能运行一小段时间(时间片)。用完了,OS 就会强行把它暂停,放回就绪队列排队,换别人上。
2.3.8.6、运行 →\to→ 阻塞
原因 :等待某个事件发生(主动请求)。
进程运行过程中,代码执行到了 scanf(读键盘)或 read(读文件)等指令,或者执行了 P操作(申请被锁住的资源)。进程主动放弃 CPU,进入睡眠。
2.3.8.7、阻塞 →\to→ 就绪
原因 :等待的事件发生(被动唤醒)。
硬盘数据读完了、用户按下回车了、或者 V操作 释放了资源。硬件会发中断通知 OS,OS 把该进程从阻塞队列移到就绪队列。注意:醒来后不能直接运行,必须先去就绪队列排队!
2.3.8.8、就绪 →\to→ 运行
原因 :调度。
CPU 空闲了,调度程序(Scheduler)从就绪队列中选中了这个进程,把 CPU 分配给它。
2.3.8.9、不可逆转换
阻塞 ≠\neq= 运行:阻塞结束必须先回就绪态排队,绝对不能直接抢 CPU。
就绪 ≠\neq= 阻塞:就绪进程没有执行代码,不可能发出 I/O 请求,所以不会直接变阻塞。只有正在运行的进程才能发出阻塞请求。
2.3.9、调度算法
2.3.9.1、分类
调度方式
非抢占式
抢占式
具体算法
先来先服务
短作业优先
时间片轮转
高响应比优先
优先级调度
进程调度算法
2.3.9.2、先来先服务
原理:谁先到就服务谁,像超市排队结账。
非抢占式:一旦获得CPU,就一直运行直到结束或阻塞。
缺点:存在**"护航效应" (Convoy Effect)**。如果一个长作业先到,后面一堆短作业都要等很久,导致平均等待时间很长。
适用:作业调度(现在很少单独用于进程调度)。
2.3.9.3、短作业优先
原理:谁运行时间短,谁先上。
优点 :能获得最短的平均等待时间 和平均周转时间(这是数学上证明过的最优解)。
缺点 :"饿死" (Starvation) 现象。如果一直有短作业源源不断地来,长作业可能永远得不到执行。
类型:有"非抢占式SJF"和"抢占式SJF"(也叫最短剩余时间优先 SRTN)。
2.3.9.4、时间片轮转
原理 :每个进程轮流执行一个时间片(如10ms)。用完就去队尾排队。
这是分时操作系统的标配。
公平性:所有进程都能得到响应。
时间片太大 →\to→ 退化为 FCFS。
时间片太小 →\to→ 频繁切换上下文(Context Switch),开销太大,CPU都在忙着切进程,没空干活。
2.3.9.5、高响应比优先
原理:它是 FCFS 和 SJF 的折中方案,既考虑等待时间,也考虑执行时间。
公式(必背计算公式):
响应比Rp=等待时间+要求服务时间要求服务时间=1+等待时间要求服务时间响应比 R_p = \frac{等待时间 + 要求服务时间}{要求服务时间} = 1 + \frac{等待时间}{要求服务时间}响应比Rp=要求服务时间等待时间+要求服务时间=1+要求服务时间等待时间
分析:
作业刚到时,等待时间=0,响应比=1。
随着等待时间增加,响应比越来越高,最终长作业也能获得CPU,避免了"饿死"现象。
2.3.9.6、优先级调度
原理:根据优先级高低来分配CPU。
静态优先级:创建进程时确定,不再改变。
动态优先级:随运行情况变化(例如等待越久,优先级越高)。
问题:低优先级进程可能"饿死"。
2.3.9.7、抢占式 & 非抢占式
非抢占式:除非进程自己放弃(结束或阻塞),否则谁也抢不走它的CPU。
抢占式:
1)如果有更高优先级 的进程到了,或者时间片用完了,系统会强行剥夺当前进程的CPU,分配给新进程。
2)实时系统 和现代分时系统(Windows/Linux)都必须是抢占式的。
2.3.10、进程的标准五态
2.3.10.1、标准五态模型
基础三态
允许/提交
调度
时间片到
等待事件
事件发生
执行结束/异常
新建态
终止态
就绪态
运行态
阻塞态
适用场景:考题问"进程是如何创建的"或"进程结束后资源如何回收"时。
2.3.10.2、新建态
进程正在被创建。系统为其分配了 PCB,但还没有把它放入内存的就绪队列中(可能因为内存满了,或者为了控制系统并发度)。
关键点 :此时进程不在就绪队列中。
2.3.10.3、终止态
进程运行结束或出现错误被系统杀掉。
关键点:系统保留其 PCB 一段时间(为了审计或统计),但进程已经不再执行指令,资源正在被回收。
2.3.11、进程的七态模型
2.3.11.1、七态模型
外存中 (静止/挂起)
内存中 (活动)
内存充足
调度
时间片到
等待事件
事件发生
结束
挂起
挂起
激活
激活
事件发生
内存不足
新建态
终止态
运行态
活动就绪
活动阻塞
静止就绪
静止阻塞
2.3.11.2、基础概念
这是操作系统中最完整的模型。它在"标准五态"的基础上,引入了**挂起(Suspend)**机制。
就绪态 →\to→ 分裂为 活动就绪 (内存) 和 静止就绪(磁盘)。
阻塞态 →\to→ 分裂为 活动阻塞 (内存) 和 静止阻塞(磁盘)。
总数计算 :新建 + 终止 + (活动就绪 + 静止就绪) + 运行 + (活动阻塞 + 静止阻塞) = 7态。
2.3.11.3、新建 →\to→ 静止就绪
系统想创建进程,但发现内存极其紧张,干脆直接把新进程创建在磁盘的交换区里,排队等着进内存。
2.3.11.4、静止阻塞 →\to→ 静止就绪
进程在磁盘里被挂起(比如因为等 I/O),此时 I/O 完成了。它不会直接飞回内存(变成活动就绪),而是变为"静止就绪"(依然在磁盘里,但状态变了,只要一进内存就能跑)。
2.3.12、同步与互斥
2.3.12.1、逻辑图
同步 (Synchronization)
定义: 速度有差异, 需要配合
类型: 直接制约关系
形象比喻: 张三李四配合工作
目标: 有序执行, 前仆后继
互斥 (Mutual Exclusion)
定义: 争夺临界资源
类型: 间接制约关系
形象比喻: 千军万马过独木桥
目标: 只能一个一个过
进程间的制约关系
临界资源: 一次只允许一个进程使用的资源
临界区: 访问临界资源的那段代码
2.3.12.2、基础概念
1)临界资源
定义 :系统中一次只允许一个进程访问的资源。
例子:物理设备(如打印机、磁带机)、共享变量(如全局变量 count)。
生活类比:公用电话亭、独木桥。
2)临界区
定义 :进程中访问临界资源的那段代码。
注意 :临界资源是硬件或数据,临界区是代码。我们通过控制代码的执行(加锁),来保护资源。
2.3.12.3、互斥
性质 :间接制约关系。
为什么叫间接? 因为进程 A 和 进程 B 可能根本互不相识,它们只是因为都要抢同一个资源(打印机),才被迫产生了排队关系。
解决原则:空闲让进、忙则等待、有限等待、让权等待。
2.3.12.4、同步
性质 :直接制约关系。
为什么叫直接? 因为进程 A 和 进程 B 是为了完成同一个大任务而配合的,它们之间有直接的消息传递或依赖关系(源于程序逻辑本身)。
假设任务是:张三把东西送到 A 点,李四才能从 A 点把东西运到 B 点。
即使李四骑车很快,他也必须停下来等 张三先走到 A 点。这就是速度有差异,在一定情况停下等待。
同步的核心是顺序。必须先做完甲,才能做乙。
2.3.12.5、经典场景
| 场景 | 类型 | 关键词 | 判别技巧 |
|---|---|---|---|
| 打印机 | 互斥 | 抢占、独占 | 资源只有一份,谁抢到谁用 |
| 数据库写锁 | 互斥 | 写操作 | 防止数据写乱 |
| 生产者-消费者 | 同步 + 互斥 | 缓冲区空/满 | 缓冲区满了生产者必须等消费者取走 |
| 流水线作业 | 同步 | 前后工序 | 上一道工序做完,下一道才能开始 |
| 公共汽车 | 同步 | 司机/售票员 | 关门后才能开车(同步),到站后才能开门(同步) |
2.3.13、信号量和PV操作
2.3.13.1、基础概念
1)原语:这意味着 PV 操作是不可中断的,要么做完,要么不做,执行过程中不允许切换进程。
2)信号量:它是一个特殊的全局变量,用于表示资源的数量。
3)信号量的物理含义(必背):
① S>0S > 0S>0:表示系统中当前可用的资源数量。
② S=0S = 0S=0:表示资源刚好分完,且没有进程在排队。
③ S<0S < 0S<0:表示资源不够用了。SSS 的绝对值 (∣S∣|S|∣S∣)表示正在等待该资源的进程数(即排队人数)。
2.3.13.2、PV操作逻辑流程图
V操作: 释放/解锁资源
Yes (有人在排队)
No (无人排队)
开始
S = S + 1
S <= 0 ?
从等待队列
唤醒一个进程
继续执行
P操作: 申请/锁定资源
Yes (资源不足)
No (资源足够)
开始
S = S - 1
S < 0 ?
自我阻塞
放入等待队列
继续执行
2.3.13.3、P操作
口诀 :"先减后判,负则阻"。
1)S = S - 1:不管有没有,先预订一个。
2)如果减完后 S<0S < 0S<0:说明刚才那是透支的,没资源了,自己乖乖去阻塞(排队)。
3)如果减完后 S≥0S \ge 0S≥0:说明还有存货,拿走资源,继续执行。
2.3.13.4、V操作
口诀 :"先加后判,不大于0则唤"。
1)S = S + 1:我用完了,把资源还回去。
2)如果加完后 S≤0S \le 0S≤0:说明 SSS 之前是负数(比如 -1, -2),意味着还有兄弟在排队等待。所以我归还的这个资源不能闲着,必须立刻唤醒队列里的一个进程。
3)如果加完后 S>0S > 0S>0:说明 SSS 之前是 ≥0\ge 0≥0 的,没人排队,资源放回库房就行。
2.3.13.5、深度思考
为什么 V 操作中判断条件是 S≤0S \le 0S≤0 而不是 S<0S < 0S<0?
1)假设 S=−1S = -1S=−1(表示有 1 个人在排队)。
2)现在我执行 V 操作:S=−1+1=0S = -1 + 1 = 0S=−1+1=0。
3)此时 S=0S=0S=0。虽然现在不欠资源了,但刚才那个 S=−1S=-1S=−1 的状态说明确实有人在等。所以我必须去唤醒那个人。
4)所以,只要 SSS 加完之后还是非正数 (≤0\le 0≤0),就说明刚才肯定是负数,就必须唤醒。
2.3.13.6、例题
有 2台 打印机,3个 进程要互斥使用打印机。 问:信号量 SSS 的初值是多少?取值范围是多少?
1)确定初值:信号量初值 = 资源的初始数量。
因为有 2 台打印机,所以 Sinitial=2S_{initial} = 2Sinitial=2。
2)阶段 1(资源充足) :进程 A 申请:S=2−1=1S = 2 - 1 = 1S=2−1=1 (拿到)。进程 B 申请:S=1−1=0S = 1 - 1 = 0S=1−1=0 (拿到,此时资源刚好空)。
3)阶段 2(资源耗尽,开始排队) :进程 C 申请:S=0−1=−1S = 0 - 1 = -1S=0−1=−1 (阻塞,此时有 1 个进程在排队)。
4)确定范围:
最大值:2(就是初值,谁都没用的时候)。
最小值 :当所有进程都来申请时。资源总量 - 进程总数 = 2−3=−12 - 3 = -12−3=−1。此时表示:2 个进程在运行(拿到了),1 个进程在排队(S=-1)。
结论 :取值范围是 [−1,2][-1, 2][−1,2]。
2.3.14、前趋图
2.3.14.1、前趋图结构
并行执行: 起始进程
信号量 S1
信号量 S2
信号量 S3
信号量 S4
A: 绞肉
B: 切葱末
C: 切姜末
D: 搅拌
E: 包饺子
2.3.14.2、定义与组成
定义 :前趋图是一个有向无环图 (DAG),用于描述进程之间执行的先后顺序。
结点 (Nodes):表示一个进程或一个程序段(如 A、B、C...)。
有向边 (Arrows) :表示前趋关系(Precedence Relation)。如果存在 A→DA \to DA→D,则表示 A 必须在 D 开始之前完成 。这种关系对应的就是 同步 (Synchronization) 关系。
2.3.14.3、关键术语
直接前趋:如 A 是 D 的直接前趋。
直接后继:如 D 是 A 的直接后继。
起始进程 (Initial Process) :没有前趋 (没有箭头指向它)的结点。如A、B、C。它们可以同时并发执行(因为谁也不用等)。
终止进程 (Final Process) :没有后继(没有箭头指出去)的结点。如E。它是最终的任务目标。
2.3.14.4、实例分析
并行性:绞肉(A)、切葱(B)、切姜© 互不干扰,可以三个人同时干(并发)。
同步性 :搅拌(D) 必须等 A、B、C 全部 做完才能开始。缺一样都没法搅拌。
顺序性:包饺子(E) 必须等搅拌(D) 做完。
2.3.15、PV操作与前趋图映射
2.3.15.1、原理
Sa (信号量)
Sb (信号量)
Sc (信号量)
Sd (信号量)
进程 A: 绞肉
进程 B: 切葱末
进程 C: 切姜末
进程 D: 搅拌
进程 E: 包饺子
2.3.15.2、信号量定义的铁律
1)数量原则:图中有几根箭头,就必须定义几个信号量。
2)初值原则 :用于**前趋关系(同步)**的信号量,初值一般为 0。
深度理解:为什么是 0?因为同步是"接力赛"。比赛刚开始时,第一棒还没跑完,接力棒还没交出来,所以第二棒能拿到的接力棒数量是 0,必须等。
2.3.15.3、代码填充法则
1)法则一:有后继 →\to→ V操作
如果一个进程屁股后面有箭头指出去,它做完事后必须执行 V(信号量),通知下家。
2)法则二:有前趋 →\to→ P操作
如果一个进程头顶上有箭头指进来,它干活前必须执行 P(信号量),检查上家做完没。
2.3.15.4、易错点
信号量初值区分:
1)互斥(抢厕所):初值 = 1(有一把钥匙)。
2)同步(前趋图/接力赛):初值 = 0(初始没接力棒)。
2.3.16、死锁
2.3.16.1、死锁处理策略逻辑图
处理策略
产生的四个必要条件
X 很难破坏
√ 可以打破
√ 可以打破
√ 可以打破
打破其中一个条件
银行家算法
死锁 Deadlock
互斥条件
Mutual Exclusion
保持和等待
Hold and Wait
不剥夺
No Preemption
环路等待
Circular Wait
预防死锁
Prevention
避免死锁
Avoidance
通常保留
一次性申请所有资源
申请不到就释放已有的
资源有序分配法
2.3.16.2、死锁的四大条件与预防
1)互斥:资源只能被一个进程使用。
这个条件通常无法破坏(打叉),因为打印机等设备的物理特性决定了它必须互斥使用。
2)保持和等待:占着碗里的(保持),看着锅里的(等待)。
策略 :要求进程一次性申请所有资源,否则一个也不给。
3)不剥夺:别人手里的资源,不能强行抢过来。
策略 :如果申请不到新的,就把手里现有的释放(剥夺自己)。
4)环路等待:A等B,B等C,C等A。
策略 :给资源编号,按顺序申请(有序资源分配法)。
2.3.16.3、最少资源数计算
场景 :有 nnn 个进程,每个进程都需要 kkk 个资源。问系统至少要有几个资源才不可能发生死锁?
极端倒霉情况 :每个进程都拿到了 k−1k-1k−1 个资源(只差 1 个就圆满了),此时系统刚好没资源了 →\to→ 死锁。
破解公式: 系统资源数≥进程数×(每个进程所需资源数−1)+1系统资源数 \ge 进程数 \times (每个进程所需资源数 - 1) + 1系统资源数≥进程数×(每个进程所需资源数−1)+1
例子:
1)3 个进程,都需 5 个资源。
2)阈值 = 3×(5−1)+1=12+1=133 \times (5-1) + 1 = 12 + 1 = 133×(5−1)+1=12+1=13。
3)只要资源数 ≥13\ge 13≥13,就绝对不会死锁。
2.3.17、银行家算法
2.3.17.1、核心思想
在分配资源之前,先算一算,借出去这笔钱后,系统会不会破产(进入不安全状态)。如果会破产,就不借(推迟分配)。
2.3.17.2、三大关键矩阵
1)Max (最大需求):进程总共要多少。
2)Allocation (已分配):进程现在手里有多少。
3)Need (还需) :这是解题的关键,通常不直接给,要自己算! Need=Max−AllocationNeed = Max - AllocationNeed=Max−Allocation
2.3.17.3、解题五步法
1)计算当前剩余资源
公式 :Available=系统总资源−∑(所有进程的已分配资源)Available = 系统总资源 - \sum(所有进程的已分配资源)Available=系统总资源−∑(所有进程的已分配资源)
2)计算需求矩阵
用 Max 减去 Allocation。
算出所有进程的 Need。
3)试探分配
拿着手里的闲钱去比对谁的 Need 能够被满足。
4)运行与回收
运行结束后,它会把它手里所有的资源(Allocation)都还给系统。
注意:不是加 Need,也不是加 Max,是加它占用的资源!
5)循环判断
循环类推,直到所有进程都运行完,就是一个安全序列。
2.3.17.4、样例题
1)题目描述
假设系统中由三类互斥资源 R1、R2、R3,可用资源总量分别是 9、8、5。
在 T0 时刻,系统中有 P1、P2、P3、P4 和 P5 五个进程,这些进程对资源的 最大需求量 (Max) 和 已分配资源数 (Allocation) 如下表所示。
| 进程 | R1 (Max) | R2 (Max) | R3 (Max) | R1 (Alloc) | R2 (Alloc) | R3 (Alloc) |
|---|---|---|---|---|---|---|
| P1 | 6 | 5 | 2 | 1 | 2 | 1 |
| P2 | 2 | 2 | 1 | 2 | 1 | 1 |
| P3 | 8 | 1 | 1 | 2 | 1 | 0 |
| P4 | 1 | 2 | 1 | 1 | 2 | 0 |
| P5 | 3 | 4 | 4 | 1 | 1 | 3 |
问题:如果进程按 ______ 序列执行,那么系统状态是安全的?
A : P1→P2→P4→P5→P3P1 \to P2 \to P4 \to P5 \to P3P1→P2→P4→P5→P3
B : P2→P4→P5→P1→P3P2 \to P4 \to P5 \to P1 \to P3P2→P4→P5→P1→P3
C : P2→P1→P4→P5→P3P2 \to P1 \to P4 \to P5 \to P3P2→P1→P4→P5→P3
D : P4→P2→P5→P1→P3P4 \to P2 \to P5 \to P1 \to P3P4→P2→P5→P1→P3
2)解题思路
在做任何判断前,必须先计算两个隐藏数据:
① Need 矩阵 (还缺多少):Need=Max−AllocationNeed = Max - AllocationNeed=Max−Allocation
② Initial Available (系统此时手里还剩多少闲钱):Available=总资源−∑已分配Available = 总资源 - \sum 已分配Available=总资源−∑已分配
准备工作:计算 Need 和 初始可用
R1 已分配总和 :1+2+2+1+1=71+2+2+1+1 = 71+2+2+1+1=7 →\to→ R1 剩 9−7=29-7=29−7=2
R2 已分配总和 :2+1+1+2+1=72+1+1+2+1 = 72+1+1+2+1=7 →\to→ R2 剩 8−7=18-7=18−7=1
R3 已分配总和 :1+1+0+0+3=51+1+0+0+3 = 51+1+0+0+3=5 →\to→ R3 剩 5−5=05-5=05−5=0
初始 Available = (2, 1, 0)
计算流程
P2满足(0,1,0 <= 2,1,0)
P4不满足(0,0,1 > 2,1,0)
P4满足
P1不满足(5>4)
P5运行
初始可用资源 Available: 2, 1, 0
寻找 Need <= 2,1,0
P2
Need: 0,1,0
Alloc: 2,1,1
P4
Need: 0,0,1
Alloc: 1,2,0
P1
Need: 5,3,1
P4暂不可运行
缺R3资源
观察选项:
A以P1开头(Need 5,3,1) -> 排除
D以P4开头(Need 0,0,1) -> 排除
锁定 B 或 C
回收 P2 资源: +2, 1, 1
当前可用: 4, 2, 1
寻找 Need <= 4,2,1
P4
Need: 0,0,1
P1
Need: 5,3,1
P1暂不可运行
观察剩余选项:
B是 P2->P4... (符合)
C是 P2->P1... (P1此时跑不了)
所以选 B
回收 P4 资源: +1, 2, 0
当前可用: 5, 4, 1
验证 B 序列后续: P5->P1->P3
P5
Need: 2,3,1 <= 5,4,1
回收 P5: +1, 1, 3
当前可用: 6, 5, 4
P1
Need: 5,3,1 <= 6,5,4
回收 P1: +1, 2, 1
当前可用: 7, 7, 5
P3
Need: 6,0,1 <= 7,7,5
安全序列 B 验证通过
2.4、存储
2.4.1、页式存储
2.4.1.1、核心概念
基本思想 :为了不浪费内存空间,我们把用户的程序(逻辑空间)和内存(物理空间)都切成一样大小的块。
在程序里,这个块叫 "页" (Page)。
在内存里,这个块叫 "页帧" (Page Frame) 或 "块"。
页表 (Page Table) :这是核心。它是一张映射表,记录了"程序里的第几页"放在了"内存里的第几号房间"。
2.4.1.2、地址转换
逻辑地址结构 :页号 + 页内地址(偏移量)
物理地址结构 :页帧号 + 页内地址(偏移量)
1)切分:根据页面大小,把逻辑地址切开。
比如页面大小 4KB。
4K=2124K = 2^{12}4K=212,说明地址的后 12 位(二进制)是页内地址。前2位是页号。
2)查表:页表映射中根据页号查询页帧号。
3)拼接:页帧号直接拼接偏移量。
口诀 :"页号查表变块号,页内偏移直接抄。"
2.4.1.3、状态位与淘汰
为了管理内存,页表里增加了很多"标记位":
1)状态位 :1=在内存里(可以直接用);0=不在内存里(需要发生缺页中断,去磁盘捞)。
2)访问位 :1=最近被读过;0=最近没人理它(淘汰时优先踢它)。
3)修改位 :1=被改过(脏了);0=没改过(干净的)。
2.4.1.4、为什么要"页式存储"?
主要是解决"碎片"问题。
如果不分页(连续分配) :程序 A 需要 10 平米,程序 B 需要 5 平米。如果内存中间空出了 3 平米、2 平米、4 平米的缝隙,虽然总和有 9 平米,但因为不连续,B 程序根本放不进去。这就叫"碎片"。
页式存储:把程序 B 打碎成 5 个 1 平米的小块。这里塞一块,那里塞一块。只要总空闲面积够,就能塞进去。
代价:因为打碎了,所以需要一个**"账本"(页表)**来记录每一块碎片到底藏在哪了。
2.4.1.5、为什么要"淘汰"?
主要是解决"穷"的问题。
现状:内存(RAM)很贵且小(比如 16G),磁盘(Disk)很便宜且大(比如 1TB)。
需求:我想玩《黑神话:悟空》,这个游戏可能高达 100G,而内存只有 16G。怎么跑?
办法(虚拟存储) :骗 CPU 说我们有很大内存。实际上,只把游戏当前正在玩的那一小部分画面(比如 2G)加载到内存里(状态位=1)。剩下的 98G 都在磁盘上睡大觉(状态位=0)。
2.4.1.6、淘汰的原则是什么?
当内存满了,新的数据要进来,必须踢走一个旧的。
核心逻辑:踢掉那个"最没用 "且"最省事"的页面。
1)第一轮筛选(看访问位)
访问位=0(最近没人理它):优先踢走。
访问位=1(最近刚有人用过):说明可能马上又要用,别踢,留着(也就是 LRU 算法的思想)。
2)第二轮筛选(看修改位)
如果两个页面最近都没人理(访问位都是0),那踢谁?
修改位=0(干净的) :直接覆盖。因为它和磁盘里的备份是一模一样的,踢它不需要写回磁盘,速度快。
修改位=1(脏的/修改过的) :踢它之前,必须把它写回磁盘保存修改,否则数据就丢了。这会消耗 I/O 时间。
结论:优先踢"干净"的,因为省事。
2.4.2、段式存储
2.4.2.1、段式存储核心逻辑图
物理地址计算
越界检查 (核心考点)
逻辑地址 (S, d)
查段表
查段表
No (d >= 段长)
Yes (d < 段长)
段号 S
段内偏移量 d
d < 段长 ?
❌ 越界中断/非法地址
✅ 合法
取出基址 Base
Base + d
物理地址
2.4.2.2、为什么要"段式"?
是为了用户/程序员着想。按逻辑分块,比如主程序段、子程序段、数据段、栈段。
特点 :段的长度是不一样的(Variable Length)。
对比页式是为了物理内存 着想。切成一样大的 4K 小块,塞进内存的缝隙里,没有逻辑意义。缺点是代码可能被从中间切断。
2.4.2.3、段表结构
因为段长不一样,所以段表比页表多了一列数据:
1)页表 :只需要记录 块号(因为大家都知道页长是 4K)。
2)段表 :必须记录 段长 (Limit) 和 基址 (Base)。
基址:这个段从内存哪里开始。
段长:这个段有多大(用于防越界)。
2.4.2.4、地址转换与计算
1)查表 :根据段号,找到对应的 段长 和 基址。
2)安检(越界检查) :规则是段内偏移量 必须小于 段长。
3)计算 :物理地址 = 基址 + 偏移量。
2.4.2.5、优点
多道程序共享内存:比如大家都可以共享同一个"标准函数库"段,只需要在段表里指过去就行。
保护:各段程序修改互不影响。
2.4.2.6、缺点
内存利用率低,内存碎片浪费大。
解释:因为段长不一样(有的 10K,有的 100K),内存里容易出现很多这种"大小不一的空洞"(外部碎片),很难塞进新的段。
2.4.3、段页式存储
2.4.3.1、核心逻辑图
段页式存储最大的考点在于**"三次访存"**(查段表 -> 查页表 -> 访问数据)。
物理地址生成
地址转换流程
逻辑地址
段号 S
页号 P
页内偏移 d
查段表与越界检查
段表 Segment Table
获取该段的页表始址
页表 Page Table
查页表获取块号 f
拼接物理地址 (f+d)
访问物理内存数据
物理内存
2.4.3.2、结构拆解
逻辑地址 = 段号(S) + 页号§ + 页内地址(d)
段号 (15-14) :占 2 位。22=42^2 = 422=4,说明系统最多支持 4个段。
页号 (13-9) :占 5 位。25=322^5 = 3225=32,说明每个段最多只有 32个页。
页内地址 (8-0) :占 9 位。29=5122^9 = 51229=512,说明页面大小是 512B。
2.4.3.3、工作原理
先分段 :把程序按照逻辑(主程序、子程序、数据栈)分成若干个段。
再分页 :把每个段,再切成固定大小的页。
进内存:内存里还是只有固定大小的"物理块",把页塞进去。
2.4.3.4、查表机制
1)段表存什么?
段表里不再存基址了(那是段式干的事)。
段页式的段表里,存的是 "页表的大小" 和 "页表的存放地址"。
2)页表存什么?
页表里存的是 物理块号(和页式一样)。
2.4.3.5、优缺点对比
| 特性 | 说明 | 评价 |
|---|---|---|
| 优点 | 空间浪费小(因为分页了)、存储共享容易(因为分段了)、能动态连接 | 集众家之长 |
| 缺点 | 硬件要求高、系统开销大、执行速度大大下降 | 死穴:慢! |
2.4.3.6、为什么速度慢?
页式/段式:查1次表 + 访1次存 = 2次内存访问。
段页式:查段表 + 查页表 + 访存 = 3次内存访问。
2.4.3.7、越界检查有几次?
第一次:检查段号是否超过段表长度。
第二次:检查页号是否超过该段的页表长度。
2.4.3.8、一个程序有几张表?
1 张段表。
n 张页表(每个段对应一张页表)。
2.5、磁盘管理
2.5.1、磁盘物理结构
2.5.1.1、盘面 (Platter)
磁盘是由多个圆形盘片叠在一起组成的。
每个盘片有两个面(上面和下面),都可以存数据。
2.5.1.2、磁道 (Track)
盘面上那一圈一圈的同心圆。
最外圈通常是 0 磁道。
2.5.1.3、扇区 (Sector)
每个磁道被切分成若干个小块,像披萨的一角,叫扇区。
这是磁盘读写的最小单位(通常是 512B 或 4KB)。
2.5.1.4、磁头 (Head)
所有盘面的磁头都连在同一根主杆 (Actuator/Main Rod) 上。
所有磁头是共进退的。要往里移,大家一起往里移。这就引出了**"柱面"**的概念。
2.5.2、存取时间计算
2.5.2.1、基本公式
存取时间=寻道时间+旋转延迟+传输时间存取时间 = 寻道时间 + 旋转延迟 + 传输时间存取时间=寻道时间+旋转延迟+传输时间
2.5.2.2、寻道时间
动作 :机械手臂把磁头移动到指定的磁道(同心圆)上。
比喻:你去图书馆找书,先要走到对应的"书架"前。
考点:这是我们要通过算法(如电梯算法)去优化的重点。
2.5.2.3、旋转延迟
动作 :磁头到了磁道不动,等盘片旋转,把目标扇区转到磁头屁股底下。
比喻:站在回转寿司台前,等你想吃的那盘菜转到面前。
计算公式:通常取旋转一周时间的一半(平均等待时间)。
例如转速 6000r/min →\to→ 100转/秒 →\to→ 转一圈 10ms →\to→ 平均等待 5ms。
2.5.2.4、传输时间
动作:磁头读取数据传给内存的时间。
比喻:把寿司盘子拿下来的时间。
这个时间通常很短,计算题里有时会忽略,或者直接给数值。
2.5.3、核心性质
磁盘属于直接存取存储器 (DASD)。
它可以随机跳到任意位置读写(通过移动磁头),不需要像磁带那样从头卷到尾(顺序存取)。
2.5.4、移臂调度算法
2.5.4.1、算法全家桶
| 算法名称 | 英文缩写 | 核心逻辑 | 关键词 |
|---|---|---|---|
| 先来先服务 | FCFS | 谁先点单先送谁 | 简单、公平、效率低 |
| 最短寻道时间优先 | SSTF | 谁离我近先送谁 | 贪心、效率高、可能"饿死"远处的 |
| 扫描算法 (电梯算法) | SCAN | 一条路走到头,再回头 | 双向、类似电梯 |
| 循环扫描算法 | C-SCAN | 只往一个方向走,到头直接飞回起点 | 单向、减少边缘等待 |
2.5.4.2、先来先服务
完全按照请求进来的顺序跑,不管远近。
当前磁头位置:100号磁道。
请求队列:55, 58, 39, 18, 90, 160, 150, 38, 184。
路线 :100 →\to→ 55 →\to→ 58 →\to→ 39 →\to→ 18 →\to→ 90 →\to→ 160 →\to→ 150 →\to→ 38 →\to→ 184。
总移动距离 = ∣100−55∣+∣55−58∣+∣58−39∣...=498|100-55| + |55-58| + |58-39| ... = 498∣100−55∣+∣55−58∣+∣58−39∣...=498。
平均寻道长度 = 55.355.355.3。
评价:虽然公平,但效率极低,基本不会用。
2.5.4.3、最短寻道时间优先
站在当前位置,看谁离我最近,就去谁那儿。
当前磁头位置:100号磁道。
请求队列:55, 58, 39, 18, 90, 160, 150, 38, 184。
谁最近?90 (距离10)。 →\to→ 去 90。
现在在 90。谁最近?(剩55, 58, 150...)。58 (距离32) 最近。 →\to→ 去 58。
现在在 58。55 (距离3) 最近。 →\to→ 去 55。
...以此类推... 39 →\to→ 38 →\to→ 18。
大跳跃 :到了 18 之后,这一带都送完了。最近的只剩那边的大号磁道了,只能跳去 150。
最后 150 →\to→ 160 →\to→ 184。
平均寻道长度 = 27.5。
对比:比 FCFS 快了一倍!
缺点 (饥饿现象):如果磁头一直在 18-50 这一带忙活,突然来个 1000 号的请求,磁头可能永远不过去,因为总有离得更近的插队。
2.5.4.4、扫描算法
就像大楼里的电梯。
如果决定往上走 ,就必须把上面所有层(请求)都送完,直到顶层 (或者最远的请求),才能掉头往下走。
中间不能随意折返(这点和 SSTF 不同)。
应用:解决了 SSTF 的"饥饿"问题,且效率不错。
2.5.4.5、循环扫描
限制磁头只能单向移动(比如只能从里向外)。
当磁头移动到最外层,它不回头服务,而是迅速返回到最内层,重新开始新一轮的"从里向外"。
为什么要这样?:为了让每一个磁道的等待时间更平均(防止两端和中间不均)。
2.5.4.6、流水线公式
总时间=第一条指令完整执行时间+(剩余任务数×流水线周期)总时间 = \text{第一条指令完整执行时间} + (\text{剩余任务数} \times \text{流水线周期})总时间=第一条指令完整执行时间+(剩余任务数×流水线周期)
第一条指令完整执行时间 :就像工厂早上开工,生产第一个零件时,机器要预热,传送带要走完,整个流程必须完整走一遍,没法省时间。(也就是你在上一轮里算出来的"初始启动时间")。
算第一个 :First=T+M+CFirst = T + M + CFirst=T+M+C (雷打不动,先把第一个算出来)。
流水线周期 :从第二个零件开始,因为机器都转起来了,生产每一个新零件只需要增加一个"最慢环节"的时间(这就是瓶颈时间)。
找周期 (Cycle):
- 单缓冲 :Cycle=max(T+M,C)Cycle = \max(T+M, C)Cycle=max(T+M,C)。
- 双缓冲 :Cycle=max(T,M+C)Cycle = \max(T, M+C)Cycle=max(T,M+C)。
套公式 :Total=First+(N−1)×CycleTotal = First + (N-1) \times CycleTotal=First+(N−1)×Cycle。
2.6、文件系统
2.6.1、索引文件结构
2.6.1.1、核心结构:混合索引表
磁盘块大小 = 1KB (1024 Bytes)
地址项大小 = 4B
核心常数 N:一个磁盘块能存多少个地址?
N=1024/4=256 个N = 1024 / 4 = \mathbf{256} \text{ 个}N=1024/4=256 个
| 索引项下标 | 类型 | 含义 | 存取速度 |
|---|---|---|---|
| 0 ~ 9 | 直接索引 | 直接存数据的物理块号 | 最快 (1次访盘) |
| 10 | 一级间接索引 | 存一个索引块的地址 (索引块里存数据块号) | 中等 (2次访盘) |
| 11 | 二级间接索引 | 存一级索引块的地址 -> 二级索引块 -> 数据 | 较慢 (3次访盘) |
| 12 | 三级间接索引 | 套娃三层 | 最慢 (4次访盘) |
2.6.1.2、直接索引
对应项 :下标 0 ~ 9。
原理:如果不差钱,直接把物理地址写在户口本上。
容量 :10 个地址项,对应 10 个物理块。
逻辑页号范围 :0 ~ 9 页。
特点:访问速度最快,专门给小文件用。
2.6.1.3、一级间接索引
对应项 :下标 10。
原理 :这一项指向一个索引块。这个块里全是物理地址。
容量 :1 个索引块能存 256 个地址。
256×1KB=256KB256 \times 1\text{KB} = 256\text{KB}256×1KB=256KB。
逻辑页号范围 :10 ~ 265 (10 + 256 - 1)。紧接在直接索引之后。
2.6.1.4、二级间接索引
对应项 :下标 11。
原理:套娃。指向一个索引块,这个块里的每一项又指向另一个索引块。
容量 :256×256=65536256 \times 256 = 65536256×256=65536 个物理块。
65536×1KB=64MB65536 \times 1\text{KB} = 64\text{MB}65536×1KB=64MB。
逻辑页号范围 :266 ~ 65801 (266 + 65536 - 1)。紧接在一级索引之后。
2.6.1.5、三级间接索引
对应项 :下标 12。
容量 :256×256×256256 \times 256 \times 256256×256×256 个物理块。
大小 :16M×1KB=16GB16\text{M} \times 1\text{KB} = 16\text{GB}16M×1KB=16GB。
这足以存放非常大的文件了。
2.6.2、访盘次数
假设 i-node 已经被读入内存(通常考试默认这样),读取文件中的数据需要访问几次磁盘?
1)直接索引 :内存查 i-node →\to→ 拿到物理块号 →\to→ 访问数据 。总计:1 次。
2)一级间接 :内存查 i-node →\to→ 读索引块 (1次) →\to→ 拿到物理块号 →\to→ 访问数据 (1次)。总计:2 次。
3)二级间接 :读一级索引块 + 读二级索引块 + 读数据。总计:3 次。
4)三级间接 :读一 + 读二 + 读三 + 读数据。总计:4 次。
2.6.3、位示图
2.6.3.1、核心概念
用途:用来记录磁盘上哪些物理块是用掉的,哪些是空的。
原理 :用 1个二进制位 (bit) 对应 1个物理块。
1:表示占用 (Used)。
0:表示空闲 (Free)。
结构:由若干个**"字" (Word)** 组成,每个字包含若干个**"位" (Bit)**。
2.6.3.2、计算 1:需要多少空间?
题目 :有 NNN 个物理块,字长为 LLL,需要多少个字来存位示图?
公式 :字数=⌈物理块总数字长⌉字数 = \lceil \frac{\text{物理块总数}}{\text{字长}} \rceil字数=⌈字长物理块总数⌉
注意:要向上取整!只要多出1个块,也得占1个完整的字
例题:
物理块数:1000 个
字长:16 位
计算:1000/16=62.51000 / 16 = 62.51000/16=62.5
结果:需要 63 个字(62个填满,第63个字填一部分)。
2.6.3.3、计算 2:位置映射 (在哪儿?)
题目 :物理块号为 NNN,它在位示图的第几个字?第几位? 关键前提 :必须看清题目是从 0 开始编号,还是从 1 开始编号!
从 0 开始编号
物理块号:0,1,2,...0, 1, 2, \dots0,1,2,...
字号、位号:0,1,2,...0, 1, 2, \dots0,1,2,...
公式:
字号=N÷字长(取商)\text{字号} = N \div \text{字长} \quad (\text{取商})字号=N÷字长(取商)
位号=N%字长(取余)\text{位号} = N \% \text{字长} \quad (\text{取余})位号=N%字长(取余)
问题 :64号 物理块在哪?(字长16)
字号:64/16=464 / 16 = \mathbf{4}64/16=4
位号:64%16=064 \% 16 = \mathbf{0}64%16=0
答案 :在 4号字 的 0号位。千万不要因为整除了就觉得在前一个字里!
如果题目说"字号、位号从1开始",公式变为:
- 字号=(N−1)÷字长+1\text{字号} = (N-1) \div \text{字长} + 1字号=(N−1)÷字长+1
- 位号=(N−1)%字长+1\text{位号} = (N-1) \% \text{字长} + 1位号=(N−1)%字长+1
三、系统性能
3.1、性能指标
3.1.1、硬件指标
时钟频率 (主频):CPU 的心跳速度,单位 Hz/MHz/GHz。
运算速度:通常用 MIPS (百万条指令/秒) 或 MFLOPS (百万次浮点运算/秒) 衡量。
字长 (Word Length):计算机一次能处理的二进制位数 (32位/64位)。
数据处理速率 (PDR)。
吞吐率:系统在单位时间内处理的任务量。
RASIS 特性:可靠性 (Reliability)、可用性 (Availability)、可维护性 (Serviceability)、完整性 (Integrity)、安全性 (Security)。
3.1.2、网络设备指标
路由器 :设备吞吐量、端口吞吐量、丢包率、时延、时延抖动。
交换机:交换机类型、支持的协议和标准。
3.1.3、软件与系统指标
操作系统:系统可靠性、系统吞吐率、系统响应时间、资源利用率、可移植性。
数据库管理系统 (DBMS):
- 容量:数据库大小、表数量、记录大小。
- 性能 :最大并发事务处理能力 、最大连接数、负载均衡能力。
WEB 服务器 :最大并发连接数、响应延迟、吞吐量。
3.2、性能指标计算公式
3.2.1、基础概念
1)时钟周期 (Clock Cycle):CPU 动作的最小时间单位。
时钟周期=1主频时钟周期 = \frac{1}{\text{主频}}时钟周期=主频1
(例如:主频 1GHz,时钟周期 = 1ns)
2)CPI (Clock Per Instruction) :执行一条指令平均需要多少个时钟周期。
- CPI 越小越好(说明干活快)。
3)IPC (Instruction Per Clock) :每个时钟周期能执行多少条指令。
- IPC=1/CPIIPC = 1 / CPIIPC=1/CPI。
- IPC 越大越好。
3.2.2、MIPS 计算 (百万条指令每秒)
MIPS=指令总条数执行时间×106MIPS = \frac{\text{指令总条数}}{\text{执行时间} \times 10^6}MIPS=执行时间×106指令总条数
因为 执行时间=指令数×CPI×时钟周期执行时间 = \text{指令数} \times CPI \times \text{时钟周期}执行时间=指令数×CPI×时钟周期,代入后可得:
MIPS=主频CPIMIPS = \frac{\text{主频}}{CPI}MIPS=CPI主频
或者
MIPS=主频×IPCMIPS = \text{主频} \times IPCMIPS=主频×IPC
3.2.3、MFLOPS 计算 (百万次浮点运算每秒)
MFLOPS=浮点操作次数执行时间×106MFLOPS = \frac{\text{浮点操作次数}}{\text{执行时间} \times 10^6}MFLOPS=执行时间×106浮点操作次数
3.3、系统性能调整
3.3.1、核心定义
什么时候做? 当系统性能降到"最基本水平"时。
做什么? 性能调整的本质就是 查找和消除瓶颈 (Find and Eliminate Bottlenecks)。
3.3.2、调整流程
3.3.2.1、准备工作
识别约束:有什么限制条件?
指定负载:系统要扛多大压力?
设定性能目标:要达到什么指标(如 TPS 多少,响应时间多少)?
3.3.2.2、建立基准
建立边界和期望。
3.3.2.3、实施循环
这是一个持续迭代的过程
收集 (Collect):获取监控数据。
分析 (Analyze):定位瓶颈在哪里。
配置 (Configure):调整参数或代码。
测试 (Test):验证调整有没有效果。
(然后回到收集,周而复始,直到达标)
3.3.4、针对不同对象的调整策略
| 调整对象 | 关注重点 (Keywords) | 具体措施 |
|---|---|---|
| 数据库系统 | 资源 & 设计 | 1. CPU/内存 使用状况 2. 优化数据库设计 (如索引、范式) 3. 优化数据库管理 (如锁机制) 4. 进程/线程 状态 5. 硬盘 剩余空间、日志文件大小 |
| 应用系统 | 体验 & 并发 | 1. 应用系统的可用性 2. 响应时间 (Response Time) 3. 并发用户数 4. 特定应用的系统资源占用 |
3.3、阿姆达尔定律
3.3.1、核心概念
定义 :系统中某组件采用更快的执行方式后,整个系统性能的提升程度,取决于该组件被使用的频率(或占总执行时间的比例)。
通俗理解:如果你优化的那部分代码(或硬件)只占整个系统运行时间的 1%,哪怕你把它优化得再快,整个系统的提升也微乎其微。
结论:要想显著提升系统性能,必须优化那些**"占大头"**(执行时间占比高)的部分。
3.3.2、计算公式
R=TpTi=1(1−Fe)+FeSeR = \frac{T_p}{T_i} = \frac{1}{(1 - F_e) + \frac{F_e}{S_e}}R=TiTp=(1−Fe)+SeFe1
RRR (Speedup) :加速比。即改进后的系统比原来快了多少倍。
TpT_pTp :改进前完成整个任务的时间。
TiT_iTi :改进后完成整个任务的时间。
FeF_eFe (Fraction enhanced) :改进比例。
- 即:被改进的部分在原系统 总执行时间中占的比例。(0≤Fe≤10 \le F_e \le 10≤Fe≤1)
SeS_eSe (Speedup enhanced) :部件加速比。
- 即:被改进的那部分,其性能提高了多少倍。(Se>1S_e > 1Se>1)
3.3.3、物理意义
改进后的总时间 = 不可改进部分的时间 + 改进部分的新时间
假设原来的总时间是 1:
- 不可改进部分 :占 1−Fe1 - F_e1−Fe。这部分时间雷打不动。
- 可改进部分 :占 FeF_eFe。现在升级了 SeS_eSe 倍,所以新时间变成 FeSe\frac{F_e}{S_e}SeFe。
- 新总时间 :(1−Fe)+FeSe(1 - F_e) + \frac{F_e}{S_e}(1−Fe)+SeFe。
系统总加速比 RRR = 原时间 / 新时间 = 1(1−Fe)+FeSe\frac{1}{(1 - F_e) + \frac{F_e}{S_e}}(1−Fe)+SeFe1
3.4、性能评价方法
3.4.1、经典评价方法
| 方法名称 | 英文缩写 | 核心特点 & 考点 |
|---|---|---|
| 时钟频率法 | Clock | 最简单 。只看 CPU 频率(主频),忽略了指令系统的差异。频率高 ≠\neq= 速度快。 |
| 指令执行速度法 | MIPS | 用 MIPS (百万指令/秒) 衡量。缺点是没考虑不同指令执行时间不同(如加法快,除法慢)。 |
| 等效指令速度法 (吉普森混合法) | Gibson mix | 加权计算 。通过统计各类指令在程序中出现的比例 (WiW_iWi) 来计算。 ⭐️ 特点:考虑了指令比例不同的问题。 |
| 数据处理速率法 | PDR | PDR=L/RPDR = L/RPDR=L/R。 ⭐️ 特点 :不仅考虑 CPU,还考虑了存储 (CPU + Cache + 主存) 的性能。PDR 值越大越好。 |
| 综合理论性能法 | CTP | 用 MTOPS (每秒百万次理论运算) 表示。先算每个单元的有效计算率,再按字长调整。 |
| 基准程序法 | Benchmark | 目前公认最好 的方法。把应用程序中用得最多、最频繁的核心程序作为标准来测试。 |
3.4.2、基准测试程序
3.4.2.1、测试精确度排名
如果题目问"哪种测试结果最接近真实性能?",按这个顺序选: 真实的程序 > 核心程序 > 小型基准程序 > 合成基准程序 (越靠左越真实,但也越麻烦;越靠右越简单,但偏差可能越大)
3.4.2.2、常见基准程序对号入座
1)Dhrystone
测 整数 (Integer) 性能。
主要用于测系统编程(编译器、操作系统)能力。
2)Linpack
测 浮点 (Floating point) 性能。
国际上最流行的高性能计算测试(用于测超级计算机)。
3)Whetstone
测 浮点 性能 (Fortran语言编写)。
4)SPEC
测 速度 和 吞吐率。
3.4.2.3、TPC 系列
TPC (Transaction Processing Council) 是专门测数据库 和事务处理 性能的标准。它的单位通常是 tpmC (transactions per minute)。
| TPC 类型 | 测试场景 | 备注 |
|---|---|---|
| TPC-A | OLTP (联机事务处理) | 评价数据库和硬件,旧标准。 |
| TPC-B | 纯事务处理 | 不包括网络,模拟企业计算环境。 |
| TPC-C | 联机订货系统 | 最经典、最常用的在线事务测试标准。 |
| TPC-D/H/R | 决策支持系统 | 测数据仓库、大数据查询分析能力。 |
| TPC-E | 大型企业信息服务 | 模拟复杂的企业环境。 |
| TPC-W | Web 应用 (电子商务) | 基于互联网的浏览、销售行为测试。 |
3.5、系统监视
3.5.1、系统监视的三种方式
| 方式 | 典型工具/命令 | 适用场景 | 备注 |
|---|---|---|---|
| 1. 系统命令 | UNIX/Linux : ps, last, W Windows : netstat |
实时查看状态 | 最快、最直接。 ps: 查看进程 netstat: 查看网络连接端口 |
| 2. 系统日志 | /var/log (Linux) Event Viewer (Windows) |
事后查阅 | 查看特定时间段内的运行状态,找历史故障原因。 |
| 3. 集成监控工具 | Perfmon (Windows自带) (以及现在的 Prometheus/Grafana 等) | 可视化监控 | 集成了命令和日志,有图表,适合长期监控趋势。 |