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
银行账户的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 }
把对信号量得操作封装为单独的文件。
信号量操作头文件:
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
信号量操作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 }
测试代码,父子进程同时对银行账户进行操作:
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 }
测试步骤:
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 }
测试步骤:
1、编译:[root@192 ipc]# gcc -o bin/read_write -g reader_writer.c
2、运行:[root@192 ipc]# ./bin/read_write