📜  进程间通信-信号量(1)

📅  最后修改于: 2023-12-03 14:57:57.346000             🧑  作者: Mango

进程间通信-信号量

在多进程编程中,进程间通信(IPC,Inter-Process Communication)是一种重要的实现方式。信号量是一种常用的进程间通信机制,它可以用来同步不同进程之间的行为和互斥访问共享资源。

什么是信号量?

在操作系统中,信号量是一个计数器,它的值用来同步进程和线程对共享资源的访问。当一个进程或线程对共享资源进行访问时,它会尝试对信号量进行操作,从而获得对共享资源的控制权。如果信号量的值大于0,那么进程或线程可以访问共享资源;如果信号量的值等于0,那么进程或线程需要等待其他进程或线程释放信号量,才能访问共享资源。

信号量可以有两种类型:二进制信号量(Binary Semaphore)和计数信号量(Counting Semaphore)。二进制信号量只具有两个可能的值,通常为0和1,用于实现进程之间的互斥访问共享资源。计数信号量可以具有多个可能的值,用于实现进程之间的同步和协调。

信号量的使用方法

在C语言中,信号量的使用可以通过操作系统的IPC机制调用实现。在Linux中,信号量是通过系统调用semctl、semop、semget来实现的。

semget函数

semget函数用于创建一个新的信号量,或获取一个已经存在的信号量。函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);

参数key用于指定信号量的键值,通常使用ftok函数来生成。参数nsems用于指定需要创建/获取的信号量个数;参数semflg则用于指定信号量的创建/获取方式。

如果成功创建/获取信号量,则semget函数返回一个非负整数值,即信号量集合的标识符;如果失败,函数返回-1。

semctl函数

semctl函数用于进行信号量集合上的控制操作。函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ...);

参数semid用于指定信号量集合的标识符;参数semnum用于指定需要操作的信号量在信号量集合中的下标(从0开始);参数cmd用于指定需要执行的控制操作,可能的参数值有:

  • SETVAL:设置信号量的值;
  • GETVAL:获取信号量的值;
  • IPC_RMID:删除信号量集合。

如果执行成功,semctl函数返回相应的值;如果执行失败,函数返回-1。

semop函数

semop函数用于进行对信号量的操作。函数原型如下:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);

参数semid用于指定信号量集合的标识符;参数sops是一个指向sembuf结构体的指针,而sembuf结构体是信号量操作的定义。参数nsops用于指定需要执行操作的信号量数量。

sembuf结构体定义如下:

struct sembuf {
    short sem_num;  // 信号量在信号量集合中的下标(从0开始)
    short sem_op;   // 信号量的操作值,可以为正值、零或负值
    short sem_flg;  // 信号量的操作标志
};

其中,sem_op的取值可以为正值、零或负值。当sem_op为正值时,表示对信号量的值进行增加;当sem_op为零时,表示不对信号量进行操作,仅用于同步和协调;当sem_op为负值时,表示对信号量的值进行减少。sem_flg用于指定信号量的操作标志,可以为IPC_NOWAIT或SEM_UNDO。

例如,当需要对一个信号量集合中的第0个信号量进行减1的操作,可以定义一个sembuf结构体:

struct sembuf sb = {0, -1, SEM_UNDO};

然后调用semop函数进行操作:

int res = semop(semid, &sb, 1);

如果执行成功,semop函数返回0;如果执行失败,函数返回-1。

信号量的实例

下面是一个简单的利用信号量实现进程同步的例子,其中包含了信号量的创建、设置、操作和删除等操作。该例子中创建了三个进程,分别代表甲、乙、丙三个人,三个人同时从门口进入一个房间,但每次只能有一人通过。程序中使用了二进制信号量来控制进程操作。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define SEM_KEY 12345   // 信号量键值

int main(int argc, char *argv[]) {
    // 创建二进制信号量
    int semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("Error creating semaphore");
        exit(EXIT_FAILURE);
    }
    
    // 初始化信号量的值为1
    semctl(semid, 0, SETVAL, 1);
    
    // 创建子进程
    pid_t pid = fork();
    if (pid == -1) {
        perror("Error forking process");
        exit(EXIT_FAILURE);
    }
    
    if (pid == 0) {
        // 甲进入房间
        struct sembuf sb = {0, -1, SEM_UNDO};
        semop(semid, &sb, 1);
        printf("Process A enters the room.\n");
        sleep(2);
        printf("Process A leaves the room.\n");
        sb.sem_op = 1;
        semop(semid, &sb, 1);
        exit(EXIT_SUCCESS);
    } else {
        pid = fork();
        if (pid == -1) {
            perror("Error forking process");
            exit(EXIT_FAILURE);
        }
        
        if (pid == 0) {
            // 乙进入房间
            struct sembuf sb = {0, -1, SEM_UNDO};
            semop(semid, &sb, 1);
            printf("Process B enters the room.\n");
            sleep(2);
            printf("Process B leaves the room.\n");
            sb.sem_op = 1;
            semop(semid, &sb, 1);
            exit(EXIT_SUCCESS);
        } else {
            // 丙进入房间
            struct sembuf sb = {0, -1, SEM_UNDO};
            semop(semid, &sb, 1);
            printf("Process C enters the room.\n");
            sleep(2);
            printf("Process C leaves the room.\n");
            sb.sem_op = 1;
            semop(semid, &sb, 1);
            // 删除信号量
            semctl(semid, 0, IPC_RMID);
            exit(EXIT_SUCCESS);
        }
    }
    
    return 0;
}
结语

信号量是一种常用的进程间通信机制,它可以用于同步不同进程之间的行为和互斥访问共享资源。本文介绍了信号量的定义、使用方法和一些注意事项,希望对读者在编写多进程程序时有所帮助。