Linux系统编程之内存映射mmap()

前言

  • 由于在项目当中使用到了内存映射技术,因此在这里总结梳理一些内存映射技术的概念和用途,给大家提供参考的同时也加深自己对该技术的理解,一举两得。

mmap()概述

mmap()映射分为两种

  • 文件映射:文件映射将一个文件的一部分直接映射到调用mmap()进程的虚拟内存中去。当一个文件被映射之后,就可以通过对该内存区域中的字节进行操作,从而实现对文件的读写操作。这种映射被称为基于文件的映射或内存映射文件。
  • 匿名映射:匿名映射没有对应的文件。可以把它看成是一个内容被初始化为0的虚拟文件的映射。

内存映射对于其他进程的可见性分为两种

  • 私有映射(MAP_PRIVATE):在映射内容上发生的变更对其他进程不可见。
  • 共享映射(MAP_SHARED):在映射内容上发生的变更对其他共享改映射的进程可见。

上面介绍的两种映射特性可以以四种不同的方式加以组合,即:

  • 私有文件映射:主要用途是使用一个文件的内容来初始化一块内存区域。
  • 私有匿名映射:主要用途是为进程分配新的(用0填充)内存(类似与malloc(),但是malloc()默认最大128KB)。
  • 共享文件映射:主要用途有两个,一是内存映射I/O(在《UNIX环境高级编程》一书中,此功能被划分在高级I/O这一章节)。简单点解释就是,对文件进行读写,不用read()和write()。而是直接对内存进行操作。第二是类似与共享内存可以用来进行进程间通信(IPC)。
  • 共享匿名映射:主要用途为进程间共享内存(IPC)。

创建一个内存映射:mmap()

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int port, int flags, int fd, off_t offset);
			Returns starting address of mapping on success,or MAP_FAILED on error.

参数解释

  • addr:指定了映射被放置的虚拟地址。如果将addr指定为NULL,那么内核将为映射选择一个合适的地址。建议使用NULL。
  • length:指定了映射的字节数。不一定设置为系统分页大小的倍数,但是内核会以分页大小为单位来创建映射,因此实际上length会被提升为分页大小往上的倍数。
  • prot:是一个位掩码。可以使用PROT_READ(区域内容可读)、PROT_WRITE(区域内容可写)、PROT_EXEC(区域内容可执行)的一种或通过位运算或进行组合使用。
  • 返回值:成功,返回新映射的起始地址。错误,返货MAP_FAILED。

文件映射

创建一个文件映射只需执行下面两步:

  1. 使用open()来获取文件描述符。
  2. 将该文件描述符fd参数传入mmap()。
    执行上述步骤之后mmap()会将打开的文件的内容映射到调用进程的地址空间中。当mmap()被调用之后就可以关闭之前打开的文件描述符。
    offset参数指定了从文件区域中的哪个字节开始映射,应为系统分页大小的倍数。将offset指定为0即从文件的起始位置开始映射。length参数指定了映射的字节数。这两个参数共同决定了文件的哪个区域会被映射进内存,如下图所示。
    内存映射文件概览
    文件映射分为,私有和共享两种。下面我主要和大家介绍用的较多的共享文件映射。

共享文件映射

当多个进程创建了同一个文件区域的共享映射时,它们会共享同样的内存物理分页,此外,对映射内容的修改也会反映在文件上。实际上,文件被当成了该块内存区域的分页存储,如下图所示。
两个进程和一个文件的同一区域的共享映射
共享文件映射的两个主要用途:内存映射I/O和IPC。

内存映射I/O

我们可以通过访问内存中的字节来进行文件I/O操作,可以使用此技术来替代使用read()和write()来访问文件中的内存。
内存映射I/O相较于read()和write()具有两个优势。

  1. 使用内存访问来取代read()和write()系统调用可以简化应用程序的逻辑。
  2. read()和write()系统调用拥有更好的性能。原因如下:
    1)read()和write()需要两次传输:一次是在文件和内核告诉缓冲区之间,另一次是在告诉缓冲区和用户空间缓冲区之间。使用mmap()就无需进行第二次传输了。对于输入来说,一旦内核将文件的内容映射到内存之后,用户进程就可以直接使用这些数据了。对于输出来说,用户进程可以直接修改内存中的内容,之后内核会自动更新文件的内容。
    2)mmap()还可以通过减少所使用的内存来提升性能。当使用read()或write()时,数据将被保存在两个缓冲区,一个位于用户空间,另一个位于内核空间。使用mmap()时,内核空间和用户空间会共享同一个缓冲区。此外,当多个进程在同一个文件上进行I/O时,通过mmap()可以共享同一个内核缓冲区,无需建立多个缓冲区。
    此外,内存映射I/O带来的优势在大文件的频繁随机访问中最能体现出来。如果顺序访问一个文件,并且假设进行I/O时使用的缓冲区大小足以避免进行大量的I/O系统调用,那么和read()和write()相比,mmap()带来的性能提升十分有限。
共享文件映射的IPC

由于使用同一文件区域的共享映射的进程共享相同的物理内存分页,因此共享文件映射可以作为进程间通信的方式。此方法与共享内存的区别在于,共享区域中的变化会反应到底层文件上。此特性对于那些需要共享内存的内容可以在程序重启或系统重启时仍能保留的应用程序十分有用。

匿名映射

匿名映射是没有对应文件的一种映射。分为私有和共享匿名映射。

MAP_PRIVATE私有匿名映射

可以用来分配进程独有的内存块并将其中的内容初始化为0。
下面代码使用MAP_ANONYMOUS创建一个私有匿名映射。

addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(addr == MAP_FAILED)
	printf("mmap failed!\n");

其实C语言库函数malloc()的实现就是使用了私有匿名映射来分配内存块。malloc分配内存的默认大小为128KB,可以通过mallopt()这个库函数来调整参数的大小。

MAP_SHARED共享匿名映射

共享匿名映射允许相关进程(如父进程和子进程)共享一块内存区域而无需对应一个映射文件。
下面代码使用MAP_ANONYMOUS创建一个共享匿名映射。

addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if(addr == MAP_FAILED)
	return -1;

参考书籍

  • 《Linux系统编程手册》下册,第49章
  • 《UNIX环境高级编程》第14章
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值