当前位置: 首页 > news >正文

第二章 进程与线程

2. 进程与线程

2.1 进程和线程

2.1.1 进程的概念和组成

  • 程序: 放在磁盘里面的可执行文件,是静态的。

  • 进程: 是程序的一次执行过程, 是动态的。

当一个进程被创建的时候,操作系统会为此程序创建一个唯一的,不重复的ID — PID

UID 就是进程所属的用户。

上面所说的PID UID 以及图片中的cpu,磁盘,内存等等 都需要放在一个进程控制块中(PCB) PCB是进程存在的唯一标志

image-20250813165156689

**进程的组成: ** PCB、程序段、数据段。

PCB,是给操作系统用的,程序段和数据段是进程给自己用的。

image-20250813165918923

程序段、数据段、PCB三部分组成了进程实体(进程映像)。

进程是进程实体的运行过程,是系统进行资源分配和调度的一个单位。

2.1.2 进程的特征

程序是静态的,进程是动态的,相比于程序,进程拥有一下特征:

  • 动态性:进程是程序的一次执行过程,是动态产生的,变化和消亡的。
  • 并发性:内存中有个进程实体,各进程可以并发执行。
  • 独立性:进程是能独立运行,独立获得资源,独立接受调度的基本单位
  • 异步性:各进程各自独立的,不可预知的速度向前推进
  • 结构性:每个进程都配置一个PCB,都由程序段和数据段 PCB组成。

2.1.3 进程的状态与转化

image-20250813171633226

状态:

  • 创建态:进程正在被创建,这个阶段操作系统会为进程分配资源,初始化PCB。
  • 就绪态:进程已经创建完毕了,但由于没有空闲的CPU,就暂时不运行。
  • 运行态:当CPU空闲下来,操作系统就会从处于就绪态的进程中,选择一个,进行运行,就叫运行态。
  • 阻塞态:在进程运行中,有可能会请求某个事件的发生,所以在这个事件发生之前,CPU会使这个进程下去,让他变位阻塞态,然后CPU再选择一个就绪态的进程。当它等待的事情发生后,这个进程就又变回就绪态。
  • 终止态:一个进程执行exit 请求操作系统终止进程,此时进程进入了终止态,然后让该进程下CPU,并回收内存空间等资源,最后回收PCB。

image-20250813174231156

2.1.4 进程控制

进程控制就是 OS 用原语通过 PCB 对进程的创建、运行、阻塞、唤醒、终止进行管理,确保多进程在系统中安全、高效、有序地运行。

原语的执行必须一气呵成,不可中断。

进程的创建

  • 分配进程控制块(PCB)
  • 分配所需的内存和资源
  • 初始化寄存器、程序计数器等运行环境
  • 典型调用:fork()(Linux)

进程的终止

  • 回收资源(内存、文件描述符等)
  • 销毁 PCB
  • 通知父进程进程结束状态

进程的阻塞与唤醒(成对出现)

  • 阻塞:进程等待某个事件(I/O 完成、信号等)而暂停运行
  • 唤醒:事件发生后让阻塞进程重新进入就绪队列

进程的切换(上下文切换)

  • 保存当前进程的 CPU 环境(寄存器值、程序计数器等)
  • 恢复下一个进程的运行环境
控制动作PCB 状态变化队列变化
创建(Create)新建 PCB → 初始化状态加入就绪队列
撤销(Terminate)释放 PCB → 回收资源从所有队列移除
阻塞(Block)状态改为阻塞移入阻塞队列
唤醒(Wakeup)状态改为就绪加入就绪队列
切换(Dispatch)保存当前 PCB 寄存器值 → 加载新 PCB运行队列切换

无论哪个进程控制原语,要做的无非三类事情。

  1. 更新PCB中信息
  2. 将PCB插入合适的队列
  3. 分配/回收资源

进程控制 = PCB + 原语 + 队列管理

2.1.5 进程通信(IPC)

进程间通信就是指两个进程或者多个进程之间产生的数据交互,

  • 高级通信: 共享存储消息传递管道通信, 套接字(Socket)(网络通信机制)
  • 低级通信: 信号量信号
高级通信(侧重数据传输)

共享存储:

  • 基于数据结构的共享:规定的类型,规定的长度,这种方式速度慢,限制多,是一种低级通信方式
  • 基于存储区的共享:操作系统在内存中划出一块共享存储区,数据形式,存放位置都由进程控制,这种方式速度快,是一种高级通信方式。

消息传递:

  • 直接通信方式:消息发送进程要指明接受进程的ID

image-20250814171206892

  • 间接通信方式:通过"信箱"间接地通信,可以多个进程往同一个信箱send消息,也可以从多个进程receive消息。

image-20250814174123208

管道通信: 管道通信的管道是一个先进先出的队列(循环队列)。

  • 管道的数据流向是一个单向的,称为半双工通信

image-20250814180128171

  • 如果要实现双向同时通信,则需要两个管道,称为全双工通信

