C++ 中的内存映射文件(mmap)使用与实现

在现代 C++ 开发中,内存映射文件(memory‑mapped files)提供了一种高效读取大文件或共享数据的方式。与传统的文件 I/O(fread/fwrite、fstream)相比,mmap 能够把文件映射到进程地址空间,使得对文件内容的访问像访问普通内存一样简单,且由操作系统负责缓存与磁盘同步。

1. 为什么要使用 mmap?

  • 性能提升:文件内容被映射后,读取不需要系统调用,数据直接在内存中访问,减少了数据拷贝开销。
  • 延迟加载:文件被按需加载,只有实际访问的页面才会被调入内存,降低初始加载时间。
  • 共享内存:不同进程可以映射同一文件,内存页会被共享,适用于 IPC。
  • 大文件支持:对于超过 2GB 的文件,mmap 可以一次性映射整个文件,避免缓冲区大小限制。

2. 关键 API(POSIX 版本)

int   open(const char *pathname, int flags, ...);        // 打开文件
void *mmap(void *addr, size_t length, int prot, int flags,
           int fd, off_t offset);                       // 映射文件
int   munmap(void *addr, size_t length);                 // 解除映射
int   close(int fd);                                     // 关闭文件
  • prot:访问权限,如 PROT_READ | PROT_WRITE
  • flags:映射属性,如 MAP_SHARED(共享)或 MAP_PRIVATE(写时复制)。
  • offset:文件偏移,通常为 0。

3. 简单示例:读取文本文件

#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <iostream>
#include <cstring>

int main() {
    const char *file = "sample.txt";

    // 1. 打开文件
    int fd = open(file, O_RDONLY);
    if (fd < 0) {
        perror("open");
        return 1;
    }

    // 2. 获取文件大小
    struct stat st;
    if (fstat(fd, &st) < 0) {
        perror("fstat");
        close(fd);
        return 1;
    }
    size_t len = st.st_size;

    // 3. 映射文件
    void *map = mmap(nullptr, len, PROT_READ, MAP_PRIVATE, fd, 0);
    if (map == MAP_FAILED) {
        perror("mmap");
        close(fd);
        return 1;
    }

    // 4. 处理内容(这里直接输出)
    std::cout.write(static_cast<char*>(map), len);
    std::cout << std::endl;

    // 5. 解除映射并关闭文件
    munmap(map, len);
    close(fd);
    return 0;
}

运行步骤

  1. g++ -std=c++17 mmap_example.cpp -o mmap_example 编译。
  2. 准备一个 sample.txt,填入内容。
  3. 运行 ./mmap_example 即可看到文件内容。

4. 写入操作(MAP_SHARED)

如果想在映射区域修改内容并同步回磁盘,需要使用 MAP_SHARED 并确保 PROT_WRITE

void *map = mmap(nullptr, len, PROT_READ | PROT_WRITE,
                 MAP_SHARED, fd, 0);

此时,对映射内存的写操作会直接写回文件。请注意:

  • 写时复制(MAP_PRIVATE)不会影响原文件。
  • 必须确保映射内存的生命周期与文件描述符保持一致。

5. 注意事项

场景 说明
多线程 每个线程共享同一映射区域时,要注意同步,避免数据竞争。
大文件 mmap 能一次映射整个文件,但如果文件非常大,仍需根据系统内存情况决定是否分段映射。
关闭文件 关闭 fd 后映射仍然有效,但不建议在映射后立即关闭。
取消映射 必须先 munmapclose,否则会导致资源泄漏。

6. 高级用法:映射一个文件的某个区域

void *map = mmap(nullptr, page_size, PROT_READ,
                 MAP_PRIVATE, fd, offset);

通过调整 offsetlength,可以只映射文件的一部分,常用于内存映射数据库、日志文件切片等场景。

7. 小结

内存映射文件是 C++ 开发中提高 I/O 性能的重要手段。掌握 mmap 的基本调用方式与使用场景,能够在处理大文件、实现高性能服务器或跨进程共享数据时,获得显著的性能优势。需要注意的是,尽管 mmap 简化了文件访问,但它也带来了与内存管理相关的细节,如页面错误、同步与一致性问题,使用时应结合具体业务场景慎重选择。

发表评论