前言

  • 原文链接
  • 知识点
    • 消息队列、信号量共享内存 被统称为 system-V IPC

      • 以上都是“持续性”资源,即它们被创建之后, 不会因为进程的退出而消失
  • 说明:
    • 以下 信号量 如无说明,均为 system-V IPC 信号量

5. 信号量

5.1 概念

  • 信号量

    • 信号量可以理解为一个计数器
    • 主要用于保护资源
  • 原子操作:单指令的操作称为原子操作,单条指令的执行时不会被打断的

5.2 工作原理

  • 信号量就是两种操作:P 操作和 V 操作

    • P 操作:就是申请资源,资源 -1
    • V 操作:就是释放资源,资源 +1
    • 资源为 0 时,无资源申请

5.3 操作函数

  • 使用 semget() 创建或获取一个信号量
  • 使用 semop() 进行 PV 操作
  • 使用 semctl() 进行一系列控制操作

5.3.1 semget()

  • 使用 semget() 创建或获取一个信号量
  • 通过命令 man 了解更多
  • 函数原型:int semget(key_t key, int nsems, int semflg);
    • key:信号量键值,可以自定义一个键值,也可以使用 IPC_PRIVATE 创建一个没有 key 的信号量
    • nsems信号量数目
    • semflg:表示创建的信号量的模式标志参数,主要有IPC_CREAT,IPC_EXCL和权限mode,如:
      • IPC_CREAT:没有关键字 key 的信号量就新建一个,有就直接打开
      • IPC_CREAT | IPC_EXCL:信号量不存在,则新建一个,如果信号量存在,则报错
      • IPC_CREAT | 0666:(注:信号量不在意执行权限)
    • 返回:
      • 成功:返回信号量标识符
      • 失败:返回 -1,原因记录在变量 error
        • EACCES:没有访问该信号量集的权限
        • EEXIST:信号量集已经存在,无法创建
        • EINVAL:参数nsems的值小于0或者大于该信号量集的限制;或者是该key关联的信号量集已存在,并且nsems大于该信号量集的信号量数
        • ENOENT:信号量集不存在,同时没有使用IPC_CREAT
        • ENOMEM :没有足够的内存创建新的信号量集
        • ENOSPC:超出系统限制
  • 创建信号量也受下面值限制:(通过命令 ipcs -l 可查)
    • SEMMNI:系统中信号量总数的最大值
    • SEMMSL:每个信号量中信号量元素个数的最大值
    • SEMMNS:系统中所有信号量中的信号量元素总数的最大值。

5.3.2 semop()

  • 使用 semop() 进行 PV 操作
  • 通过命令 man 了解更多
  • 函数原型:int semop(int semid, struct sembuf *sops, size_t nsops);
    • semid:信号量标识符
    • sops:指向存储信号操作结构的数组指针,信号操作结构的原型如下:
      1. struct sembuf
      2. {
      3. unsigned short int sem_num; /* 信号量的序号从0 ~ nsems-1 */
      4. short int sem_op; /* 对信号量的操作,>0, 0, <0 */
      5. short int sem_flg; /* 操作标识:0, IPC_WAIT, SEM_UNDO */
      6. };
      • sem_num:标识信号量中第几个信号量,从 0 开始
      • sem_op:对信号量所进行的操作类型
        • 0:V 操作(回收资源),把 sem_op 的值加到该信号量的信号量当前值 semval 上

        • = 0:表示进程要阻塞等待,直至信号量当前值 semval 变为 0
          • 如果没有设置 IPC_NOWAIT ,则调用该操作的进程或者线程将暂时睡眠,直到信号量的值为0
          • 如果设置 IPC_NOWAIT,则进程或者线程不会睡眠,函数返回错误EAGAIN
        • < 0:P 操作(申请资源),如果其绝对值大于信号值 semval ,则操作会阻塞;直到信号值 semval 大于等于 sem_op 的绝对值
    • nsops:信号操作标志
      • 0:正常操作
      • SEM_UNDO:程序结束时(不论正常或异常),保证信号值会被重设为 semop() 调用前的值。目的是避免资源永远锁定。
      • 信号操作结构的数量,恒大于或等于1
    • 返回:
      • 成功:返回 0
      • 失败:返回 -1,原因记录在变量 error
        • E2BIG:一次对信号的操作数超出系统的限制
        • EACCES:调用进程没有权能执行请求的操作,并且不具有CAP_IPC_OWNER权能
        • EAGAIN:信号操作暂时不能满足,需要重试
        • EFAULT:sops或timeout指针指向的空间不可访问
        • EFBIG:sem_num指定的值无效
        • EIDRM:信号集已被移除
        • EINTR:系统调用阻塞时,被信号中断
        • EINVAL:参数无效
        • ENOMEM:内存不足
        • ERANGE:信号所允许的值越界