image-20250814180025519

  1. 各个进程要互斥的访问管道。
  2. 管道写满时,写进程将阻塞,直到读进程将管道的数据取走,即可唤醒写进程。
  3. 管道读空时,读进程将阻塞,直到写进程往管道中写入数据,就可唤醒读进程。

image-20250814180927052

低级通信(侧重同步 & 事件通知)
  • 信号量 - 实现进程的同步,互斥。
  • 信号 - 实现进程间通信。

信号用于通知进程某个特定的事件已经发生。进程收到一个信号后,对该信号进行处理。

1️⃣ 什么是信号

  • 信号是软件中断,用于通知进程发生了某种事件。
  • 可以由 内核其他进程 发送。
  • 被发送的进程不需要提前等着,它会在执行过程中被打断去处理信号,所以叫异步

2️⃣ 信号的来源

  1. 内核产生
    • 硬件异常:除零错误 → SIGFPE
    • 访问非法内存:SIGSEGV
    • 子进程结束:SIGCHLD
  2. 用户进程产生
    • 通过系统调用 kill() 给某进程发信号
    • 通过终端快捷键:
      • Ctrl+CSIGINT(中断)
      • Ctrl+ZSIGTSTP(暂停)

3️⃣ 信号的常见类型(Linux)

信号名编号含义
SIGINT2终端中断(Ctrl+C)
SIGKILL9强制杀死进程(不可捕获、不可忽略)
SIGTERM15终止进程(可处理)
SIGSTOP19暂停进程(不可捕获)
SIGSEGV11段错误(非法内存访问)

4️⃣ 信号的处理方式

每当从内核态要转为用户态时,就会处理这些信号,进程可以选择:

  1. 执行默认动作(比如终止、暂停等)
  2. 捕获信号(自定义信号处理函数去处理)
  3. 忽略信号(除 SIGKILLSIGSTOP 外)

5️⃣ 信号的作用

  • 进程控制:杀死、暂停、恢复进程
  • 事件通知:告诉进程某事件发生(如 I/O 完成)
  • 错误处理:异常情况立即打断进程执行

📌 一句话记忆
信号 = 软件世界的“快递员”,异步送来事件通知,让进程立即响应,常用于控制和通知。

2.1.6 线程和多线程模型

线程是一个基本CPU执行单元,也是程序执行流的最小单位,引入线程后,不仅是进程之间可以并发,进程内的各线程之间也可以并发,从而进一步提升了系统的并发性。

image-20250815130725938

线程的主要属性

  1. 轻量级(Lightweight)
    • 线程依赖进程而存在,创建/切换开销比进程小。
    • 同一进程内的线程共享资源(代码段、数据段、文件等)。
  2. 共享性(Sharing)
    • 同一进程的线程共享进程资源:
      • 地址空间
      • 打开的文件
      • 全局变量、堆内存
    • 但每个线程有自己的寄存器环境,保证独立执行。
  3. 独立性(Independence)
    • 尽管共享资源,每个线程都有独立的执行流(程序计数器 PC + 栈)。
    • 一个线程崩溃可能影响整个进程(这是它和进程最大的区别)。
  4. 并发性(Concurrency)
    • 同一进程内多个线程可以并发执行。
    • 在多核 CPU 上,多个线程甚至可以并行运行。
  5. 可调度性(Schedulability)
    • 线程是操作系统调度的基本单位(多数现代 OS 都是以线程为最小调度单位)。
    • 每个线程可以有自己的优先级。
  6. 轻便性(低开销)
    • 线程切换时只需保存/恢复寄存器和栈指针,不涉及整个进程的上下文,所以效率高。

📌 一句话总结

线程 = 进程里的“小兵”:

  • 共享资源(吃同一锅饭),
  • 有自己独立的栈和执行流(各干各的活),
  • 是操作系统调度的最小单位。

线程的实现方式: 用户级线程,内核级线程,混合实现。

  • 1. 用户级线程: 线程由用户态的线程库管理,操作系统内核并不知道有这些线程。

  • 优点:用户级线程的切换在用户空间即可完成,不需要切换到核心态,线程管理的系统开销小,效率高。

  • 缺点: 如上图中while循环中,要是有一个线程被阻塞,其他的线程也无法进行下去,所以并发度不高,多线程不可在多核处理机上并行运行。

  • 2. 内核级线程: 线程由操作系统内核管理和调度。内核为每个线程维护控制块

image-20250816091314443

  • 优点: 当一个线程被阻塞后,别的线程还可以继续执行,并发能力强,多线程在多核处理机上并行执行。

  • 一个用户进程会占用多个内核线程,线程切换由操作系统内核完成,需要切换到核心态,因此线程管理的成本高,开销大。

  • 3. 混合实现:

一对一模型就是内核级线程。

多对一模型就是用户级线程。

**多对多模型:**用户线程不直接一一对应内核线程,而是通过运行时系统调度映射过去。 折中方案,结合了灵活性和高效性。

image-20250816092549721

