软考(软件设计师)进程管理—进程基本概念,信号量与PV操作

发布于:2025-07-07 ⋅ 阅读:(19) ⋅ 点赞:(0)

一、什么是进程?

  • 定义: 程序的一次执行实例。程序是静态的指令集合(存储在磁盘上的文件),进程是动态的、活动的实体(加载到内存中运行)。
  • 关键组成:
    • 代码段 (Text Segment): 要执行的机器指令。
    • 数据段 (Data Segment): 全局变量、静态变量。
    • 堆 (Heap): 动态分配的内存(如 malloc/new)。
    • 栈 (Stack): 函数调用时的局部变量、返回地址、参数等。
    • 进程控制块 (PCB): 操作系统管理进程的核心数据结构,存储进程的所有关键信息(状态、寄存器值、PID、资源列表、调度信息等)。每个进程都有唯一的PCB。在这里插入图片描述

二、进程状态与转换

进程在其生命周期中会经历不同的状态。经典的五状态模型如下:

图例2:进程状态转换图

在这里插入图片描述

  1. 新建 (New): 进程刚被创建(如用户运行命令 ./my_program),操作系统分配PCB和初始资源。
  2. 就绪 (Ready): 进程已获得除CPU外的所有必要资源,等待被CPU调度执行。就绪队列中有多个进程排队。
  3. 运行 (Running): 进程正在CPU上执行指令。单核CPU任一时刻只有一个进程处于此状态。
  4. 阻塞/等待 (Blocked/Waiting): 进程因等待某个事件(如I/O操作完成、信号量、子进程结束)而主动放弃CPU。即使CPU空闲也不会运行。
  5. 终止 (Terminated): 进程执行完毕或被强制终止。操作系统回收其资源(内存、文件描述符等),PCB可能保留一段时间供父进程查询。在这里插入图片描述

挂起(Suspend)

  • 目的: 当系统物理内存不足时,操作系统需要将暂时不需要运行的进程的整个内存映像(代码、数据、堆、栈)移出物理内存,保存到外存(如磁盘的交换区/Swap Space) 中,以腾出空间给其他急需内存的进程使用。这个过程称为换出 (Swap Out) 或被挂起 (Suspend)

  • 效果: 被挂起的进程不在物理内存中。因此,即使它处于“逻辑上”就绪或阻塞的状态,也无法被立即调度运行(因为CPU无法直接访问外存中的指令和数据)。它必须先被换入 (Swap In) 回物理内存。

  • 触发条件: 通常由操作系统内核的中程调度器 (Medium-Term Scheduler) 负责决定何时挂起哪些进程。常见触发点包括:

    • 系统整体内存紧张。

    • 需要为高优先级进程腾出内存。

    • 用户或系统管理员显式挂起一个进程(如 Ctrl+Z 发送 SIGTSTP 信号给前台进程)。

    • 进程等待一个预计会非常长时间才能发生的事件(如等待用户输入但用户长时间无操作)。


