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. }

执行结果如下:

 

相关文章
|
9月前
|
存储 缓存 Java
【高薪程序员必看】万字长文拆解Java并发编程!(5):深入理解JMM:Java内存模型的三大特性与volatile底层原理
JMM,Java Memory Model,Java内存模型,定义了主内存,工作内存,确保Java在不同平台上的正确运行主内存Main Memory:所有线程共享的内存区域,所有的变量都存储在主存中工作内存Working Memory:每个线程拥有自己的工作内存,用于保存变量的副本.线程执行过程中先将主内存中的变量读到工作内存中,对变量进行操作之后再将变量写入主内存,jvm概念说明主内存所有线程共享的内存区域,存储原始变量(堆内存中的对象实例和静态变量)工作内存。
288 0
|
7月前
|
安全 应用服务中间件 网络安全
在Linux环境部署Flask应用并启用SSL/TLS安全协议
至此,你的Flask应用应该能够通过安全的HTTPS协议提供服务了。记得定期更新SSL证书,Certbot可以帮你自动更新证书。可以设定cronjob以实现这一点。
488 10
|
7月前
|
Java Linux 网络安全
Linux云端服务器上部署Spring Boot应用的教程。
此流程涉及Linux命令行操作、系统服务管理及网络安全知识,需要管理员权限以进行配置和服务管理。务必在一个测试环境中验证所有步骤,确保一切配置正确无误后,再将应用部署到生产环境中。也可以使用如Ansible、Chef等配置管理工具来自动化部署过程,提升效率和可靠性。
735 13
|
6月前
|
存储 数据采集 监控
ubuntu(linux)系统主要应用于哪些工业场景中?研维三防ubuntu系统的手持工业三防平板电脑在哪些行业中有实际应用
Ubuntu 系统凭借其独特的优势,在众多工业场景中得到了广泛应用,为工业数字化、智能化发展提供了有力支持。而研维三防基于 Ubuntu 定制系统的手持工业三防平板电脑,更是将 Ubuntu 系统的优势与工业级的性能、坚固耐用性完美结合,在电力、物流、制造等多个行业中展现出强大的应用价值,助力企业提高生产效率、优化管理流程、提升产品质量,成为推动工业现代化发展的重要力量。随着技术的不断进步与创新,相信 Ubuntu 系统以及研维三防这类工业级设备将在更多的工业领域中发挥更大的作用,为工业发展带来更多的机遇与变革。
|
9月前
|
Go 开发者
Go语言内存共享与扩容机制 -《Go语言实战指南》
本文深入探讨了Go语言中切片的内存共享机制与自动扩容策略。切片作为动态数组的抽象,其底层结构包含指针、长度和容量。多个切片可能共享同一底层数组,修改一个切片可能影响其他切片。当切片容量不足时,`append`会触发扩容,新容量按指数增长以优化性能。为避免共享导致的副作用,可通过`copy`创建独立副本或在函数中使用只读方式处理。最后总结了最佳实践,帮助开发者高效使用切片,写出更优代码。
230 10
|
12月前
|
机器学习/深度学习 安全 Linux
Linux 主要应用领域的归纳
服务器领域 Linux在服务器领域的应用是其最为广泛和成熟的领域之一。由于其开源、稳定、高效和安全的特性,Linux成为许多企业服务器的首选操作系统。 Web服务器:Linux是Web服务器的理想选择,因为它支持Apache、Nginx等流行的Web服务器软件,这些软件在Linux上运行稳定且性能卓越。Linux服务器可以高效地处理大量并发请求,提供快速、可靠的Web服务。 数据库服务器:Linux也广泛用于数据库服务器,如MySQL、PostgreSQL和Oracle等数据库管理系统在Linux上运行良好。Linux的稳定性和安全性使得它成为存储和管理敏感数据的理想平台。 邮件服务器:Lin
688 5
|
传感器 人工智能 物联网
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发
C 语言在计算机科学中尤其在硬件交互方面占据重要地位。本文探讨了 C 语言与硬件交互的主要方法,包括直接访问硬件寄存器、中断处理、I/O 端口操作、内存映射 I/O 和设备驱动程序开发,以及面临的挑战和未来趋势,旨在帮助读者深入了解并掌握这些关键技术。
385 6
|
缓存 Linux 开发者
Linux内核中的并发控制机制:深入理解与应用####
【10月更文挑战第21天】 本文旨在为读者提供一个全面的指南,探讨Linux操作系统中用于实现多线程和进程间同步的关键技术——并发控制机制。通过剖析互斥锁、自旋锁、读写锁等核心概念及其在实际场景中的应用,本文将帮助开发者更好地理解和运用这些工具来构建高效且稳定的应用程序。 ####
292 5
|
存储 安全 关系型数据库
Linux系统在服务器领域的应用与优势###
本文深入探讨了Linux操作系统在服务器领域的广泛应用及其显著优势。通过分析其开源性、安全性、稳定性和高效性,揭示了为何Linux成为众多企业和开发者的首选服务器操作系统。文章还列举了Linux在服务器管理、性能优化和社区支持等方面的具体优势,为读者提供了全面而深入的理解。 ###
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
472 0