原来 mmap 这么简单

308次阅读  |  发布于3年以前

在《[一文看懂零拷贝技术] 》中我们介绍了 零拷贝技术 的原理,而且我们知道 mmap 也是零拷贝技术的一种实现。在本文中,我们主要介绍 mmap 的原理。

一、传统的读写文件

一般来说,修改一个文件的内容需要如下3个步骤:

过程如图 1 所示:

如果使用代码来实现上面的过程,代码如下:


read(fd, buf, 1024);  // 读取文件的内容到buf
...                   // 修改buf的内容
write(fd, buf, 1024); // 把buf的内容写入到文件

从图 1 中可以看出,页缓存(page cache) 是读写文件时的中间层,内核使用 页缓存 与文件的数据块关联起来。所以应用程序读写文件时,实际操作的是 页缓存

二、使用 mmap 读写文件

从传统读写文件的过程中,我们可以发现有个地方可以优化:如果可以直接在用户空间读写 页缓存,那么就可以免去将 页缓存 的数据复制到用户空间缓冲区的过程。

那么,有没有这样的技术能实现上面所说的方式呢?答案是肯定的,就是 mmap

使用 mmap 系统调用可以将用户空间的虚拟内存地址与文件进行映射(绑定),对映射后的虚拟内存地址进行读写操作就如同对文件进行读写操作一样。原理如图 2 所示:

前面我们介绍过,读写文件都需要经过 页缓存,所以 mmap 映射的正是文件的 页缓存,而非磁盘中的文件本身。由于 mmap 映射的是文件的 页缓存,所以就涉及到同步的问题,即 页缓存 会在什么时候把数据同步到磁盘。

Linux 内核并不会主动把 mmap 映射的 页缓存 同步到磁盘,而是需要用户主动触发。同步 mmap 映射的内存到磁盘有 4 个时机:

三、mmap的使用方式

下面我们介绍一下怎么使用 mmapmmap 函数的原型如下:

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

下面介绍一下 mmap 函数的各个参数作用:

介绍完 mmap 函数的原型后,我们现在通过一个简单的例子介绍怎么使用 mmap

int fd = open(filepath, O_RDWR, 0644);                           // 打开文件
void *addr = mmap(NULL, 8192, PROT_WRITE, MAP_SHARED, fd, 4096); // 对文件进行映射

在上面例子中,我们先通过 open 函数以可读写的方式打开文件,然后通过 mmap 函数对文件进行映射,映射的方式如下:

mmap 函数会返回映射后的内存地址,我们可以通过此内存地址对文件进行读写操作。我们通过图 3 展示上面例子在内核中的结构:

四、总结


本文主要介绍了 mmap 的原理和使用方式,通过本文我们可以知道,使用 mmap 对文件进行读写操作时可以减少内存拷贝的次数,并且可以减少系统调用的次数,从而提高对读写文件操作的效率。

由于内核不会主动同步 mmap 所映射的内存区中的数据,所以在某些特殊的场景下可能会出现数据丢失的情况(如断电)。为了避免数据丢失,在使用 mmap 的时候可以在适当时主动调用 msync 函数来同步映射内存区的数据。

Copyright© 2013-2020

All Rights Reserved 京ICP备2023019179号-8