5.3.3 semctl()

  • 使用 semctl() 进行一系列控制操作
  • 通过命令 man 了解更多
  • 函数原型:int semctl(int semid, int semnum, int cmd, ...);
    • semid:System V信号量的标识符
    • semnum:表示信号量集中的第 semnum 个信号量。它的取值范围: 0 ~ nsems-1
    • cmd:操作命令,主要有以下命令:
      • IPC_STAT:获取此信号量集合的semid_ds结构,存放在第四个参数的buf中
      • IPC_SET:通过第四个参数的buf来设定信号量集相关联的semid_ds中信号量集合权限为sem_perm中的uid,gid,mode
      • IPC_RMID:从系统中删除该信号量集合
      • GETVAL:返回第semnum个信号量的值
      • SETVAL:设置第semnum个信号量的值,该值由第四个参数中的val指定
      • GETPID:返回第semnum个信号量的sempid,最后一个操作的pid
      • GETNCNT:返回第semnum个信号量的semncnt。等待semval变为大于当前值的线程数
      • GETZCNT:返回第semnum个信号量的semzcnt。等待semval变为0的线程数
      • GETALL:去信号量集合中所有信号量的值,将结果存放到的array所指向的数组
      • SETALL:按arg.array所指向的数组中的值,设置集合中所有信号量的值
    • 第四个参数为一个可选的联合体:
      1. union semun {
      2. int val; /* Value for SETVAL */
      3. struct semid_ds *buf; /*Buffer for IPC_STAT, IPC_SET*/
      4. unsigned short *array; /*Array for GETALL, SETALL*/
      5. struct seminfo *__buf; /*Buffer for IPC_INFO (Linux-specific)*/
      6. };

5.4 例程

  • 只有一个资源
  • 等待子进程释放了资源后,父进程才继续往下执行
  • 信号量操作封装文件

  1. #include <sys/sem.h>
  2. #include <sys/ipc.h>
  3. #include <unistd.h>
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7. #include <sys/shm.h>
  8. #include <sys/stat.h>
  9. #include <fcntl.h>
  10. #include <errno.h>
  11. #include "sem.h"
  12. /**
  13. * @brief 初始化第一个信号量的资源数
  14. * @param sem_id:信号量标识符
  15. * @param init_value:初始化值
  16. */
  17. int init_sem(int sem_id, int init_value)
  18. {
  19. union semun sem_union;
  20. sem_union.val = init_value; /*init_value 为初始值*/
  21. if (semctl(sem_id, 0, SETVAL, sem_union) == -1)
  22. {
  23. perror("Initialize semaphore");
  24. return -1;
  25. }
  26. return 0;
  27. }
  28. /**
  29. * @brief 从系统中删除信号量的函数
  30. * @param sem_id:信号量标识符
  31. */
  32. int del_sem(int sem_id)
  33. {
  34. union semun sem_union;
  35. if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
  36. {
  37. perror("Delete semaphore");
  38. return -1;
  39. }
  40. }
  41. /**
  42. * @brief 对第一个信号量进行 P 操作
  43. * @param sem_id:信号量标识符
  44. */
  45. int sem_p(int sem_id)
  46. {
  47. struct sembuf sops;
  48. sops.sem_num = 0; /* 单个信号量的编号应该为 0 */
  49. sops.sem_op = -1; /* 表示 P 操作 */
  50. sops.sem_flg = SEM_UNDO; /* 若进程退出,系统将还原信号量*/
  51. if (semop(sem_id, &sops, 1) == -1)
  52. {
  53. perror("P operation");
  54. return -1;
  55. }
  56. return 0;
  57. }
  58. /**
  59. * @brief 对第一个信号量进行 V 操作
  60. * @param sem_id:信号量标识符
  61. */
  62. int sem_v(int sem_id)
  63. {
  64. struct sembuf sops;
  65. sops.sem_num = 0; /* 单个信号量的编号应该为 0 */
  66. sops.sem_op = 1; /* 表示 V 操作 */
  67. sops.sem_flg = SEM_UNDO; /* 若进程退出,系统将还原信号量 */
  68. if (semop(sem_id, &sops, 1) == -1)
  69. {
  70. perror("V operation");
  71. return -1;
  72. }
  73. return 0;
  74. }
  • 信号量demo APP
  1. #include <sys/types.h>
  2. #include <sys/shm.h>
  3. #include <sys/sem.h>
  4. #include <sys/ipc.h>
  5. #include <unistd.h>
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8. #include <string.h>
  9. #include <sys/stat.h>
  10. #include <fcntl.h>
  11. #include <errno.h>
  12. #include "sem.h"
  13. #define DELAY_TIME 5 /* 子进程释放信号量延时时间 */
  14. int main(void)
  15. {
  16. pid_t result;
  17. int sem_id;
  18. sem_id = semget((key_t)6666, 1, 0666 | IPC_CREAT); /* 创建一个信号量*/
  19. init_sem(sem_id, 0); // 初始化已创建的信号量的资源数为0
  20. /*调用 fork()函数*/
  21. result = fork();
  22. if(result == -1)
  23. {
  24. perror("Fork\n");
  25. }
  26. else if (result == 0) /*返回值为 0 代表子进程*/
  27. {
  28. printf("Child process will wait for %d seconds...\n", DELAY_TIME);
  29. sleep(DELAY_TIME);
  30. printf("The returned value is %d in the child process(PID = %d)\n",result, getpid());
  31. sem_v(sem_id); // V 操作,释放资源
  32. }
  33. else /*返回值大于 0 代表父进程*/
  34. {
  35. sem_p(sem_id); // 申请资源
  36. printf("The returned value is %d in the father process(PID = %d)\n",result, getpid());
  37. sem_v(sem_id); // 释放资源
  38. del_sem(sem_id); // 删除信号量
  39. }
  40. exit(0);
  41. }

