51.1 进程信号量

51.1.1 信号量

  • 本质上就是共享资源的数目,用来控制对共享资源的访问
  • 用于进程间的互斥和同步
  • 每种共享资源对应一个信号量,为了便于大量共享资源的操作引入了信号量集,可对所有信号量一次性操作。对信号量集中所有操作可以要求全部成功,也可以部分成功
  • 二元信号量(信号灯)值为 0 和 1
  • 对信号量做 PV 操作2

51.1.2 信号量集属性

  

51.1.3 创建信号量集

  

  • 函数参数:

    • key:用户指定的信号量集键值
    • nsems:信号量集中信号量个数
    • semflg:IPC_CREAT,IPC_EXCL 等权限组合
  • 返回值:成功,返回信号量集 ID,出错,返回 -1

51.1.4 信号量集控制

  

  

  

  

  • 函数参数:

    • semid:信号量集 ID
    • semnum:0 表示对所有信号量操作,信号量编号从 0 开始
    • cmd:控制命令,通过 cmd 参数设定对信号量集要执行的操作
      • IPC_STAT:获取信号量集的属性    ---> buf
      • IPC_SET:设置信号量集的属性      ---> buf
      • IPC_RMID:删除信号量集               ---> buf
      • GETVAL:返回信号量的值               ---> val
      • SETVAL:设置 semnum 信号量的值 ---> val
      • GETALL:获取所有信号量的值           ---> arryr
      • SETALL:设置所有信号量的初始值  ---> array
    • arg:即 ... ,semun 联合体变量
      • val:放置获取或设置信号量集中某个信号量的值
      • buf:信号量集属性指针
      • array:放置获取或设置信号量集中所有信号量的值

51.1.5 信号量集操作

  

  

  • 函数参数:

    • semid:信号集 ID
    • sops:sembuf 结构体数组指针
      • sem_num:信号集中信号量的编号
      • sem_op:正数为 V 操作,负数为 P 操作,0 可用于对共享资源是否已用完的测试
      • sem_flg:SEM_UNDO 标识,表示在进程结束时,相应的操作将被取消。如果设置了该标志,那么在进程没有释放共享资源就退出时,内核将代为释放
    • nsops:第二个参数中结构体数组的长度
  • 返回值:成功返回 0;出错返回 -1
  • 其他说明:
    • 用于信号量集中信号量的加和减操作(PV 操作)
    • 可用于进程间的互斥或同步

51.2 信号量例子

51.2.1 PV 操作

