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

执行结果如下:

 

相关文章
|
8天前
|
存储 缓存 监控
Linux缓存管理:如何安全地清理系统缓存
在Linux系统中,内存管理至关重要。本文详细介绍了如何安全地清理系统缓存,特别是通过使用`/proc/sys/vm/drop_caches`接口。内容包括清理缓存的原因、步骤、注意事项和最佳实践,帮助你在必要时优化系统性能。
124 78
|
12天前
|
Linux Shell 网络安全
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
本指南介绍如何利用 HTA 文件和 Metasploit 框架进行渗透测试。通过创建反向 shell、生成 HTA 文件、设置 HTTP 服务器和发送文件,最终实现对目标系统的控制。适用于教育目的,需合法授权。
46 9
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
|
8天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
60 13
|
9天前
|
Ubuntu Linux C++
Win10系统上直接使用linux子系统教程(仅需五步!超简单,快速上手)
本文介绍了如何在Windows 10上安装并使用Linux子系统。首先,通过应用商店安装Windows Terminal和Linux系统(如Ubuntu)。接着,在控制面板中启用“适用于Linux的Windows子系统”并重启电脑。最后,在Windows Terminal中选择安装的Linux系统即可开始使用。文中还提供了注意事项和进一步配置的链接。
27 0
|
7月前
|
Linux Shell 调度
【Linux】7. 进程概念
【Linux】7. 进程概念
72 3
|
7月前
|
存储 缓存 Linux
【Linux】进程概念(冯诺依曼体系结构、操作系统、进程)-- 详解
【Linux】进程概念(冯诺依曼体系结构、操作系统、进程)-- 详解
|
4月前
|
Linux Shell 调度
【在Linux世界中追寻伟大的One Piece】Linux进程概念
【在Linux世界中追寻伟大的One Piece】Linux进程概念
48 1
|
6月前
|
存储 Linux Shell
Linux进程概念(上)
冯·诺依曼体系结构概述,包括存储程序概念,程序控制及五大组件(运算器、控制器、存储器、输入设备、输出设备)。程序和数据混合存储,通过内存执行指令。现代计算机以此为基础,但面临速度瓶颈问题,如缓存层次结构解决内存访问速度问题。操作系统作为核心管理软件,负责资源分配,包括进程、内存、文件和驱动管理。进程是程序执行实例,拥有进程控制块(PCB),如Linux中的task_struct。创建和管理进程涉及系统调用,如fork()用于创建新进程。
70 3
Linux进程概念(上)
|
6月前
|
存储 Shell Linux
Linux进程概念(下)
本文详细的介绍了环境变量和进程空间的概念及其相关的知识。
42 0
Linux进程概念(下)
|
6月前
|
Linux Shell 调度
Linux进程概念(中)
本文详细解析了Linux进程的不同状态,包括运行、阻塞、挂起,以及僵尸和孤儿进程的概念。讨论了进程优先级的重要性,以及操作系统如何通过活动队列、过期队列和优先级管理进程调度。
54 0