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月前
|
存储 缓存 监控
|
1月前
麒麟系统mate-indicators进程占用内存过高问题解决
【10月更文挑战第7天】麒麟系统mate-indicators进程占用内存过高问题解决
169 2
|
7天前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
20天前
|
存储 安全 关系型数据库
Linux系统在服务器领域的应用与优势###
本文深入探讨了Linux操作系统在服务器领域的广泛应用及其显著优势。通过分析其开源性、安全性、稳定性和高效性,揭示了为何Linux成为众多企业和开发者的首选服务器操作系统。文章还列举了Linux在服务器管理、性能优化和社区支持等方面的具体优势,为读者提供了全面而深入的理解。 ###
|
30天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
1月前
|
编解码 Android开发 UED
构建高效Android应用:从内存优化到用户体验
【10月更文挑战第11天】本文探讨了如何通过内存优化和用户体验改进来构建高效的Android应用。介绍了使用弱引用来减少内存占用、懒加载资源以降低启动时内存消耗、利用Kotlin协程进行异步处理以保持UI流畅,以及采用响应式设计适配不同屏幕尺寸等具体技术手段。
49 2
|
28天前
|
运维 JavaScript Linux
容器内的Nodejs应用如何获取宿主机的基础信息-系统、内存、cpu、启动时间,以及一个df -h的坑
本文介绍了如何在Docker容器内的Node.js应用中获取宿主机的基础信息,包括系统信息、内存使用情况、磁盘空间和启动时间等。核心思路是将宿主机的根目录挂载到容器,但需注意权限和安全问题。文章还提到了使用`df -P`替代`df -h`以获得一致性输出,避免解析错误。
|
4月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
4月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
162 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
3月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。