image-20250816092622163

线程的状态迁移和进程几乎一样:

  • 新建 → 就绪 → 运行 →(阻塞 ↔ 就绪)→ 终止

✅ 进程 vs 线程区别表

对比维度进程(Process)线程(Thread)
基本概念资源分配的最小单位程序执行的最小单位,依赖进程存在
调度单位进程是早期操作系统的调度单位现代操作系统以线程作为基本调度单位
资源拥有拥有独立的地址空间、文件描述符、代码段、数据段等共享进程的资源(地址空间、文件),但有独立的栈和寄存器
开销创建/撤销开销大,切换需要保存整个进程上下文创建/撤销开销小,切换只需保存少量寄存器和栈指针
通信方式需要借助 IPC(管道、消息队列、共享内存、信号等),开销大共享内存(全局变量、堆)即可直接通信,简单高效
健壮性一个进程崩溃通常不会影响其他进程一个线程崩溃可能导致整个进程崩溃
并发性进程之间可并发执行一个进程内多个线程可并发执行
典型应用系统中的独立应用(浏览器、IDE、数据库服务)应用内的任务单元(浏览器的标签页、IDE 的后台编译线程)

📌 一句话记忆

  • 进程是“大房子”,线程是“房子里的工人”。
  • 进程间相互独立,线程间共享资源
  • 调度和执行粒度 → 线程更轻量,更灵活

2.2 CPU调度

2.2.1 调度的概念

当有一堆任务要处理时候,但资源有限,这些事情没法同时处理,这就需要确定某种规则来决定处理这些任务的顺序,这就是调度研究的问题。

调度的三个层次:

  • 高级调度(作业调度): 按一定的原则从外存的作业后备队列中挑选一个作业调入内存,并创建进程。每个作业只调入一次,调出一次。
  • 中级调度(内存调度): 按照某种策略决定将哪个处于挂起状态的进程重新调入内存。
  • 低级调度(进程调度): 按照某种策略从就绪队列中选取一个进程,将处理机分配给它。

暂时调到外村等待的进程状态为挂起状态

image-20250818145834360

image-20250818150039849

2.2.2 进程调度的时机切换与过程调度方式

进程调度的时机(需要调度时)

操作系统会在以下情况触发调度器,从就绪队列中选一个新进程上 CPU:

  1. 进程正常结束
    • 进程运行完成,释放 CPU,需要选择下一个进程。
  2. 进程阻塞
    • 因 I/O 请求、等待事件、获取不到资源等进入阻塞状态。
  3. 进程主动放弃 CPU
    • 调用 sleep()yield() 等,自己让出 CPU。
  4. 时间片用完(抢占式调度)
    • 分时系统里,当进程的时间片耗尽,系统强制切换。
  5. 有更高优先级的进程就绪
    • 抢占式调度策略下,高优先级进程到达,低优先级进程被剥夺 CPU。

❌ 不会发生调度的情况

有些特殊场景不允许调度,否则会破坏系统稳定性:

  • 中断处理过程 中(防止上下文切换影响中断处理)
  • 操作系统内核临界区 中(避免数据结构不一致)
  • 原子操作指令执行过程中

临界区的一般概念

  • 临界区 = 程序中访问 共享资源 的那段代码。
  • 比如多个进程/线程访问一个共享变量、文件或缓冲区 → 就需要进入临界区,避免同时修改导致数据错乱。

操作系统内核临界区

  • 操作系统内核自己使用的关键数据结构 所在的临界区。
  • 例如:
    • 进程控制块(PCB)队列
    • 内存分配表
    • I/O 缓冲队列
  • 这些数据是内核全局共享的,如果被多个进程或中断同时修改,会导致内核数据结构损坏

所以,操作系统会在内核临界区中:

  • 禁止调度(不能切换进程)
  • 禁止中断(防止中断打断内核操作)

这样可以保证内核数据结构的一致性和安全性。

进程调度的方式

进程调度的方式有两种,非剥夺调度方式和剥夺调度方式。

  • 非剥夺调度方式(非抢占式): 只允许进程主动放弃处理机。
  • 剥夺调度方式(抢占式): 当一个进程正在处理机上执行时,发现一个更重要的程序需要使用处理机,则立即暂停正在执行的程序,将处理机分配给更紧迫的那个进程。
进程的切换与过程

进程的切换指操作系统把 CPU 使用权 从一个进程转移到另一个进程。

本质:保存当前进程的执行现场(上下文),再恢复另一个进程的上下文。

进程切换 = 保存当前进程现场 → 更新 PCB → 调度新进程 → 恢复新进程现场

注意,进程切换是有代价的,因此如果过于频繁的进行调度,切换,必然使系统的效率降低。

调度器,闲逛进程

调度器是 操作系统内核里的一个模块,专门负责 决定哪个进程获得 CPU 使用权。负责挑选进程 → 谁上 CPU。

闲逛进程:当“没有人可挑”的时候,调度器就让闲逛进程上 CPU。

