我有一个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或其他值,表示我已经读取了已刷新到磁盘的所有内容。但我不明白为什么它会执行一个看似成功的读取,但数据不是我写的。很明显我遗漏了什么,但那是什么?