Почему данные повреждены при чтении из файла, поскольку они записываются с помощью O_DIRECT? - PullRequest
0 голосов
/ 14 сентября 2018

У меня есть программа на 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 или что-то, что указывает на то, что я уже прочитал все, что было записано на диск.Но я не могу понять, почему он выполняет то, что кажется успешным, но с данными, отличными от того, что я написал.Я явно что-то упускаю, но что это?

1 Ответ

0 голосов
/ 14 сентября 2018

Итак, O_DIRECT имеет некоторые дополнительные ограничения , которые могут не соответствовать требованиям:

Приложениям следует избегать смешивания O_DIRECT и нормальных I /O к тому же файлу, и особенно к перекрывающимся областям байтов в том же файле.Даже когда файловая система правильно обрабатывает проблемы когерентности в этой ситуации, общая пропускная способность ввода-вывода, вероятно, будет медленнее, чем при использовании одного из этих режимов.

Вместо этого, я думаю, O_SYNC может быть лучше, посколькуон обеспечивает ожидаемые гарантии:

O_SYNC обеспечивает синхронизированное завершение целостности файла ввода-вывода, что означает, что операции записи сбрасывают данные и все связанные метаданные на базовое оборудование.O_DSYNC обеспечивает синхронизированное завершение целостности данных ввода / вывода, что означает, что операции записи будут сбрасывать данные на базовое оборудование, но будут сбрасывать только обновления метаданных, которые необходимы для успешного завершения последующей операции чтения.Завершение целостности данных может уменьшить количество дисковых операций, которые требуются для приложений, которым не требуются гарантии завершения целостности файлов.

...