Linux系统应用编程---进程间通信(二)【内存共享映射mmap】

简介: Linux系统应用编程---进程间通信(二)【内存共享映射mmap】

 参考链接:

https://blog.csdn.net/qq_44361695/article/details/99075993

https://segmentfault.com/a/1190000014616732

http://www.cppblog.com/jerryma/archive/2011/08/02/152279.html

1. 内存共享映射的概念

mmap的作用就是把磁盘文件的一部分直接映射到进程的内存中,那么进程就可以直接对该内存文件进行操作。

mmap也设置了两种机制:共享(share)和私有(private)。

如果是共享映射,那么在内存中对文件进行修改,磁盘中对应的文件也会被修改,相反,磁盘中的文件有了修改,内存中的文件也被修改。如果是私有映射,那么内存中的文件是独立的,二者进行修改都不会对对方造成影响。通过这样的内存共享映射就相当于是进程直接对磁盘中的文件进行读写操作一样,那么如果有两个进程来mmap同一个文件,就实现了进程间的通信。

下面是一个把普通文件映射到用户空间的内存区域示意图。

2. mmap和munmap函数

磁盘中的文件通过mmap函数来实现映射,然后通过munmap函数取消映射。先来看一下函数的原型:

 

1. #include <sys/mman.h>
2. void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
3. int munmap(void *start, size_t length);

 mmap系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。mmap并不分配空间, 只是将文件映射到调用进程的地址空间里(但是会占掉你的 virutal memory), 然后你就可以用memcpy等操作写文件, 而不用write()了.写完后,内存中的内容并不会立即更新到文件中,而是有一段时间的延迟,你可以调用msync()来显式同步一下, 这样你所写的内容就能立即保存到文件里了.这点应该和驱动相关。 不过通过mmap来写文件这种方式没办法增加文件的长度, 因为要映射的长度在调用mmap()的时候就决定了.如果想取消内存映射,可以调用munmap()来取消内存映射。

mmap用于把文件映射到内存空间中,简单说mmap就是把一个文件的内容在内存里面做一个映像。映射成功后,用户对这段内存区域的修改可以直接反映到内核空间,同样,内核空间对这段区域的修改也直接反映用户空间。那么对于内核空间<---->用户空间两者之间需要大量数据传输等操作的话效率是非常高的。

mmap函数参数的说明:

Start

指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。

length

代表将文件中多大的部分映射到内存。

prot

映射区域的保护方式。可以为以下几种方式的组合:

PROT_EXEC 映射区域可被执行

PROT_READ 映射区域可被读取

PROT_WRITE 映射区域可被写入

PROT_NONE 映射区域不能存取

flags

影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。

MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。

MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。

MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。

MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。

MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。

MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。

fd

要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。

offset

文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。

返回值

若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

错误代码:

EBADF 参数fd 不是有效的文件描述词

EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。

EINVAL 参数start、length 或offset有一个不合法。

EAGAIN 文件被锁住,或是有太多内存被锁住。

ENOMEM 内存不足。

系统调用mmap()用于共享内存的两种方式:

(1)使用普通文件提供的内存映射:

适用于任何进程之间。此时,需要打开或创建一个文件,然后再调用mmap()。典型调用代码如下:

1. fd=open(name, flag, mode); if(fd<0) ...
2. 
3. ptr=mmap(NULL, len , PROT_READ|PROT_WRITE, MAP_SHARED , fd , 0);

通过mmap()实现共享内存的通信方式有许多特点和要注意的地方,可以参看UNIX网络编程第二卷。

(2)使用特殊文件提供匿名内存映射:

适用于具有亲缘关系的进程之间。由于父子进程特殊的亲缘关系,在父进程中先调用mmap(),然后调用 fork()。那么在调用fork()之后,子进程继承父进程匿名映射后的地址空间,同样也继承mmap()返回的地址,这样,父子进程就可以通过映射区 域进行通信了。注意,这里不是一般的继承关系。一般来说,子进程单独维护从父进程继承下来的一些变量。而mmap()返回的地址,却由父子进程共同维护。 对于具有亲缘关系的进程实现共享内存最好的方式应该是采用匿名内存映射的方式。此时,不必指定具体的文件,只要设置相应的标志即可。

3. 通过代码理解mmap

3.1 code1,通过mmap以shared的方式映射,实现修改映射文件,查看本地文件是否修改

先在本地创建一个hello文件,并写上helloworld

1. #include <stdio.h>
2. #include <unistd.h>
3. #include <sys/stat.h>
4. #include <sys/types.h>
5. #include <fcntl.h>
6. #include <sys/mman.h>
7. #include <stdlib.h>
8. 
9. int main(void)
10. {
11.   int fd, len;
12.     int *p;
13.   fd = open("hello", O_RDWR);
14.   if(fd < 0)
15.   {
16.     perror("open");
17.     exit(1);
18.   }
19.   len = lseek(fd, 0, SEEK_END);
20. 
21.   p = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
22.   if(p == MAP_FAILED)
23.   {
24.     perror("mmap");
25.     exit(1);
26.   }
27.   close(fd);
28.   p[0] = 0x30313233;
29. 
30.   munmap(p, len);
31. 
32.   return 0;
33. }
34. 
35. 
36. /*
37. ** 内存泄漏的话题:
38. * malloc,未free
39. * 打开文件,没有close
40. * 映射了文件,没有munmap
41. */

执行结果如下:

可以看到hello文件中的helloworld已经被替换成了3210oworld,证明通过内存共享去写的方式成功。

3.2 创建两个进程,一个往映射文件中写,一个从映射文件中读

 

proce_mmap_w.c