2.2.3 调度的目标

调度的目标(调度算法的评价指标)有CPU利用率,系统吞吐量,周转时间,等待时间,响应时间。

指标含义目标(越好越怎样)适用场景
CPU 利用率CPU 忙碌时间 ÷ 总时间越高越好批处理系统
系统吞吐量单位时间内完成的作业数越大越好批处理系统
周转时间作业提交 → 完成的总时间越短越好批处理系统
等待时间作业在就绪队列里等待 CPU 的时间越短越好批处理系统、交互式系统
响应时间从用户提交请求 → 系统首次响应的时间越短越好交互式系统

2.2.4 调度算法

调度算法大致可以分为两类:

  1. 适用于早期批处理系统(作业调度)的调度算法
    这类算法主要关心对用户的公平性,如平均周转时间、平均等待时间等评价系统整体性能的指标,对于用户来说,交互性很差。典型算法包括:
    • FCFS (先来先服务)
    • SJF (短作业优先)
    • SRTN (最短剩余时间优先)
    • HRRN (高响应比优先)
调度算法算法思想算法规则调度类型是否抢占优点缺点是否会导致饥饿
FCFS (先来先服务)按到达顺序执行先到先服务,按到达时间排队执行作业调度 / 进程调度非抢占实现简单、公平短作业可能等待时间长
SJF (短作业优先)优先执行执行时间短的作业按作业/进程长度排序,最短先执行作业调度 / 进程调度非抢占平均周转时间最短可能饿死长作业,对执行时间估计要求高
SRTN (最短剩余时间优先)抢占式 SJF当前作业剩余时间比新到作业长时抢占作业调度 / 进程调度抢占平均周转时间更短,响应快可能饿死长作业,切换频繁
HRRN (高响应比优先)优先执行响应比高的作业响应比 = (等待时间 + 服务时间)/服务时间,选择最大者作业调度 / 进程调度非抢占兼顾短作业和长作业,避免饥饿计算响应比稍复杂
  1. 适用于交互式系统(进程调度)的调度算法
    这类算法更注重系统的响应时间,公平性,平衡性等指标,典型算法包括:
  • RR (时间片轮转)
  • 优先级调度
  • 多级反馈队列调度
调度算法算法思想算法规则调度类型是否抢占优点缺点是否会导致饥饿
RR (时间片轮转)轮流分配 CPU 时间每个作业按时间片轮流执行,时间片用完换下一个进程调度抢占公平,响应时间好,适合分时操作系统时间片太大退化为 FCFS,太小切换开销大
优先级调度优先级高的作业先执行根据作业或进程优先级选择下一个执行作业调度 / 进程调度可抢占可以保证重要作业先执行低优先级可能长期等待
多级反馈队列多个队列,不同优先级和时间片,动态调整作业可在队列间移动,长作业逐渐降低优先级进程调度抢占兼顾响应时间和公平性,适合混合负载实现复杂

2.2.5 多处理机调度

image-20250819183112860

在多处理机应该追求的目标: 负载均衡处理机亲和

  • 负载均衡:尽可能让每个CPU都同等忙碌
  • 处理机亲和性: 尽量让一个进程调度到同一个CPU上运行,让发挥CPU发中缓存的作用。

解决负载均衡和亲和性有两种方案。

方案一 公共有序队列
  • 所有CPU共享同一个就绪进程队列(位于内核区)

  • 每个CPU时运行调度程序,从公共就绪队列中选择一个进程与运行。

  • 每个CPU访问公共就绪队列时需要上锁(确保互斥)

  • 优点:可以天然的实现负载均衡

  • 缺点:各个进程频繁切换CPU,‘亲和性’不好。

❓如何解决亲和性问题呢?

软亲和:进程尽可能在原 CPU 上运行,但可以迁移到其他 CPU

硬亲和:进程必须在指定 CPU 上运行,不允许迁移

方案二 私有就绪队列
  • 每个CPU都有一个私有就绪队列

  • CPU空闲时运行调度程序,从私有就绪队列中选择一个进程运行。

  • 优点:可以天然的实现亲和性

  • 缺点:初始负载不均衡时,可能出现某些 CPU 队列空闲、某些 CPU 队列繁忙。

❓如何解决队列空闲或者队列繁忙呢?

推迁移(push) : 当发现别的CPU空闲,而一个很忙,就会从忙碌的CPU中推一些给空闲的CPU的就绪队列中。

拉迁移(pull) : 当发现CPU负载很低,就会从高负载的CPU的就绪队列中拉一些进程到自己的就绪队列。

image-20250819190641935


2.3 进程同步与互斥

2.3.1 互斥和同步的基本概念

1. 进程互斥(Mutual Exclusion)

概念

  • 当多个进程需要访问同一共享资源(如内存数据、文件、打印机等)时,为了防止同时访问导致数据混乱,需要确保在同一时间只有一个进程访问该资源
  • 这个保证“同一时刻只有一个进程访问资源”的机制,就叫互斥

