linux进程间通信——信号量(通俗易懂,看这一篇就够了)

信号量

概念

特点

  • 信号量实际是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储 进程间通信 数据。很多进程会访问同一资源,或者向共享内存写入一些东西,为防止争夺资源混乱。可以给一些进程上锁,让其排队等待

工作原理

  1. P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
  2. V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

在信号量进行PV操作时都为原子操作(因为它需要保护临界资源)

注:原子操作:单指令的操作称为原子的,单条指令的执行是不会被打断的

步骤:

  1. 创建或者使用信号灯集
  2. 对某信号灯做PV操作(P减法,V加法)
  3. 删除信号灯集(非必须)

创建/获取信号量集

semphore get

功能

  • 创建一个新信号量或获取一个已有信号量

函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
  • key:唯一非零的整数值(和共享内存中的参数定义一致,key不变可以获取相同的信号灯集)
    • IPC_PRIVATE
    • ftok()
  • nsems:创建的信号量的个数(PV操作的钥匙个数)
  • semflg:一组标志
    • IPC_CREAT | 0666:当信号量不存在时创建一个新的信号量,并指定权限
    • IPC_CREAT | IPC_EXCL | 0666:创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

返回值

  • 成功返回一个相应信号量集的标识号(非负整数)
  • 失败返回-1,并设置 errno

注意事项

  • nsems当前信号集中的信号量的个数,不是value值,semid是用于标识这一组信号量(即标识信号量集)

  • 示例:

    semget(key, 2, IPC_CREAT | 0666);
    
    • 创建一个信号量集,里面包含两个信号量,一个下标为0,另一个下标为1
    • 初始的value值都为0(没有通过semctl()修改),所以一旦先执行P操作,就会阻塞。

semget error: Invalid argument

  • 原因:semget之前创建的信号量集未删除(即nsms不一致)

    image-20240902214210359

    image-20240902214248861

  • 解决:使用命令ipcrm -s semid(semid为具体的信号量集标识符)删除系统中的信号量集,重新创建

使用命令查看信号量

ipcs -s # # interprocess communication status——进程间通信的状态 s——semaphore  
image-20240902095853401

PV操作

功能

  • 对某些信号量做 P(减法)和 V(加法)操作

函数原型

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

int semop(int semid, struct sembuf *sops, size_t nsops);
  • semid:信号量集标识号,semget()的返回值

  • sops:一个指向struct sembuf类型的指针

    struct sembuf
    {
        unsigned short int sem_num;	// 信号量组中对应的序号,0~sem_nums-1
        short int sem_op;	 		// 信号量值在一次操作中的改变量 +5(V操作) -5(P操作)
        short int sem_flg;			/* operation flag */
    };
    
    • sem_flg:0 阻塞 IPC_NOWAIT 不阻塞
  • nsops:操作的信号灯个数

返回值

  • 成功返回0
  • 失败返回-1,并设置 errno

示例1

struct sembuf ss1 = {0, -1, 0};
struct sembuf ss1 = {-1, -2, 0};
struct sembuf arr[2] = {ss1, ss2};
semop(semid, arr, 2); // semid为具体的信号灯标识号

示例2

struct sembuf ss = {0, -5, 0};
semop(semid, &ss, 1);

删除信号量集

功能

  • 对信号量集进行控制

函数原型

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

int semctl(int semid, int semnum, int cmd, ...);
  • semid:信号量集的标识号

  • semnum:信号量集中的某个信号量的下标

  • cmd

    • SETVAL:设置某个信号量的 value 值(这个值通过union semun中的val成员设置)

      • union semum结构体

        union semun {
            int              val;    /* 用于 SETVAL 的值 */
            struct semid_ds *buf;    /* 用于 IPC_STAT、IPC_SET 的缓冲区 */
            unsigned short  *array;  /* 用于 GETALL、SETALL 的数组 */
            struct seminfo  *__buf;  /* 用于 IPC_INFO(Linux 特定)的缓冲区 */
        };
        
      • 示例:

        union semun us;
        us.val = 5;
        semctl(semid, 0, SETVAL, us, NULL); // 并没有要求必须以NULL结尾
        
    • GETVAL:获取到 某个信号量的 value 值

      • 示例:

        semctl(semid, 0, GETVAL, NULL);
        
    • IPC_RMID:删除整个信号量集,不需要缺省参数,只需要三个参数即可

      • 示例:

        semctl(semid, 0, IPC_RMID, NULL);
        

返回值

  • 成功,根据cmd返回一个非负值
    • 对于SETVALGETVALIPC_RMID返回0代表成功
  • 失败,返回-1,并设置errno

