本系列文章主要是学习记录Linux下进程间通信的方式。
常用的进程间通信方式:管道、FIFO、消息队列、信号量以及共享存储。
参考文档:《UNIX环境高级编程(第三版)》
参考视频:Linux进程通信 推荐看看,老师讲得很不错
Linux核心版本:2.6.32-431.el6.x86_64
注:本文档只是简单介绍IPC,更详细的内容请查看参考文档和相应视频。
本文介绍利用共享内存进行进程间的通信
1 介绍
- 共享存储允许两个或多个进程共享一个指定的存储区。当有进程正在往共享存储中写入数据时,其它进程不能从共享存储中取数据。通常使用信号量来同步共享存储的访问。
- 多个进程都可把该共享内存映射到自己的虚拟内存空间。所有用户空间的进程若要操作共享内存,都要将其映射到自己虚拟内存空间中,通过映射的虚拟内存空间地址去操作共享内存,从而达到进程间的数据通信。
- 共享内存是进程间共享数据的一种最快的方法。一个进程先共享内存区写入了数据,共享这个内存区域的所有进程就可以立即看到其中的内容。
- 本身不提供同步机制,可通过信号量进行同步。
- 提升数据处理效率,一种效率最高的IPC机制。
2 共享内存属性
1 struct shmid_ds { 2 struct ipc_perm shm_perm; /* Ownership and permissions */ 3 size_t shm_segsz; /* Size of segment (bytes) */ 4 time_t shm_atime; /* Last attach time */ 5 time_t shm_dtime; /* Last detach time */ 6 time_t shm_ctime; /* Last change time */ 7 pid_t shm_cpid; /* PID of creator */ 8 pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */ 9 shmatt_t shm_nattch; /* No. of current attaches */ 10 ... 11 };
3 使用步骤
- 使用shmget函数创建共享内存;
- 使用shmat函数创建共享内存,将这段创建的共享内存映射到具体的进程虚拟内存空间中。
4 函数原型
1 #include <sys/ipc.h> 2 #include <sys/shm.h> 3 int shmget(key_t key, size_t size, int shmflg); 4 说明:创建共享内存 5 返回:成功返回内存*享内存的的标识ID;失败返回-1; 6 参数key:用户指定的共享内存键值; 7 参数size:共享内存大小; 8 参数shmflg:IPC_CREAT、IPC_EXCL等权限组合。
1 #include <sys/ipc.h> 2 #include <sys/shm.h> 3 int shmctl(int shmid, int cmd, struct shmid_ds *buf); 4 说明:共享内存控制 5 参数shmid:共享内存ID; 6 参数buf:共享内存属性指针; 7 参数cmd:IPC_STAT:获取共享内存段属性;IPC_SET:设置共享内存段属性;
IPC_RMID:删除共享内存段;SHM_LOCK:锁定共享内存段页面(页面映射到屋里内存不和外存进行换入换出操作);
SHM_UNLOCK:解除共享内存段页面的锁定。
1 #include <sys/types.h> 2 #include <sys/shm.h> 3 void *shmat(int shmid, const void *shmaddr, int shmflg); 4 说明:共享内存映射 5 返回:成功返回共享内存映射到进程虚拟内存空间中的地址;失败返回-1; 6 int shmdt(const void *shmaddr); 7 说明:共享内存解除映射; 8 返回:如果失败,则返回-1; 9 参数shmid:共享内存ID; 10 参数shmaddr:映射到进程虚拟内存空间的地址,建议设置为0,由操作系统分配; 11 参数shmflg:若shmaddr设置为0,则shmflg也设置为0。SHM_RND:随机;SHMLBA:地址为2的乘方;SHM_RDONLY:只读方式链接。 12 注:子进程不继承父进程创建的共享内存,大家是共享的,子进程继承父进程映射的地址。
5 实现案例
(1)案例一:同步使用共享内存
父进程向共享内存中写入数据,子进程等待父进程写入数据并从中读取数据。对共享进程的同步使用管道实现。
管道的头文件:
1 #ifndef __TELL_H__ 2 #define __TELL_H__ 3 4 //管道初始化 5 extern void init(void); 6 7 //利用管道进行等待 8 extern void wait_pipe(void); 9 10 //利用管道进行通知 11 extern void notify_pipe(void); 12 13 //销毁管道 14 extern void destroy_pipe(void); 15 16 #endif
管道的C文件:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include "tell.h" 5 6 static int fd[2]; 7 8 //管道初始化 9 void init(void) 10 { 11 if (pipe(fd) < 0) { 12 perror("pipe error"); 13 } 14 } 15 16 //利用管道进行等待 17 void wait_pipe(void) 18 { 19 char c; 20 //管道读写默认是阻塞性的 21 if (read(fd[0], &c, 1) < 0) { 22 perror("wait pipe error"); 23 } 24 } 25 26 //利用管道进行通知 27 void notify_pipe(void) 28 { 29 char c = ‘c‘; 30 if (write(fd[1], &c, 1) != 1) { 31 perror("notify pipe error"); 32 } 33 } 34 35 //销毁管道 36 void destroy_pipe(void) 37 { 38 close(fd[0]); 39 close(fd[1]); 40 }
父子进程对共享进程的访问:
1 #include <sys/types.h> 2 #include <sys/shm.h> 3 #include <stdio.h> 4 #include <stdlib.h> 5 6 int main(void) 7 { 8 int shmid; 9 if ((shmid = shmget(IPC_PRIVATE, 1024, IPC_CREAT | IPC_EXCL | 0777)) < 0) { 10 perror("shmget error"); 11 exit(1); 12 } 13 pid_t pid; 14 init(); //初始化管道 15 if ((pid = fork()) < 0) { 16 perror("fork error"); 17 exit(1); 18 } else if (pid > 0) { //父进程 19 int *pi = (int *)shmat(shmid, 0, 0); 20 if (pi == (int *)-1) { 21 perror("shmat error"); 22 exit(1); 23 } 24 //往共享内存中写入数据(通过操作映射的地址即可) 25 *pi = 100; 26 *(pi + 1) = 20; 27 //操作完毕,解除共享内存的映射 28 shmdt(pi); 29 //通知子进程读取数据 30 notify_pipe(); 31 //销毁管道 32 destroy_pipe(); 33 //等待子进程结束 34 wait(0); 35 } else { //子进程 36 //子进程阻塞,等待父进程先往内存中写入数据 37 wait_pipe(); 38 //子进程从共享内存中读取数据 39 //子进程进行共享内存的映射 40 int *pi = (int *)shmat(shmid, 0, 0); 41 if (pi == (int *)-1) { 42 perror("shmat error"); 43 exit(1); 44 } 45 printf("start: %d, end: %d\n", *pi, *(pi+1)); 46 //读取完毕后解除映射 47 shmdt(pi); 48 //删除共享内存 49 shmctl(shmid, IPC_RMID, NULL); 50 //销毁管道 51 destroy_pipe(); 52 } 53 54 return 0; 55 }
测试步骤:
1、编译:[root@192 ipc]# gcc -o bin/cal_shm -Iinclude tell.c cal_shm.c
2、运行: