进程间通信——System V IPC 之进程信号量

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 | 0777);
10     if(semid < 0){
11         return -1;
12     }
13 
14     union semun un;
15     unsigned short *array = (unsigned short *)calloc(semnums, sizeof(unsigned short));
16     int i;
17     for(i = 0; i < semnums; i++){
18         array[i] = value;
19     }
20     un.array = array;
21 
22     /**
23      *  初始化信号量集中所有信号灯的初值
24      *  0: 表示要初始化所有的信号灯
25      */
26     if(semctl(semid, 0, SETALL, un) < 0){
27         perror("semctl error");
28         return -1;
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 >= 0);
38 
39     /** 定义 sembuf 类型的结构体数组,放置若干个结构体变量,对应要操作的信号量、P或V操作 */
40     struct sembuf ops[] = {
  
  {semnum, -value, SEM_UNDO}};
41     if(semop(semid, ops, sizeof(ops)/sizeof(struct sembuf)) < 0){
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 >= 0);
50 
51     /** 定义 sembuf 类型的结构体数组,放置若干个结构体变量,对应要操作的信号量、P或V操作 */
52     struct sembuf ops[] = {
  
  {semnum, value, SEM_UNDO}};
53     if(semop(semid, ops, sizeof(ops)/sizeof(struct sembuf)) < 0){
54         perror("semop error");
55     }
56 }
57 
58 /** 销毁信号量集(semid) */
59 void sem_D(int semid)
60 {
61     if(semctl(semid, 0, IPC_RMID, NULL) < 0){
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 
 9 typedef struct {
10     int     code;
11     double    balance;
12     int     semid;    ///< 在共享资源上绑定一个信号量集
13 }atm_account;
14 
15 /** 取款 */
16 extern double atm_account_withdraw(atm_account *a, double amt);
17 
18 /** 存款 */
19 extern double atm_account_deposit(atm_account *a, double amt);
20 
21 /** 查看账户余额度 */
22 extern double amt_account_balanceGet(atm_account *a);
23 
24 #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, 0, 1);
11     if(amt < 0 || amt > a->balance){
12         /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
13         sem_V(a->semid, 0, 1);
14         return 0.0;
15     }
16 
17     double balance = a->balance;
18     sleep(1);
19     balance -= amt;
20     a->balance = balance;
21     /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
22     sem_V(a->semid, 0, 1);
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, 0, 1);
33     if(amt < 0){
34         /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
35         sem_V(a->semid, 0, 1);
36         return 0.0;
37     }
38     double balance = a->balance;
39     sleep(1);
40     balance += amt;
41     a->balance = balance;
42     /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
43     sem_V(a->semid, 0, 1);
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, 0, 1);
54     double balance = a->balance;
55     /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
56     sem_V(a->semid, 0, 1);
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 | 0777)) < 0){
15         perror("shmget error");
16         return 1;
17     }
18 
19     /** 进行共享内存映射(a 为映射的地址) */
20     atm_account *a = (atm_account *)shmat(shmid, 0, 0);
21     if(a == (atm_account *)-1){
22         perror("shmat error");
23         return 1;
24     }
25     a->code = 123456789;
26     a->balance = 10000;
27 
28     /** 创建信号量集并初始化(1 个信号量/信号灯,初值为 1) */
29     a->semid = sem_I(1, 1);
30     if(a->semid < 0){
31         perror("sem_I(1, 1) error");
32         return 1;
33     }
34     printf("balance: %f\n", a->balance);
35 
36     pid_t pid;
37     if((pid = fork()) < 0){
38         perror("fork error");
39         return 1;
40     }
41     else if(pid > 0){
42         /** 父进程执行取款操作 */
43         double amt = atm_account_withdraw(a, 10000);
44         printf("pid %d withdraw %f form code %d\n", getpid(), amt, a->code);
45         wait(0);
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, 10000);
57         printf("pid %d withdraw %f form code %d\n", getpid(), amt, a->code);
58 
59         shmdt(a);    ///< 解除共享内存的映射
60     }
61 
62     return 0;
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, 2, IPC_CREAT | IPC_EXCL | 0777)) < 0){
 22         perror("semget error");
 23         exit(1);
 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[2] = {0, 0};
 35     un.array = array;
 36     if(semctl(s->semid, 0, SETALL, un) < 0){
 37         perror("semctl error");
 38         exit(1);
 39     }
 40 }
 41 
 42 void destroy(Storage *s)
 43 {
 44     assert(s != NULL);
 45     if(semctl(s->semid, 0, IPC_RMID, NULL) < 0){
 46         perror("semctl error");
 47         exit(1);
 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[1] = {
  
  {0, 1, SEM_UNDO}};
 59     /** 设置信号量 1 号作 P(1) 操作 */
 60     struct sembuf ops_p[1] = {
  
  {1, -1, SEM_UNDO}};
 61 
 62     /** V(s1) */
 63     if(semop(s->semid, ops_v, 1) < 0){
 64         perror("semop error");
 65     }
 66 
 67     /** P(s2) */
 68     if(semop(s->semid, ops_p, 1) < 0){
 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[1] = {
  
  {0, -1, SEM_UNDO}};
 79     /** 设置信号量 1 号作 V(1) 操作 */
 80     struct sembuf ops_v[1] = {
  
  {1, 1, SEM_UNDO}};
 81     /** P(s1) */
 82     if(semop(s->semid, ops_p, 1) < 0){
 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, 1) < 0){
 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 | 0777)) < 0){
 98         perror("shmget error");
 99         exit(1);
100     }
101 
102     /** 父进程进行共享内存映射 */
103     Storage *s = (Storage *)shmat(shmid, 0, 0);
104     if(s == (Storage *)-1){
105         perror("shmat error");
106         exit(1);
107     }
108 
109     /** 创建信号量并初始化 */
110     init(s);
111 
112     pid_t pid;
113     pid = fork();
114     if(pid < 0){
115         perror("fork error");
116         exit(1);
117     }
118     else if(pid > 0){
119         int i = 1;
120         for(;i <= 20; i++){
121             writer(s, i);
122         }
123         wait(0);
124         destroy(s);
125         shmdt(s);
126         shmctl(shmid, IPC_RMID, NULL);
127     }
128     else{
129         int i = 1;
130         for(;i <= 20; i++){
131             reader(s);
132         }
133         shmdt(s);
134     }
135 }

猜你喜欢

转载自blog.csdn.net/daocaokafei/article/details/124505005