进程互斥经典的临界区互斥模型,是由进入区,临界区,退出区,剩余区实现的,需要遵循空闲让进,忙则等待,有限等待,让权等待,这四个原则。

举例

  • 两个进程同时要修改银行账户余额,如果不加互斥,可能出现“钱多扣少加”的错误。互斥就保证了每次只有一个进程能修改账户余额。

2. 进程同步(Process Synchronization)

概念

  • 当多个进程之间存在执行顺序依赖时,需要保证进程按照某种顺序执行,这种协调机制叫同步
  • 简单来说,同步是为了顺序正确,互斥是为了防止资源冲突

举例

  • 生产者-消费者问题:
    • 生产者先生产数据,再让消费者消费;
    • 消费者必须等到有数据才能消费。
  • 这里就是同步:保证先后顺序正确。
特性进程互斥进程同步
目的防止共享资源冲突保证执行顺序正确
关注点同一时间访问冲突先后执行顺序
常用机制锁、信号量、临界区信号量、条件变量、消息队列
举例银行账户操作生产者-消费者问题

image-20250820162347481

2.3.2 互斥的实现方法

软件实现方法
  • 单标志法:

算法思想:两个进程在访问完临界区后会把使用临界区的权限交给另一个进程,也就是说每个进程进入临界区的权限只能被另一个进程赋予

image-20250820163857169

但是如果此时轮到P0进程使用,但P0一直不使用(占着茅坑不拉屎),而P1要用却进不去了。

所以违背了“空闲让进”原则

  • 双标志先检查:

算法思想:设置一个布尔数组flag[],数组中各个元素用来标记个进程想进入临界区的意愿。flag[0] = true 表示P0进程想进入临界区,flag[1] = false 表示P1进程不想进入临界区。

image-20250820164558320

但是进入区的检查和上锁两个处理不是一气呵成的,先检查后,上锁前可能会发生进程切换。此时就是同时访问临界区了。

所以违背了“忙则等待”原则

  • 双标志后检查:

算法思想: 双标志先检查的改版,因为上一个算法是先检查后上锁,无法一气呵成,所以人们又想到了先上锁后检查

image-20250820170549132

虽然解决了"忙则等待"的问题,但是很有可能会发生一直卡在2 6 代码处,两个进程都想进入临界区,但是谁也不让谁,会因各进程都长期无法访问临界资源产生"饥饿"。

所以又**违背了"空闲让进"和"有限等待"**的原则。

  • Peterson 算法:

结合双标志法,单标志法的思想,如果双方都争着想进入临界区,那可以让进程尝试"孔融让梨" 做一个有礼貌的进程。

image-20250820171615508

这个算法遵循了空闲让进,忙则等待,有限等待 三个原则,但是它如果进不了临界区,它会一直卡在while循环,一直在CPU上执行,占用CPU资源。

所以违背了“让权等待”的原则。

image-20250820182111043

硬件实现方法
  • 中断屏蔽方法:

利用"开/关中断指令"实现,强制的,不允许发生进程切换,因此不会发生两个同时访问临界区的情况。

image-20250820183530045

优点:简单,高效

缺点:不适用于多处理机,只适用于操作系统内核进程,不适用于用户进程(用户随便开关中断指令,很危险)

  • TestAndSet(TS指令/TSL指令)

简称TS指令,这个指令使用硬件实现的,执行的过程不允许中断,只能一气呵成。

image-20250820184614962

这个和Peterson算法一样,违背了**“让权等待”**的原则。

image-20250820185014239

这个和TS指令的思路几乎一致,缺点也一致。

image-20250820185322588

2.3.3 互斥锁

互斥锁的目的

  • 核心目标:保护 临界区(Critical Section),防止多个进程/线程同时修改共享资源而产生 竞态条件(Race Condition)
  • 实现方式:通过锁机制,让一次只有一个进程进入临界区。

自旋锁(Spinlock)

自旋锁,是互斥锁的一种实现方式,尤其适合 多处理器系统

特点:

  1. 忙等待:如果锁被占用,线程 不会被挂起,而是在 CPU 上循环检查锁是否释放 → 这叫“自旋”。
  2. 低延迟:多核系统下,如果锁很快释放,线程能立即进入临界区,比阻塞锁(挂起等待)效率高。
  3. 占用 CPU:自旋等待期间,CPU 一直在检查锁,浪费 CPU,不适合长时间持锁。

使用场景:

  • 多核 CPU,临界区很短 → 自旋锁很快就能获取锁
  • 临界区很长或单核 CPU → 不推荐自旋锁,应该使用阻塞锁
特性自旋锁阻塞锁(传统互斥锁)
CPU 消耗会占用 CPU(忙等待)不占用 CPU(挂起等待)
延迟低,锁释放立即获取高,线程被挂起和唤醒有开销
使用场景多核、临界区短单核或临界区长
实现基于原子操作(TSL/Swap)基于操作系统调度

2.3.4 信号量