1. #include <stdio.h>
2. #include <unistd.h>
3. #include <sys/stat.h>
4. #include <sys/types.h>
5. #include <fcntl.h>
6. #include <sys/mman.h>
7. #include <stdlib.h>
8. 
9. #define MAPLEN 0x1000
10. 
11. void sys_err(char *str, int exitno)
12. {
13.   perror(str);
14.   exit(exitno);
15. }
16. 
17. int main(int argc, char * argv[])
18. {
19.   char *mm;
20.   int fd, i = 0;
21.   if(argc < 2)
22.   {
23.     printf("./a.out filename\n");
24.     exit(1);
25.   }
26. 
27.   fd = open(argv[1], O_RDWR | O_CREAT, 0777);
28.   if(fd < 0)
29.     sys_err("open", 1);
30.   lseek(fd, MAPLEN-1, SEEK_SET);
31.   write(fd, "\0", 1);
32. 
33.   mm = mmap(NULL, MAPLEN, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
34.   close(fd);
35.   if(mm == MAP_FAILED)
36.     sys_err("mmap", 2);
37. 
38.   while(1)
39.   {
40.     sprintf(mm, "hello%d", i++);
41.     sleep(1);
42.   }
43.   munmap(mm, MAPLEN);
44.   return 0;
45. 
46. }

proce_mmap_r.c

1. #include <stdio.h>
2. #include <unistd.h>
3. #include <sys/stat.h>
4. #include <sys/types.h>
5. #include <fcntl.h>
6. #include <sys/mman.h>
7. #include <stdlib.h>
8. 
9. #define MAPLEN 0x1000
10. 
11. void sys_err(char *str, int exitno)
12. {
13.   perror(str);
14.   exit(exitno);
15. }
16. 
17. int main(int argc, char * argv[])
18. {
19.   char *mm;
20.   int fd, i = 0;
21.   if(argc < 2)
22.   {
23.     printf("./a.out filename\n");
24.     exit(1);
25.   }
26. 
27.   fd = open(argv[1], O_RDWR);
28.   if(fd < 0)
29.     sys_err("open", 1);
30. 
31.   mm = mmap(NULL, MAPLEN, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
32.   close(fd);
33.   if(mm == MAP_FAILED)
34.     sys_err("mmap", 2);
35. 
36.   while(1)
37.   {
38.     printf("%s\n", mm);
39.     sleep(1);
40.   }
41.   munmap(mm, MAPLEN);
42.   return 0;
43. 
44. }

执行结果如下:

 

相关文章
|
1月前
|
Ubuntu Linux Anolis
Linux系统禁用swap
本文介绍了在新版本Linux系统(如Ubuntu 20.04+、CentOS Stream、openEuler等)中禁用swap的两种方法。传统通过注释/etc/fstab中swap行的方式已失效,现需使用systemd管理swap.target服务或在/etc/fstab中添加noauto参数实现禁用。方法1通过屏蔽swap.target适用于新版系统,方法2通过修改fstab挂载选项更通用,兼容所有系统。
144 3
Linux系统禁用swap
|
1月前
|
Linux
Linux系统修改网卡名为eth0、eth1
在Linux系统中,可通过修改GRUB配置和创建Udev规则或使用systemd链接文件,将网卡名改为`eth0`、`eth1`等传统命名方式,适用于多种发行版并支持多网卡配置。
192 3
|
Ubuntu Linux 网络安全
Linux系统初始化脚本
一款支持Rocky、CentOS、Ubuntu、Debian、openEuler等主流Linux发行版的系统初始化Shell脚本,涵盖网络配置、主机名设置、镜像源更换、安全加固等多项功能,适配单/双网卡环境,支持UEFI引导,提供多版本下载与持续更新。
166 0
Linux系统初始化脚本
|
1月前
|
安全 Linux Shell
Linux系统提权方式全面总结:从基础到高级攻防技术
本文全面总结Linux系统提权技术,涵盖权限体系、配置错误、漏洞利用、密码攻击等方法,帮助安全研究人员掌握攻防技术,提升系统防护能力。
156 1
|
3月前
|
存储
阿里云轻量应用服务器收费标准价格表:200Mbps带宽、CPU内存及存储配置详解
阿里云香港轻量应用服务器,200Mbps带宽,免备案,支持多IP及国际线路,月租25元起,年付享8.5折优惠,适用于网站、应用等多种场景。
999 0
|
3月前
|
存储 缓存 NoSQL
内存管理基础:数据结构的存储方式
数据结构在内存中的存储方式主要包括连续存储、链式存储、索引存储和散列存储。连续存储如数组,数据元素按顺序连续存放,访问速度快但扩展性差;链式存储如链表,通过指针连接分散的节点,便于插入删除但访问效率低;索引存储通过索引表提高查找效率,常用于数据库系统;散列存储如哈希表,通过哈希函数实现快速存取,但需处理冲突。不同场景下应根据访问模式、数据规模和操作频率选择合适的存储结构,甚至结合多种方式以达到最优性能。掌握这些存储机制是构建高效程序和理解高级数据结构的基础。
297 0
|
3月前
|
存储 弹性计算 固态存储
阿里云服务器配置费用整理,支持一万人CPU内存、公网带宽和存储IO性能全解析
要支撑1万人在线流量,需选择阿里云企业级ECS服务器,如通用型g系列、高主频型hf系列或通用算力型u1实例,配置如16核64G及以上,搭配高带宽与SSD/ESSD云盘,费用约数千元每月。
293 0
|
存储 编译器 C语言
【C语言篇】数据在内存中的存储(超详细)
浮点数就采⽤下⾯的规则表⽰,即指数E的真实值加上127(或1023),再将有效数字M去掉整数部分的1。
863 0
|
12月前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。