【博客170】IPC(进程间通信)——共享内存(一)

内容: 记录mmap

函数接口:

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

形参说明:

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

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

参数prot:映射区域的保护方式。可以为以下几种方式的组合:
1、PROT_EXEC 映射区域可被执行
2、PROT_READ 映射区域可被读取
3、PROT_WRITE 映射区域可被写入
4、PROT_NONE 映射区域不能存取

参数flags:影响映射区域的特性,在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。

1、MAP_FIXED:如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不用
2、MAP_SHARED:对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享, 原来文件
   会被改变。
3、MAP_PRIVATE:对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on 
   write)对此区域作的任何修改都不会写回原来的文件内容。当共享的对象的虚拟存储区域为私有对象
   时,修改只会被本进程中改变。
 
4、MAP_ANONYMOUS:建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。

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

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

参数fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,
       fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件
       进行映射,可以同样达到匿名内存映射的效果。参数offset:文件映射的偏移量,通常设置为0,
       代表从文件最前方开始对应,offset必须是分页大小的整数倍。

返回值:

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

错误代码errno:

1、EBADF 参数fd 不是有效的文件描述词
2、EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有
   PROT_WRITE以及该文件要能写入。
3、EINVAL 参数start、length 或offset有一个不合法。
4、EAGAIN 文件被锁住,或是有太多内存被锁住。

映射释放函数:
int munmap(void *addr, size_t length);

mmap的意义:

主要做的事情:
mmap存在的意义就是减少了用户态和内核态数据的拷贝,这样能够大大提高效率。
mmap函数把一个文件映射到进程的地址空间,通过直接操作进程空间地址来操作文件。

原理:
实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以
采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作
而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可
以实现不同进程间的文件共享

优点:
1、对文件的读取操作跨过了页缓存,减少了数据的拷贝次数,用内存读写取代I/O读写,提高文件读取效率。

2、实现了用户空间和内核空间的高效交互方式。两空间的各自修改操作可以直接反映在映射的区域内,从而
   被对方空间及时捕捉。

3、提供进程间共享内存及相互通信的方式。而且是一种比较快的方式

4、减少缺页异常的发生:如果进程A和进程B都映射了区域C,A第一次读取C时通过缺页从磁盘复制文件页到
   内存中;但当B再读C的相同页面时,虽然也会产生缺页异常,但是不再需要从磁盘中复制文件过来,直 
   接使用已经保存在内存中的文件数据。

5、可用于实现高效的大规模数据传输。内存空间不足,是制约大数据操作的一个方面,解决方案往往借助
   硬盘空间协助操作,补充内存的不足。但是进一步会造成大量的文件I/O操作,极大影响效率。这问题
   可以通过mmap映射很好的解决。但凡是需要用磁盘空间代替内存的时候,mmap都可以发挥其功效。

在这里插入图片描述

mmap注意点:

1、mmap映射区域大小必须是物理页大小的整倍数(通常是4k字节)。原因是,内存的最小粒度是页,而进程
虚拟地址空间和内存的映射也是以页为单位。为了匹配内存的操作,mmap从磁盘到虚拟地址空间的映射也必须
是页。

2、映射建立之后,即使文件关闭,映射依然存在。因为映射的是磁盘的地址,非文件本身,和文件句柄无关。
   同时可用于进程间通信的有效地址空间不完全受限于被映射文件的大小,因为是按页映射。
   (主要原因是已经建立了进程页表与文件磁盘地址之间的关系了)

mmap异常使用情况:

异常情况1 :一个文件的大小是x>4096,但是x<8192字节,mmap函数从文件的起始位置开始,映射相应字节
           数量到虚拟内存中

实际映射:映射进程虚拟地址区域的大小需要满足整页大小,因此mmap函数执行后,实际映射到虚拟内存区域
8192个 字节,x~8191的字节部分用零填充。

实际操作:
(1)读/写前5000个字节(0~4999),会返回操作文件内容。
(2)读字节5000~8191时,结果全为0。写5000~8191时,进程不会报错,但所写的内容不会写入原文件中。
(3)读/8192以外的磁盘部分,会返回一个SIGSECV错误。


异常情况2:一个文件的大小是x字节,mmap函数从一个文件的起始位置开始,映射2x字节到虚拟内存中,
即映射大小超过了原始文件的大小。(x>4096字节,x<8192字节)

实际映射:由于文件的大小是x字节,其对应的两个物理页。那么这两个物理页都是合法可以读写的,但超出
x的部分不会体现在原文件中。由于程序要求映射2x字节,而文件只占两个物理页,因此8192字节~2x字节
都不能读写,操作时会返回异常。如下图所示:

实际操作:
(1)进程可以正常读/写被映射的前x字节,写操作的改动需要等待磁盘的写回

(2)对于x~8192字节,进程可以进行读写过程,不会报错。但是内容在写入前均为0,另外,写入后不反映
     在文件中。

(3)对于8192~2x字节,进程不能对其进行读写,会报SIGBUS错误。

(4)对于2x以外的字节,进程不能对其读写,会引发SIGSEGV错误。


异常情况3 : 一个文件初始大小为0,使用mmap操作映射了一定的大小
         

实际操作:      
(1)对文件进行读写操作,由于文件大小为0,并没有合法的物理页对应,会返回SIGBUS错误。

(2)若每次读写前,先增加文件的大小,那么在文件大小内部的操作就是合法的。
     例如,文件扩充x字节,就能使用x字节的空间,只要保证扩充的大小在映射的一定大小里面

代码实现:
【博客171】IPC(进程间通信)——共享内存(二)

大四学生一枚,如果文章有错误的地方,欢迎在下方提出,每条评论我都会去认真看并回复,同时感谢指正的前辈。有喜欢C/C++,linux的同学欢迎私信一起讨论学习。

发布了214 篇原创文章 · 获赞 41 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_43684922/article/details/104464243
今日推荐