用O_DIRECT写入文件时为什么回读时数据损坏

时间:2018-09-14 19:14:39

标签: c++ linux io posix

我有一个C ++程序,该程序使用POSIX API编写以O_DIRECT打开的文件。同时,另一个线程正在通过不同的文件描述符从同一文件读回。我注意到偶尔从文件中读取的数据包含全零,而不是我编写的实际数据。为什么会这样?

这是C ++ 17中的MCVE。使用g++ -std=c++17 -Wall -otest test.cpp或同等版本进行编译。抱歉,我似乎无法将其缩短。它所做的就是在一个线程中将100 MiB恒定字节(0x5A)写入文件,然后在另一个线程中读取它们,如果任何回读字节不等于0x5A,则会打印一条消息。

  

警告,此MCVE将删除并重写名为foo的当前工作目录中的任何文件。

#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <thread>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>

constexpr size_t CHUNK_SIZE = 1024 * 1024;
constexpr size_t TOTAL_SIZE = 100 * CHUNK_SIZE;

int main(int argc, char *argv[])
{
    ::unlink("foo");

    std::thread write_thread([]()
    {
        int fd = ::open("foo", O_WRONLY | O_CREAT | O_DIRECT, 0777);
        if (fd < 0) std::exit(-1);

        uint8_t *buffer = static_cast<uint8_t *>(
            std::aligned_alloc(4096, CHUNK_SIZE));

        std::fill(buffer, buffer + CHUNK_SIZE, 0x5A);

        size_t written = 0;
        while (written < TOTAL_SIZE)
        {
            ssize_t rv = ::write(fd, buffer,
                std::min(TOTAL_SIZE - written, CHUNK_SIZE));
            if (rv < 0) { std::cerr << "write error" << std::endl; std::exit(-1); }
            written += rv;
        }
    });

    std::thread read_thread([]()
    {
        int fd = ::open("foo", O_RDONLY, 0);
        if (fd < 0) std::exit(-1);

        uint8_t *buffer = new uint8_t[CHUNK_SIZE];

        size_t checked = 0;
        while (checked < TOTAL_SIZE)
        {
            ssize_t rv = ::read(fd, buffer, CHUNK_SIZE);
            if (rv < 0) { std::cerr << "write error" << std::endl; std::exit(-1); }

            for (ssize_t i = 0; i < rv; ++i)
                if (buffer[i] != 0x5A)
                    std::cerr << "readback mismatch at offset " << checked + i << std::endl;

            checked += rv;
        }
    });

    write_thread.join();
    read_thread.join();
}

(出于MCVE的考虑,此处省略了诸如正确的错误检查和资源管理之类的细节。这不是我的实际程序,但它表现出相同的行为。)

我正在使用SSD的Linux 4.15.0上进行测试。大约有1/3的时间我运行程序,打印出“回读不匹配”消息。有时并非如此。在所有情况下,如果我在发现foo之后发现它确实包含正确的数据,就会发现它。

如果从写线程中的O_DIRECT标志中删除::open(),问题就消失了,“回读不匹配”消息将永远不会打印。

我可以理解为什么我的::read()可能返回0或某些信息来指示我已经读取了已刷新到磁盘的所有内容。但是我不明白为什么它会执行看起来很成功的读取,但是使用我写的数据以外的数据。显然我想念什么,但这是什么?

0 个答案:

没有答案