理解了“挂起”后,我们就能定义这四种状态了:

  1. 活跃就绪 (Ready / Active Ready)

    • 定义: 进程在物理内存中,并且具备运行的所有条件(除了CPU时间片)。它位于内存的就绪队列中,等待调度器选择它运行。

    • 状态转换来源:

      • 新建进程初始化完成。

      • 运行进程时间片用完或被更高优先级进程抢占。

      • 阻塞进程等待的事件发生。

      • 静止就绪进程被换入内存。

    • 状态转换去向:

      • 被调度器选中 -> 运行

      • 被操作系统挂起(换出内存) -> 静止就绪

    • 特点: 这是标准五状态模型中的“就绪”状态。进程随时可以被调度运行。

  2. 静止就绪 (Ready, Suspended / Suspended Ready)

    • 定义: 进程在外存(交换区)中,但其PCB信息(包括状态)表明它逻辑上是就绪的。也就是说,如果它被换入内存,它就能立即进入活跃就绪队列等待CPU

    • 状态转换来源:

      • 活跃就绪进程被挂起(换出内存)

      • 静止阻塞进程等待的事件发生(事件发生时,它还在外存)。

    • 状态转换去向:

      • 被操作系统换入内存 -> 活跃就绪

      • (理论上也可能被终止,但通常挂起是为了恢复)

    • 特点:

      • 进程不在内存,无法被调度运行

      • 当内存空闲或该进程优先级高时,会被换入变成活跃就绪。

      • 是“逻辑就绪,物理缺席”的状态。

  3. 活跃阻塞 (Blocked / Active Blocked / Waiting)

    • 定义: 进程在物理内存中,但正在等待某个事件的发生(如I/O完成、信号量、子进程结束等)。它位于内存的阻塞队列(按等待事件分组)中。

    • 状态转换来源:

      • 运行进程主动请求等待资源/事件(如发起I/O调用)。
    • 状态转换去向:

      • 等待的事件发生 -> 活跃就绪

      • 被操作系统挂起(换出内存) -> 静止阻塞

    • 特点: 这是标准五状态模型中的“阻塞”状态。进程在内存中,但CPU不会选择它运行,直到事件发生。

  4. 静止阻塞 (Blocked, Suspended / Suspended Blocked)

    • 定义: 进程在外存(交换区)中,并且其PCB信息表明它逻辑上仍在等待某个事件。即使事件发生了,它也无法立即响应,必须先被换入内存。

    • 状态转换来源:

      • 活跃阻塞进程被挂起(换出内存)
    • 状态转换去向:

      • 被操作系统换入内存 -> 活跃阻塞(等待的事件可能仍未发生,也可能在换入过程中/后发生)。

      • 等待的事件发生 -> 静止就绪(注意:事件发生时,进程还在外存,所以状态变为静止就绪。当它被换入后,就会进入活跃就绪)。

    • 特点:

      • 进程不在内存,无法运行且仍在等待事件

      • 这是“双重等待”状态:既等待事件,又等待被换入内存。

      • 操作系统通常优先挂起阻塞进程,因为它们反正暂时无法运行,换出它们对系统性能影响相对较小。

在这里插入图片描述

三、进程间通信

一、进程间通信(IPC)与同步互斥的必要性

  1. 进程间通信(IPC)是什么?

    • 指运行在不同进程(有时甚至是同一进程的不同线程)之间的任务交换数据或信息。

    • 进程通常拥有独立的地址空间,一个进程无法直接访问另一个进程的内存。IPC 提供了打破这种隔离的机制。

    • 常见 IPC 方式: 管道、命名管道(FIFO)、消息队列、共享内存、信号量、套接字、RPC 等。

  2. 为什么需要同步互斥?

    • 竞态条件: 当多个进程/线程并发访问和操作共享资源(如共享内存区、文件、打印机、数据库记录)时,如果访问的最终结果取决于进程执行的特定时序,就发生了竞态条件。

    • 临界区: 访问共享资源的代码段称为临界区。

    • 互斥的必要性: 为了避免竞态条件,必须确保一次只有一个进程/线程可以进入其临界区执行。这就是互斥。

    • 同步的必要性: 除了互斥访问资源,进程间有时还需要协调执行顺序。例如,进程 A 必须在进程 B 生产完数据后才能消费该数据。这种在时间上协调进程执行顺序的机制称为同步。

  3. 互斥需要满足的条件

    • 忙则等待: 当临界区已有进程时,其他试图进入的进程必须在循环中等待(忙等)或阻塞(睡眠)。

    • 空闲让进: 当临界区空闲时,应允许一个等待进入的进程立即进入。

    • 有限等待: 一个进程等待进入临界区的时间应该是有限的,不能无限期等待(避免饥饿)。

    • 让权等待(可选但高效): 等待进入临界区的进程应主动释放 CPU 给其他进程运行(避免忙等浪费 CPU)。

二、信号量机制(Semaphore)

