进程间通信之共享内存
一、共享内存的介绍
共享内存可以从字面上去理解,就把一片逻辑内存共享出来,让不同的进程去访问它,修改它。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
但有一点特别要注意:共享内存并未提供同步机制。也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,比如信号量。
它的原理就是:在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。
1.查看系统中的共享存储段
ipcs -m
2.删除系统中的共享存储段
ipcrm -m [shmid]
二、函数介绍
shmget函数
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
有一个特殊的键值IPC_PRIVATE, 它用于创建一个只属于创建进程的共享内存,通常不会用到。
代码:
1 #include<unistd.h>
2 #include<stdlib.h>
3 #include<stdio.h>
4 #include<sys/shm.h>
5 #include <sys/ipc.h>
6
7 int main()
8 {
9 int shmid;
10 shmid = shmget(IPC_PRIVATE,128,0777);
11 if(shmid < 0)
12 {
13 printf("create error\n");
14 return -1;
15 }
16 printf("create share memory ok,shmid = %d\n",shmid);
17 system("ipcs -m");
18 return 0;
19 }
结果:
wz@wz-machine:~/linux/shm$ ./shm
create share memory ok,shmid = 1540108
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
0x00000000 1179648 wz 600 524288 2 目标
0x00000000 393217 wz 600 524288 2 目标
0x00000000 425986 wz 600 524288 2 目标
0x00000000 622595 wz 600 524288 2 目标
0x00000000 720900 wz 600 524288 2 目标
0x00000000 753669 wz 600 16777216 2
0x00000000 1015814 wz 600 524288 2 目标
0x00000000 884743 wz 600 33554432 2 目标
0x00000000 1277960 wz 600 524288 2 目标
0x00000000 1310729 wz 600 1048576 2 目标
0x00000000 1474570 wz 777 128 0
0x00000000 1507339 wz 777 128 0
0x00000000 1540108 wz 777 128 0
shmat函数
第一次创建共享内存段时,它不能被任何进程访问。要想启动对该内存的访问,必须将其连接到一个进程的地址空间。
这个工作由shmat函数完成:
函数原型:void *shmat(int shm_id, const void *shm_addr, int shmflg);
参数:
shm_id : 由shmget返回的共享内存标识。
shm_addr: 指定共享内存连接到当前进程中的地址位置。它通常是一个空指针, 表示让系统来选择共享内存出现的地址。
shmflg : 是一组标志。它的两个可能取值是SHM_RND, 和shm_addr联合使用, 用来控制共享内存连接的地址。SHM_RDONLY, 它使连接的内存只读
返回值:
如果调用成功, 返回一个指向共享内存第一个字节的指针;
如果失败,返回-1.
共享内存的读写权限由它的属主(共享内存的创建者),
它的访问权限由当前进程的属主决定。
共享内存的访问权限类似于文件的访问权限。
1 #include<unistd.h>
2 #include<stdlib.h>
3 #include<stdio.h>
4 #include<sys/shm.h>
5 #include <sys/ipc.h>
6
7 int main()
8 {
9 int shmid;
10 char *p;
11 shmid = shmget(IPC_PRIVATE,128,0777);
12 if(shmid < 0)
13 {
14 printf("create error\n");
15 return -1;
16 }
17 printf("create share memory ok,shmid = %d\n",shmid);
18 system("ipcs -m");
19 p = (char*)shmat(shmid,NULL,0);
20 if(p == NULL)
21 {
22 printf("create shmat error\n");
23 return -1;
24 }
25 //write
26 fgets(p,128,stdin);
27
28 //read
29 printf("share memory:%s\n",p);
30 printf("second share memory:%s\n",p);
31 return 0;
结果:
00000000 425986 wz 600 524288 2 目标
0x00000000 622595 wz 600 524288 2 目标
0x00000000 720900 wz 600 524288 2 目标
0x00000000 753669 wz 600 16777216 2
0x00000000 1015814 wz 600 524288 2 目标
0x00000000 884743 wz 600 33554432 2 目标
0x00000000 1277960 wz 600 524288 2 目标
0x00000000 1310729 wz 600 1048576 2 目标
0x00000000 1474570 wz 777 128 0
0x00000000 1507339 wz 777 128 0
0x00000000 1540108 wz 777 128 0
0x00000000 1638413 wz 600 524288 2 目标
0x00000000 1703950 wz 600 2097152 2 目标
0x00000000 1736719 wz 777 128 0
hello
share memory:hello
second share memory:hello
由此可知,shmat函数相当于把内存中的共享内存映射到用户空间,可以直间进程读写操作,而且读完一次之后,该内存不会释放。
shmdt(去关联)
将共享内存从当前进程中分离。
int shmdt(const void *shm_addr);
shm_addr: shmat返回的地址指针。
成功时,返回0,
失败时,返回-1.
NOTE:
共享内存分离并未删除它,
只是使得该共享内存对当前进程不再可用。
shmctl函数
删除共享内存对象
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
shmid_ds结构至少包含以下成员:
struct shmid_ds {
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
参数:
shm_id : 是shmget返回的共享内存标识符。
command: 是要采取的动作,
它可以取3个值:
IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 如果进程有足够的权限,
就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID 删除共享内存段
buf : 是一个指针,
包含共享内存模式和访问权限的结构。
返回值:
成功时,返回0,
失败时,返回-1.
1 #include<unistd.h>
2 #include<stdlib.h>
3 #include<stdio.h>
4 #include<sys/shm.h>
5 #include <sys/ipc.h>
6
7 int main()
8 {
9 int shmid;
10 char *p;
11 shmid = shmget(IPC_PRIVATE,128,0777);
12 if(shmid < 0)
13 {
14 printf("create error\n");
15 return -1;
16 }
17 printf("create share memory ok,shmid = %d\n",shmid);
18 system("ipcs -m");
19 p = (char*)shmat(shmid,NULL,0);
20 if(p == NULL)
21 {
22 printf("create shmat error\n");
23 return -1;
24 }
25 //write
26 fgets(p,128,stdin);
27
28 //read
29 printf("share memory:%s\n",p);
30 printf("second share memory:%s\n",p);
31 shmdt(p);
32 shmctl(shmid,IPC_RMID,NULL);
33 system("ipcs -m");
34 return 0;
35 }
结果:
wz@wz-machine:~/linux/shm$ ./shmctl
create share memory ok,shmid = 1900560
------------ 共享内存段 --------------
键 shmid 拥有者 权限 字节 连接数 状态
0x00000000 1179648 wz 600 524288 2 目标
0x00000000 393217 wz 600 524288 2 目标
0x00000000 425986 wz 600 524288 2 目标
0x00000000 622595 wz 600 524288 2 目标
0x00000000 720900 wz 600 524288 2 目标
0x00000000 753669 wz 600 16777216 2
0x00000000 1015814 wz 600 524288 2 目标
0x00000000 884743 wz 600 33554432 2 目标
0x00000000 1277960 wz 600 524288 2 目标
0x00000000 1310729 wz 600 1048576 2 目标
0x00000000 1474570 wz 777 128 0
0x00000000 1507339 wz 777 128 0
0x00000000 1540108 wz 777 128 0
0x00000000 1638413 wz 600 524288 2 目标
0x00000000 1703950 wz 600 2097152 2 目标
0x00000000 1736719 wz 777 128 0
由此可见,内存被删除了,因为创建的shmid没有了。
接下来就是有亲缘关系直间的通信:
1 #include<unistd.h>
2 #include<signal.h>
3 #include<stdlib.h>
4 #include<stdio.h>
5 #include<sys/shm.h>
6 #include <sys/ipc.h>
7 void myfun(int signum)
8 {
9 return ;
10 }
11 int main()
12 {
13 int shmid;
14 pid_t pid;
15 char *p;
16 shmid = shmget(IPC_PRIVATE,128,IPC_CREAT|0777);
17 if(shmid < 0)
18 {
19 printf("create error\n");
20 return -1;
21 }
22 printf("create share memory ok,shmid = %d\n",shmid);
23 //system("ipcs -m");
24 pid = fork();
25 if(pid > 0)
26 {
27 signal(SIGUSR2,myfun);
28 p = (char*)shmat(shmid,NULL,0);
29 if(p == NULL)
30 {
31 printf("create shmat error\n");
32 return -1;
33 }
34 while(1)
35 {
36 //write
37 printf("parent process start write:\n");
38 fgets(p,128,stdin);
39 kill(pid,SIGUSR1);//child process read
40 pause();//wait child read
41
42 }
43 }
44 //read
45 if(pid == 0)
46 {
47 p = (char*)shmat(shmid,NULL,0);
48 if(p == NULL)
49 {
50 printf("child chmat error\n");
51 return -1;
52 }
53 signal(SIGUSR1,myfun);
54 while(1)
55 {
56 pause();//wait parent process write
57 printf("share memory:%s\n",p);
58 //printf("second share memory:%s\n",p);
59 kill(getppid(),SIGUSR2);
60 }
61 }
62 shmdt(p);
63 shmctl(shmid,IPC_RMID,NULL);
64 system("ipcs -m");
65 return 0;
66 }
结果:
wz@wz-machine:~/linux/shm$ ./shmctl
create share memory ok,shmid = 1376266
parent process start write:
sd
share memory:sd
parent process start write:
sd
share memory:sd
parent process start write:
sd
share memory:sd
接下来就是无亲缘关系直间的通信,我们先实现单向的通信,双向通信按照单向通信再建立一个共享内存就可以了。
ser.c
1 #include<unistd.h>
2 #include<signal.h>
3 #include<stdlib.h>
4 #include<stdio.h>
5 #include<sys/shm.h>
6 #include <sys/ipc.h>
7 struct mybuf
8 {
9 int pid;
10 char buf[100];
11 };
12 void myfun(int signum)
13 {
14 return ;
15 }
16 int main()
17 {
18 int shmid;
19 pid_t pid;
20 int key;
21 struct mybuf *p;
22 key = ftok("./a.c",'a');
23 if(key < 0)
24 {
25 printf("create key fail\n");
26 return -1;
27 }
28 shmid = shmget(key,128,IPC_CREAT|0777);
29 if(shmid < 0)
30 {
31 printf("create error\n");
32 return -1;
33 }
34 printf("create share memory ok,shmid = %d\n",shmid);
35 //system("ipcs -m");
36 signal(SIGUSR2,myfun);
37 p = (struct mybuf*)shmat(shmid,NULL,0);
38 if(p == NULL)
39 {
40 printf("create shmat error\n");
41 return -1;
42 }
43 //get client pid
44 p->pid = getpid();//write server pid to share memory
45 pause();//wait client read server pid
46 pid =p->pid;
47 while(1)
48 {
49 //write
50 printf("parent process start write:\n");
51 fgets(p->buf,128,stdin);
52 kill(pid,SIGUSR1);//child process read
53 pause();//wait child read
54
55 }
56 shmdt(p);
57 shmctl(shmid,IPC_RMID,NULL);
58 system("ipcs -m");
59 return 0;
60 }
cli.c
1 #include<unistd.h>
2 #include<signal.h>
3 #include<stdlib.h>
4 #include<stdio.h>
5 #include<sys/shm.h>
6 #include <sys/ipc.h>
7 struct mybuf
8 {
9 int pid;
10 char buf[100];
11 };
12 void myfun(int signum)
13 {
14 return ;
15 }
16 int main()
17 {
18 int shmid;
19 pid_t pid;
20 int key;
21 struct mybuf *p;
22 key = ftok("./a.c",'a');
23 if(key < 0)
24 {
25 printf("create key fail\n");
26 return -1;
27 }
28 shmid = shmget(key,128,IPC_CREAT|0777);
29 if(shmid < 0)
30 {
31 printf("create error\n");
32 return -1;
33 }
34 printf("create share memory ok,shmid = %d\n",shmid);
35 //system("ipcs -m");
36 signal(SIGUSR1,myfun);
37 p = (struct mybuf*)shmat(shmid,NULL,0);
38 if(p == NULL)
39 {
40 printf("create shmat error\n");
41 return -1;
42 }
43 //get server pid
44 //read share memory
45 pid = p->pid;
46 //write client pid to share memory
47 p->pid = getpid();
48 //kill signal
49 kill(pid,SIGUSR2);
50 p->pid = getpid();//write server pid to share memory
51 while(1)
52 {
53 pause();//wait server write data to share memory
54 printf("client process receve data from share memory :%s",p->buf);//read
55 kill(pid,SIGUSR2);//server write
56
57 }
58 shmdt(p);
59 shmctl(shmid,IPC_RMID,NULL);
60 system("ipcs -m");
61 return 0;
62 }
接下来,我说一下这个通信的过程
1.由于客户端和服务端没有血缘关系,所以没办法直接通信,所以获取同一个pid来进行通信,利用信号来进行同步。
2.用一个图来说吧
不足之处请多多指教!!!