《Linux系统编程篇》System V信号量(Linux 进程间通信(IPC))——基础篇

文章目录

    • 引言
    • ‍ **Linux 中的 System V 信号量:基础与实战**
      • **System V 信号量简介**
      • **关键概念**
      • ️ **System V 信号量的相关函数**
      • **函数原型**
        • 1. `semget()` — 创建或获取信号量集
        • 2. `semop()` — 执行信号量操作
        • 3. `semctl()` — 控制信号量集
        • 4. `semctl()` — 删除信号量集
      • **示例:基本的信号量操作**
      • **解析**
      • **进阶实例:生产者-消费者问题**
      • **总结:**

切勿好高骛远,任何大事,都是一砖一瓦垒起来的 ——家驹(StrangeHead)

引言

上一篇文章我们知道了什么是信号,以及如何使用POSIX信号量,什么是P操作,什么是V操作,了解过Linux进化史的小伙伴就知道,POSIX是Unix的一个标准规范(Unix及其衍生系统遵循的一系列标准的集合),而System V也只不过是别家公司基于Linux写的另一套信号量罢了。回顾一下上一节的知识,接下来这一节对你来说简直是轻松驾驭!

Linux 中的 System V 信号量:基础与实战

在并发编程中,尤其是在多进程环境下,资源共享是一个常见问题。为了避免多个进程同时访问某个共享资源,导致数据不一致或系统崩溃,System V 信号量应运而生!信号量是一种原始的同步机制,可以在多个进程间进行通信和控制资源访问。


System V 信号量简介

System V 信号量(sem_t)是 Unix/Linux 系统中一种进程间同步和互斥的机制。它们通常用于解决 互斥(保证一次只有一个进程访问资源)和 同步(控制多个进程的执行顺序)问题。

关键概念

  1. 信号量集:每个信号量都是一个整数,表示某一资源的可用数量。多个信号量可以组合在一起形成一个信号量集。
  2. P 操作(等待):用于请求资源,减少信号量。如果信号量为 0,进程会阻塞,直到资源可用。
  3. V 操作(释放):用于释放资源,增加信号量,并唤醒一个被阻塞的进程。

System V 信号量的相关函数

系统提供了几个关键的函数来操作信号量。它们分别是:

  1. semget() – 获取信号量集的标识符。
  2. semctl() – 控制信号量集的属性。
  3. semop() – 执行信号量操作(P 操作和 V 操作)。
  4. semctl() – 删除信号量集。

函数原型

1. semget() — 创建或获取信号量集
int semget(key_t key, int nsems, int semflg);

示例:

int sem_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);

这将创建一个包含 1 个信号量的信号量集,并将其标识符保存在 sem_id 中。信号量集权限为 0666,表示所有用户均可读写。

  • key: 信号量集的标识符,通常用 IPC_PRIVATE 表示创建一个新的信号量集,或者指定一个特定的键值。
  • nsems: 信号量集的数量,即信号量的个数。
  • semflg: 权限标志,通常使用 IPC_CREAT 来创建一个新的信号量集,或者使用 IPC_EXCL 保证不会与已有的信号量集冲突。

IPC_CREAT:创建一个新的信号量集。
IPC_EXCL:如果信号量集已经存在,返回错误。
0666:信号量集的权限,类似文件权限。

  • 返回值:返回一个信号量集的标识符。
  • 失败时,返回 -1,并设置 errno。
2. semop() — 执行信号量操作
int semop(int semid, struct sembuf *sops, size_t nsops);

示例:

struct sembuf sops;
sops.sem_num = 0;   // 第一个信号量
sops.sem_op = -1;   // P 操作(减 1,表示请求资源)
sops.sem_flg = 0;

if (semop(sem_id, &sops, 1) == -1) {
    perror("semop");
    exit(1);
}

