我有一个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或某些信息来指示我已经读取了已刷新到磁盘的所有内容。但是我不明白为什么它会执行看起来很成功的读取,但是使用我写的数据以外的数据。显然我想念什么,但这是什么?