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 ", 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 ", getpid(), amt, a->code); 45 wait(0); 46 47 /** 对共享内存的操作要在解除映射之前 */ 48 printf("balance: %f ", 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 ", 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 ", 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 ", 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 }