信号量机制是一种由操作系统提供的 进程同步与互斥机制,用来协调多个进程对共享资源的访问。

信号量其实就是一个变量,一对原语wait(S)signal(S)括号里的信号量S其实就是函数调用时传入的一个参数。 wait和signal也简称P,V操作P(S),V(S);

信号量就是一个整数 + 两个操作(P/V),用来解决进程的 互斥同步 问题,是操作系统里非常经典的同步原语。

整形信号量

用一个整数型的变量作为信号量,用来表示系统中某种资源的数量

image-20250821154038596

它当S为0的时候就会一直等待,所以不满足"让权等待"会发生忙等。

记录型信号量

整形信号量的缺陷是存在忙等,因此人们又提出了’记录型信号量’,即用记录型数据结构表示的信号量

image-20250821155414529

S.value 的初值表示系统中某种资源的数目,当发现S.value<0时表示该资源已分配完毕,因此程序调用block原语进行自我阻塞,主动放弃了处理机,并插入等待队列。所以遵循了“让权等待”原则,不会出现”忙等“现象。

利用信号量实现进程同步:

✅ 信号量的两个操作

  • P 操作(wait)
    • 先执行 S = S - 1
    • 如果结果 < 0 → 说明没有资源,进程阻塞,进入等待队列。
    • 如果结果 ≥ 0 → 说明有资源,进程继续执行。

👉 所以 P 是执行前 --,再检查能不能继续

  • V 操作(signal)
    • 先执行 S = S + 1
    • 如果结果 ≤ 0 → 说明有进程在等,唤醒一个阻塞的进程。
    • 如果结果 > 0 → 说明没人在等,直接结束。

👉 所以 V 是执行时 ++,然后可能会唤醒别人

image-20250821165900527

image-20250821170817323

2.3.5 进程同步与互斥经典问题

1. 生产者消费问题

生产者,消费者共享一个初始为空,大小为n的缓冲区。

  • 只有缓冲区没满时候,生产者才能把产品放入缓冲区,否则必须等待。
  • 只有缓冲区不空时候,消费者才能从中取出产品,否则必须等待。
  • 缓冲区是临界资源,各进程必须互斥的访问。

image-20250821175601337

这是一个 生产者-消费者问题同步 + 互斥(要先生产后消费,同时不能多个进程一起写缓冲区)。

2. 多生产者多消费问题

本质和单个生产者/消费者 一模一样,区别是有多个进程在同时生产或消费。

image-20250822155133036

多生产者-多消费者问题:也是同步 + 互斥,只是进程更多。

3. 读者写者问题

有读者和写者两组并发进程,共享一个文件。

  • 允许多个读者可以同时对文件执行读操作。
  • 只允许一个写者往文件中写信息
  • 任一写者在完成写操作之前不允许其他读者或者写者工作
  • 写者执行写操作前,应让已有的读者和写者全部退出。

image-20250822163314231

但是如果源源不断的来读,一直有人读进程,可是只有conut = 0 时,也就是最后一个人读完后在可以解锁文件,写进程一直无法唤醒,就会造成’饥饿’的现象。

此时新增了一个 w 变量即可实现 “写优先”

image-20250822164756429

读者-写者问题互斥(写独占)+ 条件同步(第一个读者/最后一个读者控制写)

4. 哲学家进餐问题

场景

  • 5 个哲学家围着桌子坐,每人面前有一只筷子。
  • 吃饭要两只筷子(左+右)。
  • 如果大家都先拿左手筷子,就会 死锁(没人能吃)。

信号量设计

  • chopstick[i] = 1(第 i 只筷子)
  • 每个哲学家必须同时申请两只筷子才能吃。

如果哲学家同时拿筷子,就会出现死锁问题

image-20250823090631238

避免死锁的方法

  • 限制最多 4 个哲学家同时拿筷子。
  • 或者规定奇数号哲学家先拿左,偶数号先拿右

image-20250823090431224

哲学家进餐问题 = 死锁产生 + 死锁预防的经典案例

2.3.6 管程

信号量机制实现进程同步和互斥,编写程序困难,易出错,所以引入了“管程”这种高级同步机制。

管程是一种特殊的软件模块

  1. 局部于管程的共享数据结构说明
  2. 对该数据结构进行操作的一切过程
  3. 对局部于管程的共享数据设置初始值的语句。
  4. 管程有一个名字

有点像C++中的类

管程的基本特征

  1. 局部于管程的数据只能被局部于管程的过程所访问
  2. 一个进程只有通过调用管程内的过程才能进入管程访问共享数据
  3. 每次仅允许一个进程在管程内执行某个内部过程。
  4. 管程内部自动保证互斥,不用程序员自己写 P/V

管程 = “自带锁的类” + “条件变量”,自动实现互斥与同步。