(1)PV模块

  sem_pv.h

  1. #ifndef INCLUDE_SEM_PV_H_
  2. #define INCLUDE_SEM_PV_H_
  3.  
  4. #include <sys/sem.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <assert.h>
  8. #include <malloc.h>
  9.  
  10. union semun {
  11. int val;
  12. struct semid_ds *buf;
  13. unsigned short *array;
  14. };
  15.  
  16. /** 初始化 semnums 个信号灯/信号量值(value) */
  17. extern int sem_I(int semnums, int value);
  18.  
  19. /** 对信号量集(semid)中的信号灯(semnum)作 P() */
  20. extern void sem_P(int semid, int semnum, int value);
  21.  
  22. /** 对信号集(semid) 中的信号灯(semnum)作V(value)操作 */
  23. extern void sem_V(int semid, int semnum, int value);
  24.  
  25. /** 销毁信号量集(semid) */
  26. extern void sem_D(int semid);
  27.  
  28. #endif /* INCLUDE_SEM_PV_H_ */

  sem_pv.c

  1. #include "sem_pv.h"
  2.  
  3. /** 初始化 semnums 个信号灯/信号量值(value) */
  4. int sem_I(int semnums, int value)
  5. {
  6. /** 创建信号量集 */
  7. int semid;
  8. /** 创建信号量集 */
  9. semid = semget(IPC_PRIVATE, semnums, IPC_CREAT | IPC_EXCL | );
  10. if(semid < ){
  11. return -;
  12. }
  13.  
  14. union semun un;
  15. unsigned short *array = (unsigned short *)calloc(semnums, sizeof(unsigned short));
  16. int i;
  17. for(i = ; i < semnums; i++){
  18. array[i] = value;
  19. }
  20. un.array = array;
  21.  
  22. /**
  23. * 初始化信号量集中所有信号灯的初值
  24. * 0: 表示要初始化所有的信号灯
  25. */
  26. if(semctl(semid, , SETALL, un) < ){
  27. perror("semctl error");
  28. return -;
  29. }
  30. free(array);
  31. return semid;
  32. }
  33.  
  34. /** 对信号量集(semid)中的信号灯(semnum)作 P() */
  35. void sem_P(int semid, int semnum, int value)
  36. {
  37. assert(value >= );
  38.  
  39. /** 定义 sembuf 类型的结构体数组,放置若干个结构体变量,对应要操作的信号量、P或V操作 */
  40. struct sembuf ops[] = {{semnum, -value, SEM_UNDO}};
  41. if(semop(semid, ops, sizeof(ops)/sizeof(struct sembuf)) < ){
  42. perror("semop error");
  43. }
  44. }
  45.  
  46. /** 对信号集(semid) 中的信号灯(semnum)作V(value)操作 */
  47. void sem_V(int semid, int semnum, int value)
  48. {
  49. assert(value >= );
  50.  
  51. /** 定义 sembuf 类型的结构体数组,放置若干个结构体变量,对应要操作的信号量、P或V操作 */
  52. struct sembuf ops[] = {{semnum, value, SEM_UNDO}};
  53. if(semop(semid, ops, sizeof(ops)/sizeof(struct sembuf)) < ){
  54. perror("semop error");
  55. }
  56. }
  57.  
  58. /** 销毁信号量集(semid) */
  59. void sem_D(int semid)
  60. {
  61. if(semctl(semid, , IPC_RMID, NULL) < ){
  62. perror("semctl error");
  63. }
  64. }

  编译:

  gcc -o obj/sem_pv.o -Iinclude -c src/sem_pv.c

