【Linux C编程】第十一章 进程间通信2
3. 共享存储映射
(1)文件进程间通信
使用文件也可以完成IPC,理论依据是,fork后,父子进程共享文件描述符。也就共享打开的文件。
(2)存储映射IO
存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。于此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不适用read和write函数的情况下,使用地址(指针)完成I/O操作。
使用这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。
1)mmap函数
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);
返回:
成功:返回创建的映射区首地址;
失败:MAP_FAILED宏
参数:
addr:建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
length:欲创建映射区的大小
prot: 映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags:标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
MAP_SHARED: 会将映射区所做的操作反映到物理设备(磁盘)上。
MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。
fd:用来建立映射区的文件描述符
offset:映射文件的偏移(4k的整数倍)
2)munmap函数
同malloc函数申请内存空间类似的,mmap建立的映射区在使用结束后也应调用类似free的函数来释放。
int munmap(void *addr, size_t length); 成功:0; 失败:-1
mmap.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <fcntl.h>
6 #include <sys/mman.h>
7 #include <string.h>
8
9 int main()
10 {
11 int fd = open("mem.txt",O_RDWR);//创建并且截断文件
12 //int fd = open("mem.txt",O_RDWR|O_CREAT|O_TRUNC,0664);//创建并且截断文件
13
14 ftruncate(fd,8);
15 //创建映射区
16 char *mem = mmap(NULL,20,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
17 //char *mem = mmap(NULL,8,PROT_READ|PROT_WRITE,MAP_PRIVATE,fd,0);
18
19 if(mem == MAP_FAILED){
20 perror("mmap err");
21 return -1;
22 }
23 close(fd);
24 //拷贝数据
25 strcpy(mem,"helloworld");
26 // mem++;
27 //释放mmap
28 printf("mem = %s\n", mem);
29
30 if(munmap(mem,20) < 0){
31 perror("munmap err");
32 }
33 return 0;
34 }
执行结果:
执行mmap_size之前,mem.txt大小为30,代码中 ftruncate(fd,8); 将文件截断为8个字节大小,共享映射为20个字节,虽然文件大小(8) < 映射区大小(20),映射区可以存储helloworld(10),同时修改文件mem.txt内容。
[root@centos 09-linux-day06]# cat mem.txt
*****************************
[root@centos 09-linux-day06]# ./mmap_size
mem = helloworld
[root@centos 09-linux-day06]# cat mem.txt
hellowor
借鉴malloc和free函数原型,尝试装自定义函数smalloc,sfree来完成映射区的建立和释放。思考函数接口该如何设计?
mmap九问:
1. 如果更改mem变量的地址,释放的时候munmap,传入mem还能成功吗? 不能!!
2. 如果对mem越界操作会怎么样? 文件的大小对映射区操作有影响,尽量避免。
3. 如果文件偏移量随便填个数会怎么样? offset必须是 4k的整数倍
4 如果文件描述符先关闭,对mmap映射有没有影响?没有影响
5. open的时候,可以新创建一个文件来创建映射区吗?不可以用大小为0的文件
6. open文件选择O_WRONLY,可以吗? 不可以: Permission denied
7. 当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择PROT_READ|PROT_WRITE吗?Permission denied ,SHARED的时候,映射区的权限 <= open文件的权限
8. mmap什么情况下会报错?很多情况
9. 如果不判断返回值会怎么样? 会死的很难堪!!
总结:使用mmap时务必注意以下事项:
1. 创建映射区的过程中,隐含着一次对映射文件的读操作。
2. 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。
3. 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。
4. 特别注意,当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小!! mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
5. munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作。
6. 文件偏移量必须为4K的整数倍
7. mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。
(3)mmap父子进程间通信
父子等有血缘关系的进程之间也可以通过mmap建立的映射区来完成数据通信。但相应的要在创建映射区的时候指定对应的标志位参数flags:
MAP_PRIVATE: (私有映射) 父子进程各自独占映射区;
MAP_SHARED: (共享映射) 父子进程共享映射区;
练习:父进程创建映射区,然后fork子进程,子进程修改映射区内容,而后,父进程读取映射区内容,查验是否共享。
父子进程共享映射区
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <sys/mman.h>
6 #include <fcntl.h>
7 #include <sys/wait.h>
8
9 int main()
10 {
11 // 先创建映射区
12 int fd = open("mem.txt",O_RDWR);
13 int *mem = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
14 //int *mem = mmap(NULL,4, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
15 if(mem == MAP_FAILED){
16 perror("mmap err");
17 return -1;
18 }
19 // fork子进程
20 pid_t pid = fork();
21
22 // 父进程和子进程交替修改数据
23 if(pid == 0 ){
24 //son
25 *mem = 100;
26 printf("child,*mem = %d\n",*mem);
27 sleep(3);
28 printf("child,*mem = %d\n",*mem);
29 }
30 else if(pid > 0){
31 //parent
32 sleep(1);
33 printf("parent,*mem=%d\n",*mem);
34 *mem = 1001;
35 printf("parent,*mem=%d\n",*mem);
36 wait(NULL);
37 }
38
39 munmap(mem,4);
40 close(fd);
41
42 return 0;
43 }
结论:父子进程共享:1. 打开的文件 2. mmap建立的映射区(但必须要使用MAP_SHARED)
(4)匿名映射
通过使用我们发现,使用映射区来完成文件读写操作十分方便,父子进程间通信也较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了再unlink、close掉,比较麻烦。 可以直接使用匿名映射来代替。其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区。同样需要借助标志位参数flags来指定。
使用MAP_ANONYMOUS (或MAP_ANON), 如:
int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
需注意的是,MAP_ANONYMOUS和MAP_ANON这两个宏是Linux操作系统特有的宏。在类Unix系统中如无该宏定义,可使用如下两步来完成匿名映射区的建立。
1)fd = open("/dev/zero", O_RDWR);
2)p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);
mmap_anon.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <sys/mman.h>
6 #include <fcntl.h>
7 #include <sys/wait.h>
8
9 int main()
10 {
11 int *mem = mmap(NULL,4,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
12
13 if(mem == MAP_FAILED){
14 perror("mmap err");
15 return -1;
16 }
17
18 pid_t pid = fork();
19
20 if(pid == 0 ){
21 //son
22 *mem = 100;
23 printf("child,*mem=%d\n",*mem);
24 sleep(3);
25 printf("child,*mem=%d\n",*mem);
26 }else if(pid > 0){
27 //parent
28 sleep(1);
29 printf("parent,*mem=%d\n",*mem);
30 *mem = 200;
31 printf("parent,*mem=%d\n",*mem);
32 wait(NULL);
33 }
34
35 munmap(mem,4);
36
37 return 0;
38 }
(5)mmap无血缘关系进程间通信
实质上mmap是内核借助文件帮我们创建了一个映射区,多个进程之间利用该映射区完成数据传递。由于内核空间多进程共享,因此无血缘关系的进程间也可以使用mmap来完成通信。只要设置相应的标志位参数flags即可。若想实现共享,当然应该使用MAP_SHARED了。
mmap_w.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <sys/mman.h>
6 #include <fcntl.h>
7 #include <sys/wait.h>
8
9 typedef struct _Student{
10 int sid;
11 char sname[20];
12 }Student;
13
14 int main(int argc,char *argv[])
15 {
16 if(argc != 2){
17 printf("./a.out filename\n");
18 return -1;
19 }
20
21 // 1. open file
22 int fd = open(argv[1],O_RDWR|O_CREAT|O_TRUNC,0666);
23 int length = sizeof(Student);
24
25 ftruncate(fd,length);
26
27 // 2. mmap
28 Student * stu = mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
29
30 if(stu == MAP_FAILED){
31 perror("mmap err");
32 return -1;
33 }
34 int num = 1;
35 // 3. 修改内存数据
36 while(1){
37 stu->sid = num;
38 sprintf(stu->sname,"from mmap write-%03d",num++);
39 sleep(1);//相当于没隔1s修改一次映射区的内容
40 }
41 // 4. 释放映射区和关闭文件描述符
42 munmap(stu,length);
43 close(fd);
44
45 return 0;
46 }
mmap_r.c
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <sys/stat.h>
5 #include <sys/mman.h>
6 #include <fcntl.h>
7 #include <sys/wait.h>
8
9 typedef struct _Student{
10 int sid;
11 char sname[20];
12 }Student;
13
14 int main(int argc,char *argv[])
15 {
16 //open file
17 int fd = open(argv[1],O_RDWR);
18 //mmap
19 int length = sizeof(Student);
20 Student *stu = mmap(NULL,length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
21 if(stu == MAP_FAILED){
22 perror("mmap err");
23 return -1;
24 }
25 //read data
26 while(1){
27 printf("sid=%d,sname=%s\n",stu->sid,stu->sname);
28 sleep(1);
29 }
30 //close and munmap
31 munmap(stu,length);
32 close(fd);
33 return 0;
34 }
练习:
1.通过命名管道传输数据,进程A和进程B,进程A将一个文件(MP3)发送给进程B?
2.实现多进程拷贝文件?
实现思路:
代码实现:
多进程文件拷贝
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/types.h>
4 #include <fcntl.h>
5 #include <sys/mman.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/stat.h>
9
10 int main(int argc, char *argv[])
11 {
12 //拷贝文件起的进程数,通过参数输入,默认是5个
13 int n = 5;
14 //输入参数至少是3,第4个参数是进程个数
15 if (argc < 3)
16 {
17 printf("./a.out src dst [n]\n");
18 return -1;
19 }
20 if (argc == 4)
21 {
22 n = atoi(argv[3]);
23 }
24
25 //打开源文件
26 int srcfd = open(argv[1], O_RDONLY);
27 if (srcfd < 0)
28 {
29 perror("open src err:");
30 exit(1);
31 }
32
33 //打开目标文件
34 int dstfd = open(argv[2], O_RDWR|O_CREAT|O_TRUNC, 0644);
35 if (dstfd < 0)
36 {
37 perror("open dst err:");
38 exit(1);
39 }
40
41 //目标拓展,从源文件获得文件大小,stat
42 struct stat sb;
43 stat(argv[1], &sb);
44 //将目标文件设置为和源文件大小相同
45 int len = sb.st_size;
46 truncate(argv[2], len);
47
48 //将源文件映射到缓冲区
49 char *psrc = mmap(NULL, len, PROT_READ, MAP_SHARED, srcfd, 0);
50 if (psrc == MAP_FAILED)
51 {
52 perror("mmap src err:");
53 exit(1);
54 }
55
56 //将目标文件映射
57 char *pdst = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, dstfd, 0);
58 if (pdst == MAP_FAILED)
59 {
60 perror("mmap pdst err:");
61 exit(1);
62 }
63 //创建多个子进程
64 int i = 0;
65 for (i = 0; i < n; i++)
66 {
67 if (fork() == 0)
68 {
69 break;
70 }
71 }
72 //计算子进程需要拷贝的起点大小
73 int cpsize = len/n;
74 int mod = len%n;
75 //数据拷贝,memcpy
76 if (i < n)
77 {
78 //最后一个子进程
79 if (i == n-1)
80 {
81 memcpy(pdst+i*cpsize, psrc+i*cpsize, cpsize+mod);
82 }
83 else
84 {
85 memcpy(pdst+i*cpsize, psrc+i*cpsize, cpsize);
86 }
87 }
88 else
89 {
90 for (i = 0; i < n; i++)
91 {
92 //回收子线程
93 wait(NULL);
94 }
95 }
96 //释放映射区
97 if (munmap(psrc, len) < 0)
98 {
99 perror("munmap src err:");
100 exit(1);
101 }
102
103 if (munmap(pdst, len) < 0)
104 {
105 perror("munmap dst err:");
106 exit(1);
107 }
108
109 //关闭文件
110 close(srcfd);
111 close(dstfd);
112
113 return 0;
114 }
- 点赞
- 收藏
- 关注作者
评论(0)