特性信号量(Semaphore)管程(Monitor)
定位低级同步原语,需要手动管理高级同步工具,自动管理
使用方式通过 P()V() 控制访问通过调用管程内过程访问
互斥实现需要程序员显式用互斥信号量保护管程内部自动互斥
同步实现通过信号量计数实现通过条件变量 wait()/signal()
难度容易出错(P/V顺序、死锁)简单、安全
语言支持OS级特性,偏底层高级语言直接支持(Java synchronized,C++条件变量等)

2.4 死锁

2.4.1 死锁的概念

死锁是各进程互相等待对方手里的资源,导致进程全部阻塞,无法向前推进。

死锁,饥饿,死循环的共同点都是进程无法顺利向前推进(故意设计的死循环除外)

名称产生原因是否永远无法结束典型场景
死锁多个进程/线程相互等待资源,形成环路,无法推进。(无外力不解开)哲学家进餐、互斥锁环等
饥饿调度策略不公平,某进程长期得不到资源不一定(可能很久才执行)优先级调度低优先级任务长期不执行
死循环程序逻辑错误,循环条件永远为真(除非人为打断)while(1) {}

关键点:

  • 必须是多个进程/线程参与。
  • 每个进程都持有部分资源,又请求其他资源,形成循环等待
  • 一旦进入死锁状态,没有外部干预无法自行解开

经典例子:
两个进程A和B:

  • A占有资源R1,请求R2;
  • B占有资源R2,请求R1;
    二者相互等待,导致系统“卡死”。
死锁必要条件含义预防方法(破坏条件)
互斥条件资源一次只能被一个进程占用尽量使用可共享资源(如只读文件、SPOOLing假脱机技术)
占有且等待条件(请求和保持)进程持有资源的同时还申请新资源一次性申请全部资源先释放再申请
不可剥夺条件已占有的资源不能强制剥夺允许资源被强制剥夺(如超时回收)
循环等待条件存在资源等待环资源有序分配法(按序号申请,避免环路)

这四个条件必须同时满足,才可能发生死锁。

只要有一个条件不满足,就一定不会发生死锁。

满足这四个条件 ≠ 一定死锁,只是具备了发生死锁的可能。

死锁的处理策略

  1. 预防死锁。破坏死锁产生的四个必要条件中的一个或者几个。
  2. 避免死锁。用某种方法防止系统进入不安全状态,从而避免死锁(银行家算法)
  3. 死锁的检测和解除。允许死锁的发生,不过操作系统会负责检测出死锁的发生,然后采取某种措施解除死锁。

2.4.2 预防死锁

破坏互斥条件:

互斥条件:只有对必须互斥使用的资源争抢才会导致死锁。

image-20250825083022463

缺点:并不是所有的资源都可以造成可共享的资源,并且为了系统安全,很多地方还必须保护这种互斥性。因此很多时候都无法破坏互斥条件。

破坏不剥夺条件:

不剥夺条件:进程所获得的资源在未使用前,不能由其他进程强行夺走,只能主动释放。

我们可以利用时间片轮转,或者调度优先级的方式破坏不剥夺条件。

缺点:

  1. 实现起来比较复杂
  2. 释放的已获得的资源,有可能造成前一阶段的资源失效。
  3. 反复的申请和释放资源会增加系统开销,降低性能。

破坏请求和保持条件

请求和保持条件:进程在运行过程中已经保持了至少一个资源,需要申请新的资源,但是所需资源被其他进程占有,所以自己就会阻塞,但又对自己所占有的资源保持不放

可以采取静态分配方法,就是进程在开始运行前,必须一次性申请完它所需的全部资源,在它的资源未满足之前,不能让它运行,一但运行起来,所占用的资源必须等待进程结束,才可以归还出去。

缺点:有些资源只需要占用很短的时间,但却一直被占用,所以会导致很严重的资源浪费,还会导致出现饥饿现象。

破坏循环等待条件

在系统中存在一个进程—资源的环形等待链,即:

  • 进程 P1 等待 P2 占用的资源,
  • P2 等待 P3 占用的资源,
  • ……
  • Pn 又等待 P1 占用的资源,
    形成一个首尾相连的等待环路

例子:

  • P1 占用 R1,等待 R2
  • P2 占用 R2,等待 R3
  • P3 占用 R3,等待 R1
    → 三个进程互相等待,谁都无法推进,系统陷入死锁。

预防方法:

  • 资源有序分配法:为资源统一编号,进程申请资源必须按编号递增(防止环路形成)。

缺点: 不方便增加新的设备,会导致资源浪费;用户编程麻烦。

2.4.3 避免死锁

银行家算法(Banker’s Algorithm)是操作系统中避免死锁的一种经典算法,由Dijkstra提出。它通过 “安全状态” 判断是否分配资源,保证系统始终不会进入死锁状态。


1. 基本思想

  • 系统在每次分配资源时,都先预判此次分配是否会让系统进入不安全状态(可能死锁)。
  • 如果分配后系统仍然有可能让所有进程顺利完成,则允许分配;否则拒绝本次分配,让进程等待。
  • 类比“银行贷款”:银行只在保证最终能让所有客户都能还款的情况下才会放贷。

