操作系统基础
操作系统是计算机系统中最核心的软件之一。
它向下管理 CPU、内存、磁盘、输入输出设备等硬件资源,向上为用户和应用程序提供稳定、友好的运行环境。学习操作系统,本质上是在理解计算机如何把有限的硬件资源组织起来,并让多个程序看起来可以同时、稳定、有序地运行。
这篇笔记主要整理:
- 操作系统的作用、特征、功能和分类。
- 进程的组成、状态和进程关系。
- 前驱图、进程资源图、同步与互斥、信号量。
- 死锁、银行家算法和线程。
- 分区存储、页式存储、段式存储和段页式存储。
操作系统概述
Section titled “操作系统概述”操作系统的核心作用可以概括为两点:
- 通过资源管理提高计算机系统效率。
- 改善人机交互,为用户提供友好的工作环境。
没有操作系统时,应用程序需要直接面对硬件。这样不仅开发复杂,也很难保证多个程序之间互不干扰。操作系统把底层硬件抽象成更容易使用的接口,让应用程序可以通过统一方式申请资源、读写文件、创建进程、访问设备。
操作系统的特征
Section titled “操作系统的特征”操作系统通常具有四个基本特征。
| 特征 | 含义 |
|---|---|
| 并发性 | 多个程序在同一时间段内交替运行,看起来像同时执行 |
| 共享性 | 多个进程可以共同使用系统中的资源 |
| 虚拟性 | 把一个物理资源抽象成多个逻辑资源,例如虚拟内存 |
| 不确定性 | 程序执行顺序和完成时间可能受到调度、资源竞争等因素影响 |
并发性和共享性是操作系统最基本的两个特征。
如果多个程序可以并发执行,就必然涉及资源共享;而资源共享又会带来同步、互斥、死锁等问题。
操作系统的功能
Section titled “操作系统的功能”操作系统主要负责五类管理工作。
| 功能 | 说明 |
|---|---|
| 进程管理 | 创建、调度、阻塞、唤醒和终止进程 |
| 存储管理 | 管理内存分配、地址转换和虚拟存储 |
| 文件管理 | 管理文件的创建、删除、读写、目录和权限 |
| 设备管理 | 管理输入输出设备,提高设备利用率 |
| 作业管理 | 管理用户提交的作业,组织作业执行流程 |
可以简单理解为:操作系统负责让程序能运行、让内存能分配、让文件能保存、让设备能使用。
操作系统的分类
Section titled “操作系统的分类”常见操作系统可以按使用场景和设计目标分类。
| 类型 | 特点 |
|---|---|
| 批处理操作系统 | 用户提交作业后由系统成批处理,交互性较弱 |
| 分时操作系统 | 多个用户轮流使用 CPU 时间片,强调交互性 |
| 实时操作系统 | 要求在规定时间内快速响应,常用于工业控制等场景 |
| 网络操作系统 | 支持网络通信和网络资源共享 |
| 分布式操作系统 | 多台物理分散的计算机协同工作,对用户表现为统一系统 |
| 微机操作系统 | 面向个人计算机,例如 Windows、macOS、Linux 桌面版 |
| 嵌入式操作系统 | 面向嵌入式设备,强调资源占用小、稳定性高 |
不同类型的操作系统侧重点不同,但底层都离不开进程、内存、文件和设备管理。
计算机启动流程
Section titled “计算机启动流程”计算机启动时,操作系统并不是一开始就在运行。
一个简化的启动流程如下:
BIOS / UEFI -> 主引导记录 / 引导加载程序 -> 加载操作系统内核 -> 初始化系统服务 -> 进入用户环境传统说法中常见的流程是:
BIOS -> 主引导记录 -> 操作系统BIOS 或 UEFI 负责完成硬件自检,并找到可启动设备。引导加载程序再负责把操作系统内核加载到内存中,最终由操作系统接管整个系统。
进程是正在运行的程序实例。
程序本身只是静态文件,只有被加载到内存并开始执行后,才会形成进程。操作系统以进程为单位进行资源分配和管理。
一个进程通常包括:
| 组成 | 说明 |
|---|---|
| 进程控制块 PCB | 进程的唯一标识,保存进程状态、调度信息、资源信息等 |
| 程序 | 描述进程要执行的指令 |
| 数据 | 进程运行过程中需要使用或产生的数据 |
其中 PCB 是操作系统管理进程的关键。
操作系统并不是直接“记住一个程序”,而是通过 PCB 记录这个进程是谁、处于什么状态、占用了哪些资源、下一步应该如何调度。
进程最基础的状态模型是三态模型。
就绪 -> 运行 -> 等待 ^ | | | | v +-------+-------+更清晰地说:
| 状态 | 含义 |
|---|---|
| 就绪 | 进程已经具备运行条件,只差 CPU |
| 运行 | 进程正在 CPU 上执行 |
| 等待 | 进程正在等待某个事件或资源,例如 I/O 完成 |
状态转换可以这样理解:
就绪 --调度--> 运行运行 --时间片到--> 就绪运行 --等待事件--> 等待等待 --事件发生--> 就绪五态模型是在三态模型基础上增加了挂起状态。
| 状态 | 含义 |
|---|---|
| 活跃就绪 | 在内存中,等待 CPU 调度 |
| 活跃阻塞 | 在内存中,等待事件发生 |
| 静止就绪 | 被挂起,不在内存中,但已经具备运行条件 |
| 静止阻塞 | 被挂起,不在内存中,同时还在等待事件 |
| 运行 | 正在 CPU 上执行 |
挂起通常来自人为干预或系统调度策略。进程被挂起后进入静止状态,需要被激活后才能重新回到活跃状态。
前驱图用于表示任务之间的先后关系。
它可以说明哪些任务可以并行执行,哪些任务必须等待前置任务完成。
例如:
A ─┐B ─┼──> D ───> EC ─┘这个前驱图表示:
- A、B、C 之间没有依赖关系,可以并行执行。
- D 必须等 A、B、C 全部完成后才能执行。
- E 必须等 D 完成后才能执行。
所以前驱图主要确定两件事:
- 任务之间能否并行。
- 任务之间的先后顺序。
进程资源图用于表示进程和资源之间的分配、请求关系。
常见符号:
| 符号 | 含义 |
|---|---|
P | 进程 |
R | 资源 |
| 资源框中的圆点 | 该类资源的实例数量 |
R -> P | 资源已经分配给进程 |
P -> R | 进程正在请求资源 |
例如:
R1 -> P1 -> R2可以理解为:
- R1 中的某个资源已经分配给 P1。
- P1 还需要请求 R2 才能继续执行。
节点可以分为两类:
| 类型 | 含义 |
|---|---|
| 阻塞节点 | 进程请求的资源已经全部分配完,暂时无法获得资源 |
| 非阻塞节点 | 进程请求的资源仍有剩余,可以继续分配 |
当进程资源图中所有进程都是阻塞节点,并且形成循环等待关系时,系统就可能进入死锁状态。
同步和互斥都是为了处理并发环境下的进程协作问题,但它们关注点不同。
互斥指多个进程不能同时访问某个临界资源。
临界资源是一次只允许一个进程访问的资源。例如:
- 打印机。
- 公共变量。
- 某些共享文件。
- 数据库中的某个共享记录。
如果多个进程同时访问临界资源,可能导致数据错误、文件损坏或程序异常。
互斥的目标是:
同一时刻,只允许一个进程进入临界区。互斥信号量通常只有两个值:
1:资源空闲,可以进入0:资源被占用,需要等待可以把互斥信号量想象成门锁。门开着表示可以进入,门锁上表示里面有人,其他人必须等待。
同步指多个进程按照某种顺序协调执行。
它关注的不是“谁不能同时访问”,而是“谁必须先执行,谁必须后执行”。
例如生产者和消费者:
生产者先生产数据消费者后消费数据如果没有数据,消费者就应该等待;如果缓冲区满了,生产者也应该等待。
同步信号量可以用来表示某类资源或条件的数量。例如餐厅里的餐桌数量有限,顾客需要先获得餐桌才能吃饭。餐桌数量就是一个同步信号量。
信号量是一种用于进程同步和互斥的机制。
它通常配合两个操作使用:
- P 操作:申请资源。
- V 操作:释放资源。
P 操作表示申请资源。
S = S - 1判断规则:
| 条件 | 结果 |
|---|---|
S >= 0 | 说明资源可用,进程继续执行 |
S < 0 | 说明资源不足,进程进入阻塞队列 |
也可以这样理解:
先尝试拿走一个资源。如果拿完后资源数量仍然够用,就继续执行。如果资源不够,就阻塞等待。V 操作表示释放资源。
S = S + 1判断规则:
| 条件 | 结果 |
|---|---|
S > 0 | 没有进程因该资源阻塞,当前进程继续执行 |
S <= 0 | 有进程正在等待该资源,需要唤醒一个阻塞进程 |
也可以这样理解:
释放一个资源。如果有人在等,就唤醒一个等待进程。如果没人等,资源数量增加。死锁是指多个进程因为竞争资源而互相等待,最终都无法继续执行。
死锁产生通常需要同时满足四个必要条件:
| 条件 | 含义 |
|---|---|
| 互斥条件 | 资源一次只能被一个进程使用 |
| 请求并保持条件 | 进程已经占有资源,同时还在请求其他资源 |
| 不可剥夺条件 | 已分配给进程的资源不能被系统强制剥夺 |
| 循环等待条件 | 多个进程之间形成资源等待环 |
例如:
P1 占有 R1,等待 R2P2 占有 R2,等待 R1此时 P1 和 P2 都在等待对方释放资源,系统进入死锁。
银行家算法是一种避免死锁的算法。
它的核心思想是:在分配资源之前,先判断这次分配是否会让系统进入不安全状态。
如果分配后系统仍然安全,就允许分配;如果可能进入不安全状态,就暂时不分配。
它关注几个概念:
| 概念 | 含义 |
|---|---|
| Available | 当前系统可用资源数量 |
| Max | 每个进程最多需要的资源数量 |
| Allocation | 已经分配给每个进程的资源数量 |
| Need | 每个进程还需要的资源数量 |
其中:
Need = Max - Allocation银行家算法并不是等死锁发生后再处理,而是在资源分配前尽量避免系统走向死锁。
传统进程有两个重要属性:
- 进程是拥有资源的独立单位。
- 进程是可独立调度和分配的基本单位。
引入线程后,这两个属性被拆开:
| 概念 | 作用 |
|---|---|
| 进程 | 拥有资源的基本单位 |
| 线程 | 独立调度的最小单位 |
同一进程中的多个线程可以共享进程资源,例如:
- 代码段。
- 全局变量。
- 打开的文件。
- 堆空间。
但线程也有自己的私有数据,例如:
- 程序计数器。
- 寄存器上下文。
- 栈空间。
线程的引入可以减少进程切换开销,提高并发执行效率。
存储管理主要解决一个问题:
如何把程序合理地放入内存,并让 CPU 正确访问它。程序运行时往往不能简单地一次性完整装入内存。操作系统需要负责内存分配、地址转换、内存保护和虚拟存储。
常见存储管理方式包括:
- 分区存储。
- 页式存储。
- 段式存储。
- 段页式存储。
分区存储把内存划分成若干区域,每个进程装入一个分区。
常见方式:
| 方式 | 特点 |
|---|---|
| 固定分区 | 分区大小固定,实现简单,但容易造成内部碎片 |
| 可变分区 | 分区大小按进程需求动态分配,利用率更高,但容易产生外部碎片 |
内部碎片指分区内部没有被使用的空间。
外部碎片指空闲空间分散在内存各处,虽然总量够,但无法形成连续空间。
页式存储是一种常见的内存管理方式。
它把程序的逻辑地址空间划分为大小相等的页,同时把物理内存划分为大小相等的块。页和块大小相同,常见大小是 4KB。
程序运行时,不需要连续存放在物理内存中。不同页面可以离散地放入不同内存块,再通过页表建立映射关系。
逻辑页 -> 页表 -> 物理块页式存储的好处是:
- 不要求程序连续存放。
- 减少外部碎片。
- 便于实现虚拟内存。
逻辑地址和物理地址
Section titled “逻辑地址和物理地址”逻辑地址是程序看到的地址,也可以理解为 CPU 生成的虚拟地址。
物理地址是内存单元在真实物理内存中的地址。
程序访问内存时,并不是直接拿逻辑地址访问物理内存,而是由操作系统和硬件共同完成地址转换。
逻辑地址 -> 地址转换 -> 物理地址在页式存储中,逻辑地址通常分为:
页号 + 页内地址页号用于查页表,找到对应的物理块号。
页内地址表示在该页内部的偏移量。
地址转换过程可以简化为:
逻辑地址 = 页号 + 页内地址页号 -> 查页表 -> 块号物理地址 = 块号 + 页内地址页号决定数据在哪个物理块中,页内地址决定数据在这个块中的具体位置。
段式存储按照程序的逻辑结构划分内存。
例如:
- 代码段。
- 数据段。
- 栈段。
- 堆段。
段式存储中的逻辑地址通常由两部分组成:
段号 + 段内地址段式存储更符合程序员对程序结构的理解,但每个段长度不固定,容易产生外部碎片。
段页式存储结合了段式和页式的特点。
它先按逻辑结构分段,再把每个段划分为页。
地址结构可以理解为:
段号 + 页号 + 页内地址段页式存储的特点:
- 保留段式存储的逻辑结构。
- 利用页式存储减少外部碎片。
- 地址转换过程更复杂。
操作系统基础可以先抓住三条主线:
- 进程管理:解决多个程序如何并发执行。
- 存储管理:解决程序如何装入内存并正确访问。
- 资源管理:解决多个进程如何共享有限资源。
进程、线程、同步、互斥、信号量、死锁和页式存储,是学习操作系统时最重要的一组基础概念。
真正理解这些概念后,再看 Linux、数据库、后端服务、并发编程和容器技术,很多底层问题都会更容易串起来。