Linux进程间通信--信号量

本系列文章主要是学习记录Linux下进程间通信的方式。

常用的进程间通信方式:管道、FIFO、消息队列、信号量以及共享存储。

参考文档:《UNIX环境高级编程(第三版)》

参考视频:Linux进程通信  推荐看看,老师讲得很不错

Linux核心版本:2.6.32-431.el6.x86_64

注:本文档只是简单介绍IPC,更详细的内容请查看参考文档和相应视频。

本文介绍利用信号量进行进程间的通信

1  介绍

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

2  信号量集属性

1 struct semid_ds {
2     struct ipc_perm sem_perm;  /* Ownership and permissions */
3     time_t          sem_otime; /* Last semop time */
4     time_t          sem_ctime; /* Last change time */
5     unsigned short  sem_nsems; /* No. of semaphores in set */
6 };

3  函数原型

1 #include <sys/types.h>
2 #include <sys/ipc.h>
3 #include <sys/sem.h>
4 int semget(key_t key, int nsems, int semflg);
5 说明:创建信号量集
6 返回:成功返回信号量集ID,出错返回-1
7 参数key:用户指定的信号量集键值
8 参数nsems:信号量集中信号量个数
9 参数semflg:IPC_CREAT、IPC_EXCL等权限组合
 1 #include <sys/types.h>
 2 #include <sys/ipc.h>
 3 #include <sys/sem.h>
 4 int semctl(int semid, int semnum, int cmd, .../*union semum arg*/);
 5 union semun {
 6     int              val;    /* Value for SETVAL */
 7     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
 8     unsigned short  *array;  /* Array for GETALL, SETALL */
 9     struct seminfo  *__buf;  /* Buffer for IPC_INFO
10                                 (Linux-specific) */
11 };
12 说明:信号量集控制
13 参数semid:信号量集ID;
14 参数semnum:0表示对所有信号量操作,信号量编号从0开始;
15 参数val:放置获取或设置信号量集中某个信号量的值;
16 参数buf:信号量集属性指针
17 参数array:放置获取或设置信号量集中所有信号量的值。
18 参数cmd:设定对信号量集要执行的操作
19 IPC_STAT:获取信号量集的属性(buf)
20 IPC_SET:设置信号量集的属性(buf)
21 IPC_RMID:删除信号量集(buf)
22 GETVAL:返回信号量的值(val)
23 SETVAL:设置semnum信号量的值(val)
24 GETALL:获取所有信号量的值(array)
25 SETALL:设置所有信号量的初始值(array)
 1 #include <sys/types.h>
 2 #include <sys/ipc.h>
 3 #include <sys/sem.h>
 4 int semop(int semid, struct sembuf *sops, unsigned nsops);
 5 struct sembuf
 6 {
 7     unsigned short sem_num;  /* semaphore number */
 8     short          sem_op;   /* semaphore operation */
 9     short          sem_flg;  /* operation flags */
10 };
11 说明:信号量集操作
12 返回:成功返回0,出错返回-1
13 参数semid:信号量集ID
14 参数sops:sembuf结构体数组指针
15 参数nsops:第二个参数中结构体数组的长度。
16 参数sem_num:信号量集中信号量的编号
17 参数sem_op:正数为V操作,负数为P操作,0可用于对共享资源时候已用完的测试。
18 参数sem_flg:SEM_UNDO标志,表示在进程结束时,相应的操作将被取消。如果设置了此标志,那么在进程没有释放共享资源就退出时,内核将代为释放。
19 注:用于信号量集中信号量的加和减操作(PV操作),可用于进程间的互斥和同步。

4  测试案例

(1)实例1  利用信号量实现互斥操作

 银行账户的头文件,主要是对账户的一些操作的申明:

 1 #ifndef __ACCOUNT_H__
 2 #define __ACCOUNT_H__
 3 
 4 typedef struct 
 5 {
 6     int     code;
 7     double  balance;
 8     int     semid;  //在共享资源上绑定一个信号量集
 9 }Account;
10 
11 //取款
12 extern double withdraw(Account *a, double amt);
13 
14 //存款
15 extern double deposit(Account *a, double amt);
16 
17 //查看账户余额
18 extern double get_balance(Account *a);
19 
20 
21 #endif
View Code

银行账户的C文件,账户操作的实现:

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

把对信号量得操作封装为单独的文件。

信号量操作头文件:

 1 #ifndef __PV_H__
 2 #define __PV_H__
 3 
 4 // 初始化semnums个信号灯/信号量的值(value)
 5 extern int I(int semnums, int value);
 6 
 7 // 对信号量集(semid)中的信号灯(semnum)做P(value)操作
 8 extern void P(int semid, int semnum, int value);
 9 