(2)互斥操作

  

  

  atm_account.h

  1. #ifndef INCLUDE_ATM_ACCOUNT_H_
  2. #define INCLUDE_ATM_ACCOUNT_H_
  3.  
  4. #include <malloc.h>
  5. #include <assert.h>
  6. #include <string.h>
  7.  
  8. typedef struct {
  9. int code;
  10. double balance;
  11. int semid; ///< 在共享资源上绑定一个信号量集
  12. }atm_account;
  13.  
  14. /** 取款 */
  15. extern double atm_account_withdraw(atm_account *a, double amt);
  16.  
  17. /** 存款 */
  18. extern double atm_account_deposit(atm_account *a, double amt);
  19.  
  20. /** 查看账户余额度 */
  21. extern double amt_account_balanceGet(atm_account *a);
  22.  
  23. #endif /* INCLUDE_ATM_ACCOUNT_H_ */

  atm_account.c

  1. #include "sem_pv.h"
  2. #include "atm_account.h"
  3.  
  4. /** 取款 */
  5. double atm_account_withdraw(atm_account *a, double amt)
  6. {
  7. assert(a != NULL);
  8.  
  9. /** 对信号量集 semid 中的0号信号量/信号灯作 P(1) 操作 */
  10. sem_P(a->semid, , );
  11. if(amt < || amt > a->balance){
  12. /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
  13. sem_V(a->semid, , );
  14. return 0.0;
  15. }
  16.  
  17. double balance = a->balance;
  18. sleep();
  19. balance -= amt;
  20. a->balance = balance;
  21. /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
  22. sem_V(a->semid, , );
  23. return amt;
  24. }
  25.  
  26. /** 存款 */
  27. double atm_account_deposit(atm_account *a, double amt)
  28. {
  29. assert(a != NULL);
  30.  
  31. /** 对信号量集 semid 中的0号信号量/信号灯作 P(1) 操作 */
  32. sem_P(a->semid, , );
  33. if(amt < ){
  34. /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
  35. sem_V(a->semid, , );
  36. return 0.0;
  37. }
  38. double balance = a->balance;
  39. sleep();
  40. balance += amt;
  41. a->balance = balance;
  42. /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
  43. sem_V(a->semid, , );
  44.  
  45. return amt;
  46. }
  47.  
  48. /** 查看账户余额度 */
  49. double amt_account_balanceGet(atm_account *a)
  50. {
  51. assert(a != NULL);
  52. /** 对信号量集 semid 中的0号信号量/信号灯作 P(1) 操作 */
  53. sem_P(a->semid, , );
  54. double balance = a->balance;
  55. /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
  56. sem_V(a->semid, , );
  57. return balance;
  58. }

  测试代码:atm_account_test.c

  1. #include "atm_account.h"
  2. #include "sem_pv.h"
  3. #include <unistd.h>
  4. #include <sys/shm.h>
  5. #include <sys/wait.h>
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8. #include <string.h>
  9.  
  10. int main(void)
  11. {
  12. /** 在共享内存中创建银行账户 */
  13. int shmid;
  14. if((shmid = shmget(IPC_PRIVATE, sizeof(atm_account), IPC_CREAT | IPC_EXCL | )) < ){
  15. perror("shmget error");
  16. return ;
  17. }
  18.  
  19. /** 进行共享内存映射(a 为映射的地址) */
  20. atm_account *a = (atm_account *)shmat(shmid, , );
  21. if(a == (atm_account *)-){
  22. perror("shmat error");
  23. return ;
  24. }
  25. a->code = ;
  26. a->balance = ;
  27.  
  28. /** 创建信号量集并初始化(1 个信号量/信号灯,初值为 1) */
  29. a->semid = sem_I(, );
  30. if(a->semid < ){
  31. perror("sem_I(1, 1) error");
  32. return ;
  33. }
  34. printf("balance: %f\n", a->balance);
  35.  
  36. pid_t pid;
  37. if((pid = fork()) < ){
  38. perror("fork error");
  39. return ;
  40. }
  41. else if(pid > ){
  42. /** 父进程执行取款操作 */
  43. double amt = atm_account_withdraw(a, );
  44. printf("pid %d withdraw %f form code %d\n", getpid(), amt, a->code);
  45. wait();
  46.  
  47. /** 对共享内存的操作要在解除映射之前 */
  48. printf("balance: %f\n", a->balance);
  49.  
  50. sem_D(a->semid); ///< 销毁信号量集
  51. shmdt(a); ///< 解除共享内存的映射
  52. shmctl(shmid, IPC_RMID, NULL);///< 释放共享内存
  53. }
  54. else {
  55. /** 子进程进行取款操作 */
  56. double amt = atm_account_withdraw(a, );
  57. printf("pid %d withdraw %f form code %d\n", getpid(), amt, a->code);
  58.  
  59. shmdt(a); ///< 解除共享内存的映射
  60. }
  61.  
  62. return ;
  63. }

  编译运行如下:

  