信号量是荷兰计算机科学家 Edsger Dijkstra 在 1965 年提出的一种经典的、强大的进程同步与互斥工具。它本质上是一个计数器,结合了等待队列和两个原子操作(P/V 操作,或 Wait/Signal, Down/Up)。

  1. 信号量的结构

    • 整数值S: 表示可用资源的数量或信号量的状态。

    • 等待队列: 当进程因执行 P 操作而阻塞时,会被放入这个队列中休眠。

    • 两个原子操作:

      • P 操作:

        • 将信号量的值减 1。

        • 如果减 1 后信号量值 >= 0,则该进程继续执行。

        • 如果减 1 后信号量值 < 0,则该进程被阻塞(睡眠),并放入与该信号量关联的等待队列中。

      • V 操作 (Signal / Up / Verhogen(荷兰语“增加”)):

        • 将信号量的值加 1。

        • 如果加 1 后信号量值 <= 0,则说明等待队列中有进程在等待,此时唤醒等待队列中的第一个进程(将其移入就绪队列)。

        • 如果加 1 后信号量值 > 0,则没有进程在等待,直接返回。

关键点: P 和 V 操作是原子的!这意味着在执行 P 或 V 操作期间,不会被中断(如被时钟中断或切换到另一个进程),从而保证了操作的完整性,避免了在检查信号量值和修改变量之间发生竞态条件。

在这里插入图片描述

三、PV操作实现进程同步互斥


一、核心概念回顾

  1. 信号量 (Semaphore) S

    • 一个整型变量 S.value:表示资源可用数量或状态。
    • 一个等待队列 S.queue:存储因执行 P 操作而阻塞的进程。
  2. P 操作 (Wait / Down)

    void P(Semaphore S) {
        S.value--;               // 原子地减1
        if (S.value < 0) {       // 资源不足
            block();             // 当前进程阻塞,加入S.queue
        }
    }
    
  3. V 操作 (Signal / Up)

    void V(Semaphore S) {
        S.value++;               // 原子地加1
        if (S.value <= 0) {      // 有进程在等待
            wakeup();            // 从S.queue唤醒一个进程
        }
    }
    

关键特性:P/V 操作是 原子的(执行过程不可中断),由操作系统内核或硬件指令(如 Test-and-Set)保证。


二、实现互斥 (Mutual Exclusion)

目标:确保任一时刻只有一个进程进入临界区(Critical Section)。
工具二元信号量 (Mutex),初始值 S = 1

伪代码
Semaphore mutex = 1; // 初始化为1 (表示临界区空闲)

// 进程Pi
Process Pi() {
    P(mutex);     // 申请锁
    // 临界区代码 (访问共享资源)
    V(mutex);     // 释放锁
    // 剩余区代码
}
工作流程
  1. 进程A 进入临界区
    • P(mutex):
      mutex.value = 1-1 = 0 ≥ 0成功进入
  2. 进程B 尝试进入
    • P(mutex):
      mutex.value = 0-1 = -1 < 0阻塞,加入等待队列。
  3. 进程A 退出临界区
    • V(mutex):
      mutex.value = -1+1 = 0 ≥ 0 → 唤醒进程B。
  4. 进程B 被唤醒
    • P(mutex) 后继续执行 → 进入临界区。
示意图
        进程A                    进程B
       ┌─────────┐             ┌─────────┐
       │ P(mutex)│             │ P(mutex)│
       │ (S=1→0) │             │ (S=0→-1)│ ← 阻塞!
       ├─────────┤             └────┬────┘
       │ 临界区   │                  │
       ├─────────┤                  ▼
       │ V(mutex)│             ┌─────────┐
       │ (S=0→-1)│ ← 唤醒B      │ (阻塞中)│
       └─────────┘             └─────────┘

互斥锁的本质:通过 S=1 表示“钥匙”,P 操作是拿钥匙,V 操作是还钥匙。


三、实现同步 (Synchronization)

目标:协调进程间的执行顺序(如:进程B 必须在 进程A 完成后执行)。
工具同步信号量,初始值 S = 0

场景:进程A 先执行任务X,进程B 后执行任务Y。
Semaphore sync = 0; // 初始化为0 (表示“事件未发生”)