10 // 对信号量集(semid)中的信号灯(semnum)做V(value)操作
11 extern void V(int semid, int semnum, int value);
12 
13 // 销毁信号量集(semid)
14 extern void D(int semid);
15 
16 #endif
View Code

信号量操作C文件:

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

测试代码,父子进程同时对银行账户进行操作:

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

测试步骤:

1、编译:[root@192 ipc]# gcc -o bin/account_test -Iinclude obj/pv.o account.c account_test.c 

2、运行:

可以看出,通过信号量能够对银行账户实现互斥访问。

(2)案例2  利用信号量实现同步操作

这里实现的读者和写者问题和书本中有一些差别。

测试代码:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <string.h>
  4 #include <sys/types.h>
  5 #include <sys/ipc.h>
  6 #include <sys/sem.h>
  7 #include <assert.h>
  8 #include <unistd.h>
  9 #include <sys/shm.h>
 10 
 11 // 读者和写者的共享资源
 12 typedef struct 
 13 {
 14     int val;
 15     int semid;
 16 }Storage;
 17 
 18 void init_s(Storage *s) 
 19 {
 20     assert(s != NULL);
 21     // 创建信号量集,包含两个信号量
 22     if ((s->semid = semget(IPC_PRIVATE, 2, IPC_CREAT | IPC_EXCL | 0777)) < 0) {
 23         perror("semget error");
 24         exit(0);
 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_s(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 write_s(Storage *s, int val)
 52 {
 53     assert(s != NULL);
 54     // 写入数据到storage
 55     s->val = val;
 56     printf("%d write %d\n", getpid(), val);
 57 
 58     // 设置0号信号量(s1)做V(1)操作
 59     struct sembuf ops_v[1] = {{0, 1, SEM_UNDO}};
 60     // 设置1号信号量(s2)做P(1)操作
 61     struct sembuf ops_p[1] = {{1, -1, SEM_UNDO}};
 62     
 63     // V(s1)  0号信号量做V(1)
 64     if (semop(s->semid, ops_v, 1) < 0) {
 65         perror("semop error");
 66     }
 67     // P(s2)  1号信号量做V(1)
 68     if (semop(s->semid, ops_p, 1) < 0) {
 69         perror("semop error");
 70     }
 71 }
 72 
 73 void read_s(Storage *s)
 74 {
 75     assert(s != NULL);
 76 
 77     // 设置0号信号量(s1)做P(1)操作
 78     struct sembuf ops_p[1] = {{0, -1, SEM_UNDO}};
 79     // 设置1号信号量(s2)做V(1)操作
 80     struct sembuf ops_v[1] = {{1, 1, SEM_UNDO}};
 81 
 82     // P(s1)  o号信号量做P(1)操作
 83     if (semop(s->semid, ops_p, 1) < 0) {
 84         perror("semop error");
 85     }
 86 
 87     // 从Storage中读取数据
 88     printf("%d read %d\n", getpid(), s->val);
 89 
 90     // V(s2)  1号信号量做V(1)操作
 91     if (semop(s->semid, ops_v, 1) < 0) {
 92         perror("semop error");
 93     }
 94 }
 95 
 96 int main(void)
 97 {
 98     // 将共享资源Storage创建在共享内存中
 99     int shmid;
100     if ((shmid = shmget(IPC_PRIVATE, sizeof(Storage), IPC_CREAT | IPC_EXCL | 0777)) < 0) {
101         perror("shmget error");
102         exit(1);
103     }
104     // 父进程进行共享内存映射
105     Storage *s = (Storage *)shmat(shmid, 0, 0);
106     if (s == (Storage *)-1) {
107         perror("shmat error");
108         exit(1);
109     }
110     // 创建信号量集并初始化
111     init_s(s);
112 
113     pid_t pid;
114     pid = fork();
115     if (pid < 0) {
116         perror("fork error");
117         exit(1);
118     } else if (pid > 0) {
119         // 父进程写入数据
120         int i = 1;
121         for (; i <= 5; i++) {
122             write_s(s, i);
123         }
124         wait(0);
125         destroy_s(s);
126         shmdt(s);  //解除映射
127         shmctl(shmid, IPC_RMID, NULL);  //释放共享内存
128     } else {
129         // 子进程读取数据
130         int i = 1;
131         for (; i <= 5; i++) {
132             read_s(s);
133         }
134         shmdt(s);
135     }
136 
137     return 0;
138 }
View Code

测试步骤:

1、编译:[root@192 ipc]# gcc -o bin/read_write -g reader_writer.c 

2、运行:[root@192 ipc]# ./bin/read_write 

posted @ 2020-06-05 00:13  zhengcixi  阅读(227)  评论(0编辑  收藏  举报
回到顶部