51.2.2 PV操作--读者写者案例

  目的:利用进程信号量的 PV操作实现进程间的同步问题

  共享内存中读写数据(读者和写者问题)

  

  

  1. #include <sys/shm.h>
  2. #include <sys/sem.h>
  3. #include <sys/wait.h>
  4. #include <unistd.h>
  5. #include <string.h>
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8. #include <assert.h>
  9.  
  10. /** 读者和写者的共享资源 */
  11. typedef struct {
  12. int val;
  13. int semid;
  14. }Storage;
  15.  
  16. void init(Storage *s)
  17. {
  18. assert(s != NULL);
  19.  
  20. /** 创建信号量集(包含 2 个信号量) */
  21. if((s->semid = semget(IPC_PRIVATE, , IPC_CREAT | IPC_EXCL | )) < ){
  22. perror("semget error");
  23. exit();
  24. }
  25.  
  26. /** 对信号量集中的所有信号量初始化 */
  27. union semun{
  28. int val;
  29. struct semid_ds *ds;
  30. unsigned short *array;
  31. };
  32. union semun un;
  33. /** 2 个信号量的初值设置为 0 */
  34. unsigned short array[] = {, };
  35. un.array = array;
  36. if(semctl(s->semid, , SETALL, un) < ){
  37. perror("semctl error");
  38. exit();
  39. }
  40. }
  41.  
  42. void destroy(Storage *s)
  43. {
  44. assert(s != NULL);
  45. if(semctl(s->semid, , IPC_RMID, NULL) < ){
  46. perror("semctl error");
  47. exit();
  48. }
  49. }
  50.  
  51. void writer(Storage *s, int val)
  52. {
  53. /** 写入数据到 Storage */
  54. s->val = val;
  55. printf("%d write %d\n", getpid(), val);
  56.  
  57. /** 设置信号量 0 号作 V(1) 操作 */
  58. struct sembuf ops_v[] = {{, , SEM_UNDO}};
  59. /** 设置信号量 1 号作 P(1) 操作 */
  60. struct sembuf ops_p[] = {{, -, SEM_UNDO}};
  61.  
  62. /** V(s1) */
  63. if(semop(s->semid, ops_v, ) < ){
  64. perror("semop error");
  65. }
  66.  
  67. /** P(s2) */
  68. if(semop(s->semid, ops_p, ) < ){
  69. perror("semop error");
  70. }
  71. }
  72.  
  73. void reader(Storage *s)
  74. {
  75. assert(s != NULL);
  76.  
  77. /** 设置信号量 0 号作 P(1) 操作 */
  78. struct sembuf ops_p[] = {{, -, SEM_UNDO}};
  79. /** 设置信号量 1 号作 V(1) 操作 */
  80. struct sembuf ops_v[] = {{, , SEM_UNDO}};
  81. /** P(s1) */
  82. if(semop(s->semid, ops_p, ) < ){
  83. perror("semop error");
  84. }
  85. /** 从 Storage 中读取数据 */
  86. printf("%d read %d\n", getpid(), s->val);
  87. /** V(s2) */
  88. if(semop(s->semid, ops_v, ) < ){
  89. perror("semop error");
  90. }
  91. }
  92.  
  93. int main(void)
  94. {
  95. /** 将共享资源 Storage 创建在共享内存中 */
  96. int shmid;
  97. if((shmid = shmget(IPC_PRIVATE, sizeof(Storage), IPC_CREAT | IPC_EXCL | )) < ){
  98. perror("shmget error");
  99. exit();
  100. }
  101.  
  102. /** 父进程进行共享内存映射 */
  103. Storage *s = (Storage *)shmat(shmid, , );
  104. if(s == (Storage *)-){
  105. perror("shmat error");
  106. exit();
  107. }
  108.  
  109. /** 创建信号量并初始化 */
  110. init(s);
  111.  
  112. pid_t pid;
  113. pid = fork();
  114. if(pid < ){
  115. perror("fork error");
  116. exit();
  117. }
  118. else if(pid > ){
  119. int i = ;
  120. for(;i <= ; i++){
  121. writer(s, i);
  122. }
  123. wait();
  124. destroy(s);
  125. shmdt(s);
  126. shmctl(shmid, IPC_RMID, NULL);
  127. }
  128. else{
  129. int i = ;
  130. for(;i <= ; i++){
  131. reader(s);
  132. }
  133. shmdt(s);
  134. }
  135. }