2. 算法所需数据结构

设系统有n个进程,m种资源:

  • Available[m]:当前可用的各类资源数。
  • Max[n][m]:每个进程对各类资源的最大需求。
  • Allocation[n][m]:当前已分配给每个进程的资源数。
  • Need[n][m]:每个进程还需的资源数 = Max - Allocation

3. 工作流程

当一个进程请求资源时(Request[i]):

  1. 检查合法性
    • Request[i] <= Need[i]Request[i] <= Available,继续;否则非法请求。
  2. 试分配
    • 先假设把资源分配给它:
      • Available = Available - Request[i]
      • Allocation[i] = Allocation[i] + Request[i]
      • Need[i] = Need[i] - Request[i]
  3. 安全性检查
    • 判断系统是否处于安全状态(能否按某个顺序让所有进程执行完释放资源)。
    • 若安全 → 真正分配;
    • 若不安全 → 恢复原状态,让进程等待。

4. 特点

  • 优点:能避免死锁发生。
  • 缺点:需要事先知道每个进程的最大资源需求,开销较大,不适用于资源动态变化频繁的系统。

如果系统处于安全状态,就一定不会发生死锁。如果系统进入不安全状态,就可能发生死锁。

处于不安全状态未必就是发生了死锁,但发生了死锁一定是在不安全状态。

4.4.4 检测和解除

死锁的检测

当系统不采取预防或避免措施时,需要定期或在资源不足时进行死锁检测。

资源分配图(RAG)法(适用于每类资源只有一个实例)

  • 图中两类节点
    • 进程节点(圆形)
    • 资源节点(方形)
  • 两类边
    • 请求边(P → R)
    • 分配边(R → P)

  • 检测方法
    • 在资源分配图中找是否有
    • 有环 ⇒ 必然死锁(单实例资源)。

我们首先将图中即不阻塞,也不是孤点(至少有一条边)的进程,然后消去它所有的请求边和分配边,使之称为孤点,然后再找下一个不阻塞也不是孤点的点,依次重复,则称改图是可完全简化,否则就会产生死锁。


死锁的解除

一旦系统检测到死锁,常用的解决方法有:

  1. 资源剥夺法(Resource Preemption)
    • 挂起(暂时阻塞)某些死锁进程,并强制回收其占用的资源,分配给其他需要的进程。
    • 缺点:可能导致饥饿,且状态恢复复杂。
  2. 进程撤销法(Process Termination)
    • 强制终止一个或多个死锁进程,释放其资源。
    • 策略:
      • 一次性终止所有死锁进程(简单粗暴,代价高)。
      • 按优先级、运行代价等因素逐个终止,直到死锁解除。
  3. 进程回退法(Rollback)
    • 系统在运行过程中为进程设置检查点,一旦发生死锁,回退到某个安全状态重新运行。
    • 缺点:实现复杂,需要额外存储和恢复机制。
http://www.dtcms.com/a/352516.html

相关文章:

  • 简明 | Yolo-v3结构理解摘要
  • Python-机器学习概述
  • ruoyi-vue(十二)——定时任务,缓存监控,服务监控以及系统接口
  • Python 轻量级的 ORM(对象关系映射)框架 - Peewee 入门教程
  • CentOS 7 升级 OpenSSH 10.0p2 完整教程(含 Telnet 备份)
  • 性能瓶颈定位更快更准:ARMS 持续剖析能力升级解析
  • 告别繁琐运维,拥抱自动化:EKS Auto Mode 实战指南
  • C代码学习笔记(二)
  • RK3506 开发板:嵌入式技术赋能多行业转型升级
  • 大数据时代UI前端的智能化升级路径:基于用户行为数据的预测性分析
  • PMP项目管理知识点-⑨项⽬资源管理
  • 大模型应用编排工具Dify之插件探索
  • 【LeetCode - 每日1题】求对角线最长矩形的面积
  • Claude 的优势深度解析:大模型竞争格局中的隐藏护城河
  • NX773HSA19美光固态闪存D8BJND8BJQ
  • inline内联函数
  • TensorFlow 深度学习:使用 feature_column 训练心脏病分类模型
  • 【软考论文】论可观测性架构技术的应用
  • 【资源】Github资源整理
  • C6.3:发射结交流电阻
  • Vue3 + Element Plus实现表格多行文本截断与智能Tooltip提示
  • 【黑客技术零基础入门】2025最新黑客工具软件大全,零基础入门到精通,收藏这篇就够了!
  • 【数据结构】单链表详解
  • Java基础 8.26
  • 【7】SQL 语句基础应用
  • 基于SpringBoot的演唱会网上订票系统的设计与实现(代码+数据库+LW)
  • 自由学习记录(89)
  • 一份兼容多端的HTML邮件模板实践与详解
  • 美妆品牌如何用 DAM 管理海量产品图片?
  • 开脑洞,末日降临,堡垒求生,ARMxy系列BL410能做什么?