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 }