五十一、进程间通信——System V IPC 之进程信号量的更多相关文章

  1. 四十九、进程间通信——System V IPC 之消息队列

    49.1 System V IPC 介绍 49.1.1 System V IPC 概述 UNIX 系统存在信号.管道和命名管道等基本进程间通讯机制 System V 引入了三种高级进程间通信机制 消息 ...

  2. 五十、进程间通信——System V IPC 之共享内存

    50.1 共享内存 50.1.1 共享内存的概念 共享内存区域是被多个进程共享的一部分物理内存 多个进程都可把该共享内存映射到自己的虚拟内存空间.所有用户空间的进程若要操作共享内存,都要将其映射到自己 ...

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

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

  4. UNIX 进程间通讯(IPC)概念(Posix,System V IPC)

     IPC(Inter-Process Communication,进程间通讯)可以有三种信息共享方式(随文件系统,随内核,随共享内存).(当然这里虽然说是进程间通讯,其实也是可以和线程相通的). 相对 ...

  5. System V IPC 之共享内存

    IPC 是进程间通信(Interprocess Communication)的缩写,通常指允许用户态进程执行系列操作的一组机制: 通过信号量与其他进程进行同步 向其他进程发送消息或者从其他进程接收消息 ...

  6. System V IPC 之信号量

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

  7. System V IPC 之消息队列

    消息队列和共享内存.信号量一样,同属 System V IPC 通信机制.消息队列是一系列连续排列的消息,保存在内核中,通过消息队列的引用标识符来访问.使用消息队列的好处是对每个消息指定了特定消息类型 ...

  8. pause、jobs、setitimer(2)、system v ipc(day12)

    一.pause()的使用 #include <unistd.h> int pause(void); 功能:等待信号的到来 返回值: - 错误 errno被设置 只有在信号处理函数执行完毕的 ...

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

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

随机推荐

  1. 通过shell命令往android中写入配置

    C:\Users>adb shell setprop "persist.sys.btylevel" 100 C:\Users>adb shell getprop &qu ...

  2. 数据可视化的开源方案: Superset vs Redash vs Metabase (二)

    在上篇结尾处我提到“如果现在让我重新选择,我会使用哪个可视化工具?”我的答案是 Redash,原因主要不是功能层面,而是技术层面.本篇就从项目关注度与活跃度,项目的技术架构,源代码的规模与质量,这三个 ...

  3. web框架开发-模板层

    你可能已经注意到我们在例子视图中返回文本的方式有点特别. 也就是说,HTML被直接硬编码在 Python代码之中. def current_datetime(request): now = datet ...

  4. [LeetCode]2. 两数相加

    题目链接:https://leetcode-cn.com/problems/add-two-numbers/ 题目描述: 给出两个 非空 的链表用来表示两个非负的整数.其中,它们各自的位数是按照 逆序 ...

  5. STM32F40G-EVAL_UC/OS III

    micrum官网下载uc/os程序包: 包含文件cotex_M4.h:

  6. ES ik分词器使用技巧

    match查询会将查询词分词,然后对分词的结果进行term查询. 然后默认是将每个分词term查询之后的结果求交集,所以只要分词的结果能够命中,某条数据就可以被查询出来,而分词是在新建索引时指定的,只 ...

  7. centos7内网源站建设

    centos7内网源站建设 1.部署环境: 系统:Centos7 x86_64 应用服务:nginx.createrepo.reposync 镜像源:https://mirrors.aliyun.co ...

  8. 解决hash冲突的三个方法

    通过构造性能良好的哈希函数,可以减少冲突,但一般不可能完全避免冲突,因此解决冲突是哈希法的另一个关键问题.创建哈希表和查找哈希表都会遇到冲突,两种情况下解决冲突的方法应该一致.下面以创建哈希表为例,说 ...

  9. 小小知识点(四)——MATLAB如何画等高线图和线性规划约束方程

    MATLAB程序: figure contourf(x,y,data) % 画等高线 hold on plot(x,y(x)) %画线性规划约束方程1 hold on plot(y,x(y)) %画线 ...

  10. CodeForces Round #552 Div.3

    A. Restoring Three Numbers 代码: #include <bits/stdc++.h> using namespace std; ]; int a, b, c; i ...