// 进程A
Process A() {
    // 执行任务X
    V(sync);  // 通知B:"X已完成"
}

// 进程B
Process B() {
    P(sync);  // 等待A的通知
    // 执行任务Y
}
工作流程
  1. 若 B 先执行
    • P(sync):
      sync.value = 0-1 = -1 < 0阻塞等待
  2. A 执行任务X
    • 完成后执行 V(sync):
      sync.value = -1+1 = 0 ≥ 0唤醒 B
  3. B 被唤醒
    • 继续执行任务Y。
示意图
        进程A                    进程B
       ┌─────────┐             ┌─────────┐
       │ 执行任务X│             │ P(sync) │
       ├─────────┤             │ (S=0→-1)│ ← 阻塞!
       │ V(sync) │             └────┬────┘
       │ (S=0→-1)│ ← 唤醒B          ▼
       └─────────┘             ┌─────────┐
                               │ 唤醒后   │
                               │ 执行任务Y│
                               └─────────┘

同步信号量的意义S=0 表示“条件不满足”,P 是等待条件,V 是发出信号。


四、综合案例:生产者-消费者问题

问题描述

  • 生产者向**有限缓冲区(Buffer Size=N)**写入数据。
  • 消费者从缓冲区读取数据。
    需满足
  1. 互斥:任一时刻仅一个进程访问缓冲区。
  2. 同步
    • 缓冲区满时,生产者等待 (empty=0);
    • 缓冲区空时,消费者等待 (full=0)。
信号量设计
信号量 含义 初始值
mutex 缓冲区互斥锁 1
empty 空闲缓冲区数量 N
full 已用缓冲区数量 0
伪代码
// 全局定义
Semaphore mutex = 1, empty = N, full = 0;

// 生产者
Producer() {
    while (true) {
        item = produce_item();  // 生产数据
        P(empty);              // 等待空位 (empty--)
        P(mutex);              // 申请缓冲区锁
        buffer.add(item);       // 写入缓冲区
        V(mutex);              // 释放锁
        V(full);               // 增加数据项 (full++)
    }
}

// 消费者
Consumer() {
    while (true) {
        P(full);               // 等待数据 (full--)
        P(mutex);              // 申请缓冲区锁
        item = buffer.remove(); // 取出数据
        V(mutex);              // 释放锁
        V(empty);             // 释放空位 (empty++)
        consume_item(item);     // 消费数据
    }
}
关键点图解
消费者
生产者
通知消费者
通知生产者
P mutex
P full
读缓冲区
V mutex
V empty
消费数据
P empty
生产数据
P mutex
写缓冲区
V mutex
V full
为什么P操作顺序必须是 empty→mutexfull→mutex

若颠倒顺序(如生产者先 P(mutex)P(empty)):

  1. 生产者获锁后,若发现缓冲区满 (empty=0) → 执行 P(empty) 阻塞
  2. 但此时锁未被释放!消费者无法进入临界区取数据 → 死锁

黄金规则

  • 先申请资源信号量 (empty/full),再申请互斥锁 (mutex)
  • 释放时顺序相反:先放锁,再放资源

五、经典同步问题模式

问题类型 信号量设计 关键点
读者-写者 rw_mutex=1 (写互斥) 读者计数 + 写者优先/读者优先
read_count=0 (读者计数)
哲学家就餐 chopstick[5]={1,1,1,1,1} 解决死锁:限人数/按序拿/异步放

六、总结:P/V 操作的核心逻辑

  1. 互斥实现
    • mutex=1 的二元信号量包裹临界区。
  2. 同步实现
    • 前驱关系:用 S=0 的信号量,前驱V后驱P。
    • 资源限制:用 计数信号量(如 empty/full)管理资源数量。
  3. 避免死锁
    • 固定顺序申请多个信号量(如先资源后锁)。
    • 设置等待超时或使用tryP操作。
  4. 原子性保证
    • P/V 操作必须由操作系统内核实现(关中断/硬件指令)。

一句话精髓
P 是等待资源(可能阻塞),V 是释放资源(可能唤醒),信号量是协调进程的“交通灯”。