该代码会让当前进程等待,直到第一个信号量的值大于 0。

  • semid: 信号量集的标识符。

  • sops: 指向一个 struct sembuf 数组的指针,表示一系列信号量操作。每个 sops 包含:

    • sem_num:操作的信号量编号,表示要操作的信号量。

    • sem_op:操作类型:

      • -1:P 操作,减少信号量(等待资源)。
      • 1:V 操作,增加信号量(释放资源)。
    • sem_flg:通常为 0,表示没有额外标志。

  • nsops: 操作的数量。

  • 返回值:成功时返回 0,失败时返回 -1。

  • 失败时,返回 -1,并设置 errno。

3. semctl() — 控制信号量集
int semctl(int semid, int semnum, int cmd, ...);

示例:

// 设置信号量的初始值
if (semctl(sem_id, 0, SETVAL, 1) == -1) {
    perror("semctl");
    exit(1);
}

// 获取信号量的值
int val = semctl(sem_id, 0, GETVAL);
if (val == -1) {
    perror("semctl");
    exit(1);
}
printf("信号量值为:%d\n", val);
  • semid: 信号量集标识符。
  • semnum: 信号量的编号。如果是对整个信号量集进行操作,可以传入 0。
  • cmd: 控制命令,常用命令有:
    • IPC_RMID:删除信号量集。
    • GETVAL:获取信号量当前的值。
    • SETVAL:设置信号量的值。

返回值:根据命令不同,返回不同的值。

  • 根据命令不同,返回不同的值。例如,SETVAL 返回旧的信号量值,GETVAL 返回信号量的当前值。
4. semctl() — 删除信号量集

通过 semctl()IPC_RMID 命令删除信号量集。


示例:基本的信号量操作

我们通过一个简单的程序来演示如何创建信号量、执行 P 操作、V 操作,以及如何删除信号量集。

#include 
#include 
#include 
#include 
#include 

int main() {
    // 创建一个信号量集,包含 1 个信号量
    int sem_id = semget(IPC_PRIVATE, 1, IPC_CREAT | 0666);
    if (sem_id == -1) {
        perror("semget");
        exit(1);
    }

    // 初始化信号量为 1
    if (semctl(sem_id, 0, SETVAL, 1) == -1) {
        perror("semctl");
        exit(1);
    }

    struct sembuf sops;
    sops.sem_num = 0;   // 使用第一个信号量
    sops.sem_flg = 0;   // 不使用其他标志位

    // P 操作:请求资源,信号量减 1
    sops.sem_op = -1;  // 这里是 P 操作(信号量减 1)
    if (semop(sem_id, &sops, 1) == -1) {
        perror("semop");
        exit(1);
    }

    printf("进程 %d 获取资源,开始工作...\n", getpid());
    sleep(2);  // 模拟处理资源的时间

    // V 操作:释放资源,信号量加 1
    sops.sem_op = 1;   // 这里是 V 操作(信号量加 1)
    if (semop(sem_id, &sops, 1) == -1) {
        perror("semop");
        exit(1);
    }

    printf("进程 %d 释放资源,结束工作!\n", getpid());

    // 删除信号量集
    if (semctl(sem_id, 0, IPC_RMID) == -1) {
        perror("semctl");
        exit(1);
    }

    return 0;
}

解析

  1. semget():创建一个信号量集,信号量的数量为 1(即一个资源)。
  2. semctl():初始化信号量的值为 1,表示资源初始状态为可用。
  3. semop():执行 P 操作,使得进程占用资源。若信号量为 0,进程将被阻塞。
  4. sleep(2):模拟进程使用资源的时间。
  5. semop():执行 V 操作,释放资源,将信号量值加 1,唤醒其他等待的进程。
  6. semctl():删除信号量集,释放资源。

进阶实例:生产者-消费者问题

在这个经典问题中,生产者不断生产资源,消费者不断消费资源,我们使用信号量来控制生产者和消费者的同步。

下一节我们使用System V来实现这个进阶实例


总结:

System V 信号量是 Linux 系统中提供的一种强大的并发控制工具。通过 semget() 创建信号量集,利用 semop() 执行操作,使用 semctl() 控制信号量的值,进程间可以高效地同步和互斥。对于初学者来说,理解信号量的基本操作和应用场景(如生产者-消费者问题)是学习多进程编程的重要一步。

你可能感兴趣的:(从0开始学linux,linux)