参考:

  1. * 野火

【linux】系统编程-3-system-V IPC 信号量的更多相关文章

  1. linux进程间通讯-System V IPC 信号量

    进程间通信的机制--信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的很多其它内容,能够阅读我的还有一篇文章:Linux进程间通信--使用信号.以下就进入信号量的 ...

  2. linux系统编程之(一) 信号量

    信号量 一.什么是信号量 信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)所拥有. 信号量的值为正的时候,说明它空闲.所测试的线程可以锁定而使用它.若为0,说明 它被占用,测 ...

  3. linux c编程:System V消息队列一

    消息队列可以认为是一个消息链表,System V 消息队列使用消息队列标识符标识.具有足 够特权的任何进程都可以往一个队列放置一个消息,具有足够特权的任何进程都可以从一个给定队列读出一个消息.在某个进 ...

  4. System V IPC 之信号量

    本文继<System V IPC 之共享内存>之后接着介绍 System V IPC 的信号量编程.在开始正式的内容前让我们先概要的了解一下 Linux 中信号量的分类. 信号量的分类 在 ...

  5. 线程同步、信号量、system v IPC

    一.线程同步 条件变量 什么是条件变量? 线程A等待某个条件成立,条件成立,线程A才继续向下执行.线程B的执行使条件成立,条件成立以后唤醒线程A,以继续执行.这个条件就是条件变量. pthread_c ...

  6. 从并发处理谈PHP进程间通信(二)System V IPC

    .container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .conta ...

  7. Linux 系统编程 学习:04-进程间通信2:System V IPC(1)

    Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...

  8. Linux 系统编程 学习:05-进程间通信2:System V IPC(2)

    Linux 系统编程 学习:05-进程间通信2:System V IPC(2) 背景 上一讲 进程间通信:System V IPC(1)中,我们介绍了System IPC中有关消息队列.共享内存的概念 ...

  9. Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道

    Linux 系统编程 学习:02-进程间通信1:Unix IPC(1)管道 背景 上一讲我们介绍了创建子进程的方式.我们都知道,创建子进程是为了与父进程协作(或者是为了执行新的程序,参考 Linux ...

  10. Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号

    Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...

随机推荐

  1. web development all in one

    web development all in one https://javascript.xgqfrms.xyz/web-development-all-in-one.html refs https ...

  2. iPad pro & 显示器

    iPad pro 显示器 iPad Pro 如何当做外接屏幕使用 XDisplay https://www.splashtop.com/wiredxdisplay https://play.googl ...

  3. .NET微服务最佳实践 eShopOnContainers

    本文翻译自微软Docs, 内嵌译者多年使用的参悟,如理解有误,请不吝赐教. 微软与社区专家合作,开发了功能齐全的云原生微服务示例应用eShopOnContainers. 该应用旨在展示使用.NET.D ...

  4. [转]ROS学习笔记十一:ROS中数据的记录与重放

    本节主要介绍如何记录一个正在运行的ROS系统中的数据,然后在一个运行的系统中根据记录文件重新产生和记录时类似的运动情况.本例子还是以小海龟例程为例. 记录数据(创建一个bag文件) 首先运行小海龟例程 ...

  5. 线上CPU飙升100%问题排查

    本文转载自线上CPU飙升100%问题排查 引子 对于互联网公司,线上CPU飙升的问题很常见(例如某个活动开始,流量突然飙升时),按照本文的步骤排查,基本1分钟即可搞定!特此整理排查方法一篇,供大家参考 ...

  6. 解决margin-top无效问题

    当两个空的块级元素嵌套时,如果内部的块设置有margin-top属性,而且父元素没有下边解决方法所述的特征,那么内部块的margin-top属性会绑架父元素(即将margin-top传递凌驾给了父元素 ...

  7. Warning: Cannot update during an existing state transition (such as within `render`). Render 报错

    原来 修改(不用在构造函数里面定义)

  8. JUnit5学习之四:按条件执行

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  9. 学习笔记-python基础

    一. 1.python按装 1.1 官网 https://www.python.org 1.2 点 downloads下的 windows下载64位python3.7.3版本 Download Win ...

  10. Oracle RMAN scripts to delete archivelog

    vi del_arch.shexport ORACLE_SID=pdcsdbrman target / cmdfile=/home/oracle/scripts/del_arch.sql log=/h ...