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

信号量#

概念#

特点

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

工作原理

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

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

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

步骤:

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

创建/获取信号量集#

semphore get

功能

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

函数原型

Copy
#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是用于标识这一组信号量(即标识信号量集)

  • 示例:

    Copy
    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为具体的信号量集标识符)删除系统中的信号量集,重新创建

使用命令查看信号量

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

PV操作#

功能

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

函数原型

Copy
#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类型的指针

    Copy
    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

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

示例2

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

删除信号量集#

功能

  • 对信号量集进行控制

函数原型

Copy
#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结构体

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

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

      • 示例:

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

      • 示例:

        Copy
        semctl(semid, 0, IPC_RMID, NULL);

返回值

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

linux union semun报错storage size isn‘t known

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

  • 解决:自己实现

    Copy
    #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

使用命令删除信号量集

Copy
ipcrm -s # interprocess communication remove s——semphore

综合示例#

semget_p

概述:

  1. 通过调用ftok函数,创建唯一键值
  2. 创建/返回信号量集(初始时value为0)
  3. 修改value值为5
  4. 执行p操作,需要value值为6,不够,故阻塞
Copy
#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操作结束阻塞
Copy
#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 @   YOLO_01  阅读(631)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
CONTENTS