【发布时间】:2019-02-19 14:58:50
【问题描述】:
我有一个 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 确实包含正确的数据之后检查它。
如果您从写入线程中的::open() 标志中删除O_DIRECT,问题就会消失,并且永远不会打印“回读不匹配”消息。
我可以理解为什么我的::read() 可能会返回 0 或其他东西,表明我已经读取了所有已刷新到磁盘的内容。但我不明白为什么它会执行看似成功的读取,但使用的数据不是我写的。显然我遗漏了一些东西,但它是什么?
【问题讨论】:
-
正如the
openmanual page 中提到的,您确实需要使用O_SYNC来同步写入文件。您所体验的可能是异步效果以及写入和读取线程之间的数据竞争。 -
O_SYNC | O_DIRECT的行为与仅O_DIRECT的行为相同。