У меня есть программа на C ++, которая использует POSIX API для записи файла, открытого с O_DIRECT
.Одновременно другой поток читает обратно из того же файла через другой файловый дескриптор.Я заметил, что иногда данные, считанные из файла, содержат все нули, а не фактические данные, которые я написал.Почему это?
Вот MCVE на C ++ 17.Компилировать с g++ -std=c++17 -Wall -otest test.cpp
или эквивалентным.Извините, я не могу сделать это немного короче.Все, что он делает, это записывает 100 МБ постоянных байтов (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. Это не моя настоящая программа, но она показывает то же самое поведение.)
Я тестирую на Linux 4.15.0 с SSD.Примерно в 1/3 времени, когда я запускаю программу, печатается сообщение «несоответствие чтения».Иногда это не так.Во всех случаях, если я проверю foo
после того, как факт обнаружит, что он содержит правильные данные.
Если вы удалите O_DIRECT
из флагов ::open()
в потоке записи, проблема исчезнети сообщение «несоответствие при чтении» никогда не печатается.
Я могу понять, почему мой ::read()
может возвращать 0 или что-то, что указывает на то, что я уже прочитал все, что было записано на диск.Но я не могу понять, почему он выполняет то, что кажется успешным, но с данными, отличными от того, что я написал.Я явно что-то упускаю, но что это?