我有一个C ++程序,它使用POSIX API编写一个用O_DIRECT
打开的文件。同时,另一个线程通过不同的文件描述符从同一文件读回。我注意到,从文件中读回的数据偶尔会包含所有零,而不是我写的实际数据。为什么是这样?
这是C ++中的MCVE 17。用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或者某些东西来表示我已经读过已被刷新到磁盘的所有内容。但是我无法理解为什么它会执行看似成功的读取,但是除了我写的内容以外的数据。显然我错过了什么,但它是什么?
所以,O_DIRECT
has some additional constraints可能无法满足您的需求:
应用程序应避免将
O_DIRECT
和普通I / O混合到同一文件中,尤其是同一文件中的重叠字节区域。即使文件系统在这种情况下正确处理一致性问题,整体I / O吞吐量也可能比单独使用任一模式慢。
相反,我认为O_SYNC
可能会更好,因为它确实提供了预期的保证:
O_SYNC
提供同步I / O文件完整性完成,这意味着写操作会将数据和所有相关元数据刷新到底层硬件。O_DSYNC
提供同步I / O数据完整性完成,这意味着写入操作将数据刷新到底层硬件,但只会刷新允许后续读取操作成功完成所需的元数据更新。数据完整性完成可以减少不需要保证文件完整性完成的应用程序所需的磁盘操作数。