Skip to content
EN

#系统分析师

信号量与 PV 操作

整型信号量(Integer Semaphore)和PV操作是操作系统中用于进程同步和互斥的重要概念。它们由荷兰计算机科学家 Edsger Dijkstra 提出,主要用于解决并发进程中的竞争条件和资源分配问题。

1. 整型信号量

整型信号量是一个整数变量,通常用于表示系统中可用资源的数量。信号量的值可以是正数、零或负数:

  • 正数:表示系统中可用的资源数量。
  • :表示没有可用资源,但没有进程在等待。
  • 负数:表示没有可用资源,且绝对值为等待该资源的进程数量。

信号量的操作通常通过两个原子操作来实现:P操作(等待操作)和V操作(释放操作)。


2. P操作(Wait操作)

P操作是信号量的减操作,用于申请资源。其伪代码如下:

c
P(Semaphore S) {
    S.value--; // 申请资源,信号量减1
    if (S.value < 0) {
        // 如果信号量为负,表示资源不足,当前进程进入等待队列
        block(); // 阻塞当前进程
    }
}
  • 如果信号量的值大于0,表示有可用资源,进程可以继续执行。
  • 如果信号量的值小于0,表示资源不足,当前进程会被阻塞,直到有资源可用。

3. V操作(Signal操作)

V操作是信号量的加操作,用于释放资源。其伪代码如下:

c
V(Semaphore S) {
    S.value++; // 释放资源,信号量加1
    if (S.value <= 0) {
        // 如果信号量小于等于0,表示有进程在等待资源
        wakeup(); // 唤醒一个等待的进程
    }
}
  • 如果信号量的值小于等于0,表示有进程在等待资源,唤醒一个等待的进程。
  • 如果信号量的值大于0,表示没有进程在等待资源,直接释放资源。

4. PV操作的应用

PV操作通常用于解决以下问题:

  1. 互斥问题:确保多个进程不会同时访问共享资源。

    • 初始化信号量为1。
    • 在进入临界区前执行P操作。
    • 在离开临界区后执行V操作。
  2. 同步问题:确保进程按照特定顺序执行。

    • 初始化信号量为0。
    • 一个进程执行P操作等待另一个进程完成某些操作。
    • 另一个进程完成操作后执行V操作,唤醒等待的进程。

5. 示例

假设有两个进程A和B,需要互斥访问共享资源:

c
Semaphore S = 1; // 初始化信号量为1

Process A() {
    P(S); // 申请资源
    // 访问共享资源
    V(S); // 释放资源
}

Process B() {
    P(S); // 申请资源
    // 访问共享资源
    V(S); // 释放资源
}
  • 进程A和B通过PV操作实现了对共享资源的互斥访问。

6. 总结

  • 整型信号量是一个整数变量,用于表示资源的可用数量。
  • P操作用于申请资源,V操作用于释放资源。
  • PV操作是解决进程同步和互斥问题的核心工具。

例二

PV操作的缺点明显

  • 编程难度大。通信对用户不透明,需要用户利用第几通信工具实现进程间的同步和互斥。容易引起死锁
  • 效率低,如上述案例,每次只能传递一个产品

为降低编程难度、提高编程效率,产生了高级通信原语

高级通信原语

高级通信机制是操作系统中用于进程间通信(IPC, Inter-Process Communication)的几种方式,主要包括共享存储模式消息传递模式管道通信。这些机制与PV操作(基于信号量的同步机制)不同,它们更侧重于进程间的数据交换和协作。


1. 共享存储模式(Shared Memory)

共享存储模式允许多个进程共享一块内存区域,进程可以直接读写这块内存中的数据,从而实现高效的数据交换。

特点:

  • 高效:进程直接访问共享内存,无需通过内核进行数据复制。
  • 需要同步:由于多个进程可以同时访问共享内存,因此需要使用同步机制(如信号量)来避免竞争条件。
  • 适合大数据量通信:适合需要频繁交换大量数据的场景。

实现步骤:

  1. 创建一个共享内存区域。
  2. 进程将共享内存映射到自己的地址空间。
  3. 进程通过读写共享内存进行通信。
  4. 使用同步机制(如PV操作)确保数据一致性。

示例:

  • 在Linux中,可以使用shmgetshmat等系统调用来实现共享内存。

2. 消息传递模式(Message Passing)

消息传递模式通过操作系统提供的消息队列或直接发送消息的方式,实现进程间的数据交换。

特点:

  • 解耦:发送方和接收方不需要直接共享内存,通信通过内核完成。
  • 同步或异步:可以是阻塞的(同步)或非阻塞的(异步)。
  • 适合小数据量通信:适合需要传递少量数据的场景。

实现方式:

  1. 直接通信:发送方和接收方直接通过系统调用发送和接收消息。
    • 发送方:send(receiver, message)
    • 接收方:receive(sender, message)
  2. 间接通信:通过消息队列(Message Queue)进行通信。
    • 发送方:send(queue, message)
    • 接收方:receive(queue, message)

示例:

  • 在Linux中,可以使用msggetmsgsndmsgrcv等系统调用来实现消息队列。

3. 管道通信(Pipe)

管道是一种半双工的通信机制,允许两个进程通过一个共享的缓冲区进行数据交换。管道通常用于父子进程或兄弟进程之间的通信。

特点:

  • 半双工:数据只能单向流动(一个进程写,另一个进程读)。
  • 基于字节流:数据以字节流的形式传输,没有消息边界。
  • 适合父子进程通信:常用于命令行中的管道操作(如ls | grep)。

实现方式:

  1. 创建一个管道(使用pipe()系统调用)。
  2. 父进程创建子进程(使用fork())。
  3. 父进程和子进程分别关闭不需要的管道端(读端或写端)。
  4. 通过管道进行数据交换。

示例:

c
int fd[2];
pipe(fd); // 创建管道

if (fork() == 0) { // 子进程
    close(fd[1]); // 关闭写端
    read(fd[0], buffer, sizeof(buffer)); // 从管道读取数据
} else { // 父进程
    close(fd[0]); // 关闭读端
    write(fd[1], "Hello", 6); // 向管道写入数据
}

4. 对比

通信机制共享存储模式消息传递模式管道通信
数据交换方式直接访问共享内存通过消息队列或直接发送通过管道缓冲区
同步需求需要同步机制通常不需要额外同步通常不需要额外同步
适用场景大数据量、高效通信小数据量、解耦通信父子进程通信
复杂性较高(需管理共享内存)中等较低

5. 总结

  • 共享存储模式:适合大数据量通信,但需要同步机制。
  • 消息传递模式:适合解耦的进程通信,支持同步和异步。
  • 管道通信:适合父子进程之间的简单通信。

这些高级通信机制与PV操作(基于信号量的同步机制)不同,它们更侧重于进程间的数据交换,而PV操作主要用于解决进程间的同步和互斥问题。