Linux mmap内存映射介绍以及性能

2021-05-11  本文已影响0人  superme_

什么是mmap

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系,函数原型如下

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。如下图所示

mmap除了可以减少read,write等系统调用以外,还可以减少内存的拷贝次数,比如在read调用时,一个完整的流程是操作系统读磁盘文件到页缓存,再从页缓存将数据拷贝到read传递的buffer里,而如果使用mmap之后,操作系统只需要将磁盘读到页缓存,然后用户就可以直接通过指针的方式操作mmap映射的内存,减少了从内核态到用户态的数据拷贝。

mmap适合于对同一块区域频繁读写的情况,比如一个64M的文件存储了一些索引信息,我们需要频繁修改并持久化到磁盘,这样可以将文件通过mmap映射到用户虚拟内存,然后通过指针的方式修改内存区域,由操作系统自动将修改的部分刷回磁盘,也可以自己调用msync手动刷磁盘。

内核角度分析mmap原理,这篇博客图文并茂,直接参考就好了。 linux内存映射mmap原理分析 - 鱼思故渊的专栏 - CSDN博客 blog.csdn.net/yusiguyuan/…

映射只不过是映射到虚拟内存,不用担心映射的文件太大。

每个进程的4G内存空间只是虚拟内存空间,每次访问内存空间的某个地址,都需要把地址翻译为实际物理内存地址

所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。

二、mmap参数说明

映射文件或设备到内存中,取消映射就是munmap函数。

语法如下:

void *mmap(void *addr, size_t length, int prot, int flags,

int fd, off_t offset);

int munmap(void *addr, size_t length);

该函数主要用途有三个:

将普通文件映射到内存中,通常在需要对文件进行频繁读写时使用,用内存读写取代I/O读写,以获得较高的性能;

将特殊文件进行匿名内存映射,为关联进程提供共享内存空间;

为无关联的进程间的Posix共享内存(SystemV的共享内存操作是shmget/shmat)

我们来看下函数的入参选择:

➢ 参数addr:

指向欲映射的内存起始地址,通常设为 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。

➢ 参数offset:

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

返回说明

成功执行时,mmap()返回被映射区的指针,munmap()返回0。失败时,mmap()返回MAP_FAILED[其值为(void *)-1],munmap返回-1。errno被设为以下的某个值。

EACCES:访问出错

EAGAIN:文件已被锁定,或者太多的内存已被锁定

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

EINVAL:一个或者多个参数无效

ENFILE:已达到系统对打开文件的限制

ENODEV:指定文件所在的文件系统不支持内存映射

ENOMEM:内存不足,或者进程已超出最大内存映射数量

EPERM:权能不足,操作不允许

ETXTBSY:已写的方式打开文件,同时指定MAP_DENYWRITE标志

SIGSEGV:试着向只读区写入

SIGBUS:试着访问不属于进程的内存区

三、mmap与直接IO(read、write)的效率比较

无论是通过mmap方式或read/write方式访问文件在内核中都必须经过两个缓存:一个是用address_space来组织的以页为基础的缓存;一个是以buffer来组织的缓存,但实际上这两个缓存只是同一个缓冲池里内容的不同组织方式。当需要从文件读写内容时,都经过 address_space_operation中提供的函数也就是说路径是一致的。如果是用read/write方式,用户须向内核指定要读多少,内核再把得到的内容从内核缓冲池拷向用户空间;写也须要有一个大致如此的过程。

mmap的优势在于通过把文件的某一块内容映射到用户空间上,用户可以直接向内核缓冲池读写这一块内容,这样一来就少了内核与用户空间的来回拷贝所以通常更快。但 mmap方式只适用于更新、读写一块固定大小的文件区域而不能做像诸如不断的写内容进入文件导到文件增长这类的事。

二者的主要区别在于,与mmap和memcpy相比,read和write执行了更多的系统调用,并做了更多的复制。read和write将数据从内核缓冲区中复制到应用缓冲区,然后再把数据从应用缓冲区复制到内核缓冲区。而mmap和memcpy则直接把数据从映射到地址空间的一个内核缓冲区复制到另一个内核缓冲区。当引用尚不存在的内存页时,这样的复制过程就会作为处理页错误的结果而出现(每次错页读发生一次错误,每次错页写发生一次错误)

所以他们两者的效率的比较就是系统调用和额外的复制操作的开销和页错误的开销之间的比较,哪一个开销少就是哪一个表现更好。用mmap可以避免与读写打交道,这样可以简化程序逻辑,有利于编程实现。

系统调用mmap()可以将某文件映射至内存(进程空间),如此可以把对文件的操作转为对内存的操作,以此避免更多的lseek()与read()、write()操作,这点对于大文件或者频繁访问的文件而言尤其受益。但有一点必须清楚:mmap的addr与offset必须对齐一个内存页面大小的边界,即内存映射往往是页面大小的整数倍,否则maaped_file_size%page_size内存空间将被闲置浪费。映射时,指定offset最好是内存页面大小的整数倍。

内存文件映射的使用:

(1)大数据量文件的读取,有效的提高磁盘和内存间数据通信的性能;

(2)进程间快速的共享内存,实现进程间高效的通信。

内存映射文件性能高于普通IO的原因:

内存文件映射和普通的文件IO都是要通过文件系统和硬盘驱动拷贝数据到内存中,内存文件映射数据越大越快主要是:

(1)实际拷贝数据前,需要建立映射信息,内存文件映射已经提前准备好了映射关系,内核调度好了进程内的内存块,交付给内核进行了预先处理,内存文件映射会消耗掉一些时间。

(2)实际拷贝时候,内存文件映射将磁盘数据直接拷贝到用户进程内存空间只进行了一次拷贝,而普通的IO是先将文件拷贝到内核缓存空间,然后才拷贝到用户进程内存空间,进行了两次拷贝。

下面是一个使用普通的fread函数和内存映射文件函数,读取不同大小的磁盘文件的性能分析表:

综合:当读写磁盘文件的数据较小(少于1MB)时候,使用内存文件映射和普通IO是差异很小的,所以建议使用普通IO就可以了;当很多文件的大小在几十MB, 几百MB, 或者1GB以上的文件数据需要进行较频繁的访问,或者一开始需要全部加载这些大文件的时候,那么就需要考虑使用内存文件映射了。

上一篇下一篇

猜你喜欢

热点阅读