0 引言
Linux 提供了非常强大的 mmap(2) 系统调用; 它使开发人员能够将任何内容直接映射到进程虚拟地址空间 (VAS)。 此内容包括文件数据、硬件设备(适配器)内存区域,或只是通用内存区域。 在本文中,我们将只关注使用 mmap(2) 将常规文件的内容映射到进程 VAS 中。
1 mmap简介
mmap(2)系统调用接口如下
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
如果将文件的给定区域,从给定的偏移量offset和长度length字节映射到我们的进程虚拟地址空间 VAS; 下图描绘了想要实现的目标的简单视图:
为了实现这个文件映射到进程的VAS,可使用 mmap(2) 系统调用。
关于mmap的相关参数解释如下
fd 由open系统调用等打开的文件描述符
offset 文件映射的偏移位置
length 文件映射的长度
addr 向内核提示应该在进程 VAS 中的哪个位置创建映射; 建议在这里传递 0 (NULL),允许操作系统决定新映射的位置
prot 给定区域的内存保护标志位
flag 是一个称为标志的位掩码; 有几个标志,它们影响映射的许多属性。
上述prot具体由如下四个标志构成
PROT_NONE | 没有访问权限 |
PROT_READ | 拥有读权限 |
PROT_WRITE | 拥有写权限 |
PROT_EXEC | 拥有可执行权限 |
2 文件和匿名映射
mmap映射可分为:文件映射和匿名区映射。
mmap中的flag标记由如下概念
MAP_SHARED: 映射是共享的; 其他进程可能同时在同一个映射上工作(事实上,这是实现 IPC 机制(共享内存)的通用方式)。 在文件映射的情况下,如果写入内存区域,则更新底层文件! (您可以使用 msync(2) 来控制将内存写入刷新到底层文件。)
MAP_PRIVATE: 这建立了一个私有映射; 如果它是可写的,它意味着 COW 语义)。 私有的文件映射区域不会对底层文件进行写入。 实际上,私有文件映射在 Linux 上很常见:这正是在开始执行进程时,加载器将二进制可执行文件的文本和数据以及 进程使用的所有共享库的文本和数据。
3 mmap的优点
mmap(2) 通过在内部将包含文件数据(从存储设备读入)的内核页面缓存页面直接映射到进程虚拟地址空间来设置文件映射。这是零拷贝技术的一种实现。
下图更加形象的说明mmap的工作过程
一个map并不是一个copy。
4 代码示例
该部分代码可参考
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define handle_error(msg) \
do { perror(msg); exit(EXIT_FAILURE); } while (0)
int main(int argc, char *argv[]) {
char *addr = nullptr;
int fd{-1};
fd = open(argv[1], O_RDONLY);
if (fd == -1)
handle_error("open");
addr = static_cast<char*>(mmap(NULL, 10 * sysconf(_SC_PAGE_SIZE), PROT_READ,
MAP_SHARED, fd, 0));
if (addr == MAP_FAILED)
handle_error("mmap");
while (true) {
sleep(10);
}
exit(EXIT_SUCCESS);
}
譬如我运行两次
./main test1.cc
./main test1.cc
在我的机器上通过fincore test1.cc显示如下结果
RES PAGES SIZE FILE
4K 1 818B test1.cc
5 总结
本文总结了mmap的基本概念和使用。