代码之家  ›  专栏  ›  技术社区  ›  TypeIA

为什么在用O\u DIRECT写文件时从文件中读回数据会损坏

  •  1
  • TypeIA  · 技术社区  · 7 年前

    我有一个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,这里省略了诸如正确的错误检查和资源管理等细节。这不是我的实际程序,但它显示了相同的行为。)

    如果你移除 O\ U直接 ::open() 写线程中的标志,问题就会消失,“readback mismatch”消息永远不会打印。

    我能理解为什么我的 ::read() 可能返回0或其他值,表示我已经读取了已刷新到磁盘的所有内容。但我不明白为什么它会执行一个看似成功的读取,但数据不是我写的。很明显我遗漏了什么,但那是什么?

    1 回复  |  直到 7 年前
        1
  •  3
  •   Barmar    7 年前

    所以, O_DIRECT has some additional constraints 这可能不是你想要的:

    应用应避免混合 O\ U直接 和正常I/O相同 即使文件系统正确地处理了 单独使用任一模式。

    相反,我认为 O_SYNC

    O\U同步 提供同步的I/O文件完整性完成,这意味着 写操作将把数据和所有相关的元数据刷新到 O_DSYNC 完整性完成,意味着写操作将把数据刷新到 底层硬件,但将只刷新 完成后续读取操作所必需的 成功。数据完整性的完成可以减少 不需要的应用程序所需的磁盘操作 文件完整性的保证。