首页 > 解决方案 > 写入映射文件缓冲区时出现轻微页面错误

问题描述

在写入文件由磁盘支持的 mmapped 文件缓冲区时,我注意到轻微的页面错误。

我对mmap的理解是,对于文件映射,页缓存有文件的数据,页表会更新指向页缓存中的文件数据。这意味着在第一次写入 mmapped 缓冲区时,必须更新页表以指向页缓存,我们可能会看到轻微的页错误。但是,正如我下面的基准测试所示,即使在预先对 mmapped 缓冲区进行故障处理之后,我在进行随机写入时仍然会看到轻微的页面错误。

buf请注意,仅当我在写入 mmapped 缓冲区之间写入随机缓冲区(在下面的基准测试中)时,才会显示这些轻微的页面错误。另请注意,当使用非磁盘支持的 tmpfs 时,这些小页面错误似乎不会发生。

所以我的问题是为什么我们在写入磁盘支持的文件时会看到这些小页面错误?

这是基准:

#include <iostream>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <fstream>
#include <sys/mman.h>
#include <sys/resource.h>

int main(int argc, char** argv) {
    // Command line parsing.
    if (argc != 2) {
        std::cout << "Usage: ./bench <path to file>" << std::endl;
        exit(1);
    }
    std::string filepath = argv[1];
  
    // Open and truncate the file to be of size `FILE_LEN`.
    int fd = open(filepath.c_str(), O_CREAT | O_TRUNC | O_RDWR, 0664);
    const size_t FILE_LEN = (1 << 26); // 64MiB
    if (fd < 0) {
        std::cout << "open failed: " << strerror(errno) << std::endl;
        exit(1);
    }
    if (ftruncate(fd, FILE_LEN) < 0) {
        std::cout << "ftruncate failed: " << strerror(errno) << std::endl;
        exit(1);
    }

    // `mmap` the file and pre-fault it.
    char* ptr = static_cast<char*>(mmap(nullptr, FILE_LEN, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, fd, 0));
    if(ptr == MAP_FAILED) {
       std::cout << "mmap failed: " << strerror(errno) << std::endl;
       exit(1);
    }
    memset(ptr, 'A', FILE_LEN);
  
    // Create a temporary buffer to clear the cache.
    constexpr size_t BUF_LEN = (1 << 22); // 4MiB
    char* buf = new char[BUF_LEN];
    memset(buf, 'B', BUF_LEN);

    std::cout << "Opened file " << fd << ", pre faulted " << ptr[rand() % FILE_LEN] << " " << buf[rand() % BUF_LEN]<< std::endl;
  
  
    // Run the actual benchmark
    rusage usage0, usage1;
    getrusage(RUSAGE_THREAD, &usage0);
    unsigned int x = 0;
    for (size_t i = 0; i < 1000; ++i) {
      char c = i % 26 + 'a';
      const size_t WRITE_SIZE = 1024;
      size_t start = i*WRITE_SIZE;
      if (start + WRITE_SIZE >= FILE_LEN) {
        start %= FILE_LEN;
      }
      memset(ptr + start, c, WRITE_SIZE);
      x += ptr[start];


      char d = (c * 142) % 26 + 'a';
      for (size_t k = 0; k < BUF_LEN; ++k) {
        buf[k] = d;
      }
      x += buf[int(d)];
    }
    std::cout << "Using the buffers " << ptr[rand() % FILE_LEN] << " " << buf[rand() % BUF_LEN] << " " << x << std::endl;
    getrusage(RUSAGE_THREAD, &usage1);

    std::cout << "========================" << std::endl;
    std::cout << "Minor page faults = " << usage1.ru_minflt - usage0.ru_minflt << std::endl;
    std::cout << "========================" << std::endl;

    return 0;
}

./bench "/dev/shm/test.txt"在 /dev/shm/ 使用 tmpfs 文件系统的地方运行,基准测试始终显示 0 个次要页面错误。运行./bench "/home/username/test.txt"时,上面的基准测试显示了大约 200 个次要页面错误。即我用上面的命令看到这样的输出:

========================
Minor page faults = 231
========================

请注意,增加基准测试中的迭代次数也会增加次要页面错误的数量(例如,将迭代次数从 1000 更改为 2000 会导致大约 450 个次要页面错误)。

标签: c++linuxmmap

解决方案


推荐阅读