linux union semun报错storage size isn‘t known

  • 原因:内核中的union sem_union联合体被注释

  • 解决:自己实现

    #if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
    /* union semun is defined by including <sys/sem.h> */
    #else
    /* according to X/OPEN we have to define it ourselves */
    union semun
    {
        int              val;   /* value for SETVAL */
        struct semid_ds* buf;   /* buffer for IPC_STAT, IPC_SET */
        unsigned short*  array; /* array for GETALL, SETALL */
        /* Linux specific part: */
        struct seminfo* __buf; /* buffer for IPC_INFO */
    };
    #endif
    

使用命令删除信号量集

ipcrm -s # interprocess communication remove	s——semphore

综合示例

semget_p

概述:

  1. 通过调用ftok函数,创建唯一键值
  2. 创建/返回信号量集(初始时value为0)
  3. 修改value值为5
  4. 执行p操作,需要value值为6,不够,故阻塞
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>

#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
/* union semun is defined by including <sys/sem.h> */
#else
/* according to X/OPEN we have to define it ourselves */
union semun
{
    int              val;   /* value for SETVAL */
    struct semid_ds* buf;   /* buffer for IPC_STAT, IPC_SET */
    unsigned short*  array; /* array for GETALL, SETALL */
    /* Linux specific part: */
    struct seminfo* __buf; /* buffer for IPC_INFO */
};
#endif

int main(int argc, char* argv[])
{
    key_t          key;
    int            semid;
    struct sembuf* sops;
    int            ret;

    // 创建key
    if (-1 == (key = ftok("../ftok.txt", 7)))
    {
        perror("ftok error");
        exit(-1);
    }
    printf("key:%#x\n", key);

    // 创建信号量集,当前集合中有3个信号量,下标分别为012,初始value为0
    if (-1 == (semid = semget(key, 3, IPC_CREAT | 0666)))
    {
        perror("semget error");
        exit(-1);
    }
    printf("semid:%d\n", semid);

    // V操作, 下标0,增加1个value值,0表示阻塞
    struct sembuf ss = {0, 1, 0};
    if (-1 == (ret = semop(semid, &ss, 1)))
    {
        perror("semop error");
        exit(-1);
    }
    printf("I am back--v\n");

    // 删除整个信号灯集
    semctl(semid, 0, IPC_RMID, NULL);

    return 0;
}

semget_v

概述:

  1. 通过调用ftok函数,创建唯一键值
  2. 创建/返回信号量集(初始时value为0)
  3. 修改value值为5(二次修改,会立即生效,即使此时P操作还在阻塞)
    • 如果直接修改value值为6,此时P操作(需要value值为5)会立刻结束阻塞
  4. 执行V操作,释放一个value值,P操作结束阻塞
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>

#if defined(__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
/* union semun is defined by including <sys/sem.h> */
#else
/* according to X/OPEN we have to define it ourselves */
union semun
{
    int              val;   /* value for SETVAL */
    struct semid_ds* buf;   /* buffer for IPC_STAT, IPC_SET */
    unsigned short*  array; /* array for GETALL, SETALL */
    /* Linux specific part: */
    struct seminfo* __buf; /* buffer for IPC_INFO */
};
#endif

int main(int argc, char* argv[])
{
    key_t          key;
    int            semid;
    struct sembuf* sops;
    int            ret;
    union semun    us;

    // 创建key
    if (-1 == (key = ftok("../ftok.txt", 7)))
    {
        perror("ftok error");
        exit(-1);
    }
    printf("key:%#x\n", key);

    // 创建信号量集,当前集合中有3个信号量,下标分别为012,初始value为0
    if (-1 == (semid = semget(key, 3, IPC_CREAT | 0666)))
    {
        perror("semget error");
        exit(-1);
    }
    printf("semid:%d\n", semid);

    // 修改信号量的value值(修改后,会立即剩下,即使p操作还在阻塞状态)
    us.val = 5;
    // 并没有要求必须以NULL结尾
    if (0 != semctl(semid, 0, SETVAL, us, NULL))
    {
        perror("semctl error");
        exit(-1);
    }

    // V操作, 下标0,增加1个value值,0表示阻塞
    struct sembuf ss = {0, 1, 0};
    if (-1 == (ret = semop(semid, &ss, 1)))
    {
        perror("semop error");
        exit(-1);
    }
    printf("I am back--v\n");

    // 删除整个信号灯集
    semctl(semid, 0, IPC_RMID, NULL);

    return 0;
}
posted @ 2024-09-02 22:15  YOLO_01  阅读(1855)  评论(0)    收藏  举报