mmap 函数:原理与使用(含代码)

参考

中文资料
英文资料
使用场景

介绍

除了标准的文件 IO,例如 open, read, write,内核还提供接口允许应用将文件 map 到内存。使得内存中的一个字节与文件中的一个字节一一对应。

  • 优势
    • 读写文件避免了 read()write() 系统调用,也避免了数据的拷贝。
    • 除了潜在的页错误,读写 map 后的文件不引起系统调用或者上下文切换。就像访问内存一样简单。
    • 多个进程 map 同一个对象,可以共享数据。
    • 可以直接使用指针来跳转到文件某个位置,不必使用 lseek() 系统调用。
  • 劣势
    • 内存浪费。由于必须要使用整数页的内存。
    • 导致难以找到连续的内存区域
    • 创建和维护映射和相关的数据结构的额外开销。在大文件和频繁访问的文件中,这个开销相比 read write 的 copy 开销小。
mmap 原理

使用方法

函数原型为:

#include <sys/mman.h>

void * mmap (void *addr,
             size_t len,
             int prot,
             int flags,
             int fd,
             off_t offset);
  • addr
    这个参数是建议地址(hint),没有特别需求一般设为0。这个函数会返回一个实际 map 的地址。

  • len
    文件长度。

  • prot
    表明对这块内存的保护方式,不可与文件访问方式冲突。
    PROT_NONE
    无权限,基本没有用
    PROT_READ
    读权限
    PROT_WRITE
    写权限
    PROT_EXEC
    执行权限

  • flags
    描述了映射的类型。
    MAP_FIXED
    开启这个选项,则 addr 参数指定的地址是作为必须而不是建议。如果由于空间不足等问题无法映射则调用失败。不建议使用。
    MAP_PRIVATE
    表明这个映射不是共享的。文件使用 copy on write 机制映射,任何内存中的改动并不反映到文件之中。也不反映到其他映射了这个文件的进程之中。如果只需要读取某个文件而不改变文件内容,可以使用这种模式。
    MAP_SHARED
    和其他进程共享这个文件。往内存中写入相当于往文件中写入。会影响映射了这个文件的其他进程。与 MAP_PRIVATE冲突。

  • fd
    文件描述符。进行 map 之后,文件的引用计数会增加。因此,我们可以在 map 结束后关闭 fd,进程仍然可以访问它。当我们 unmap 或者结束进程,引用计数会减少。

  • offset
    文件偏移,从文件起始算起。

如果失败,mmap 函数将返回 MAP_FAILED

页面对齐

内存拥有独立权限的最小单位就是页。因此,mmap 的最小单位也是页。addroffset 参数都必须页对齐,len 会被 roundup。被 roundup 的多余的内存会以 \0 填充。对这一部分的写入操作不会影响文件。我们可以通过如下方式获取本机的页面大小:

#include <unistd.h>

long page_size = sysconf(_SC_PAGESIZE);

代码实现

因为项目需求并发写入,为了提高性能,实现了一个可以并行写入的mmap。
具体代码可以查看我的Github

遇到的问题

  1. 写入时发生错误
bus error(core dump)

stackoverflow 大佬的原话:

You are creating a new zero sized file, you can't extend the file size with mmap. You'll get a bus error when you try to write outside the content of the file.

因此使用 lseek 先把文件扩展到需要的大小。

  // solve the bus error problem:
  // we should allocate space for the file first.
  lseek(fd, size_lim_-1, SEEK_SET);
  write(fd,"",1);
  1. 文件权限设置
int fd = open(file_path_.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0644);

打开的时候忘了加 0644 设置权限。

  1. 文件大小

由于文件最初利用 lseek 扩张了一次,中间有大量的'\0'段。导致文件在验证中出错,而且打开缓慢。

// resize the file to actual size
truncate(file_path_.c_str(), cur_pos_.load());

在析构函数中增加 truncate 解决。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • UNIX网络编程第二卷进程间通信对mmap函数进行了说明。该函数主要用途有三个:1、将一个普通文件映射到内存中,通...
    宇文黎琴阅读 3,543评论 0 4
  • 如果你看完书中的所有例子,你很可能已经做完你的实验和在已经越狱的iPhone上的研究。因为和许多人一样,几乎所有的...
    fishmai0阅读 16,298评论 2 42
  • 转自认真分析mmap:是什么 为什么 怎么用 阅读目录mmap基础概念mmap内存映射原理mmap和常规文件操作的...
    扎Zn了老Fe阅读 847评论 0 3
  • 子曰:“君子不器。” 译文:孔子说:“君子不像器皿一般(只有一定的用途。)” 不器就是不成为某一个定型的人才。不被...
    甜_sweet阅读 188评论 0 1