Как обеспечить запись данных на физический носитель?

У меня есть программа, которая вызывается по сценарию. Эта программа записывает большой объем данных в файл на диске, а затем завершает свою работу. Как только он будет запущен, скрипт отключит питание всей системы.

Проблема, с которой я столкнулся, заключается в том, что файл не записывается полностью. Если это файл размером 4 ГБ, на самом деле на диске будет только около 2 ГБ, когда я буду просматривать его позже. Единственный способ, которым я смог надежно убедиться, что все данные записаны, - это засыпать программу на небольшой период перед выходом, но это действительно плохой и ненадежный прием, который я не хочу использовать. Вот пример кода моей последней попытки:

int main () {
    FILE *output;
    output = fopen("/logs/data", "w");

    [fwrite several GiB of data to output]

    fflush(output);

    int fdo = open("/logs", O_RDONLY);
    fsync(fdo);

    fclose(output);
    close(fdo);

    return 0;
}

Сначала я попытался создать свой ФАЙЛ с файловым дескриптором и вызвать fsync () для используемого дескриптора (/ logs / data), однако это вызвало ту же проблему. Согласно спецификации для fsync (2) :

Вызов fsync () не обязательно гарантирует, что запись в каталоге, содержащем файл, также достигла диска. Для этого также необходима явная fsync () для файлового дескриптора каталога.

который привел меня к приведенному выше коду, создав специальный дескриптор файла только для каталога, содержащего мой файл данных, и вызвал для него fsync (). Однако результаты были такими же. Я действительно не понимаю, почему это происходит, потому что fsync () должен блокировать:

Вызов блокируется до тех пор, пока устройство не сообщит о завершении передачи.

кроме того, как вы можете видеть, я добавил fflush () в ФАЙЛ, думая, что, возможно, fsync () синхронизирует только данные, которые ранее были сброшены, но это не повлияло на ситуацию.

Мне нужно как-то убедиться, что данные действительно были записаны на физический носитель, прежде чем завершить программу, и я не уверен, как это сделать. Я вижу, что есть некоторые файлы, такие как / sys / block / [device] / [partition] / stat, которые могут сказать мне, сколько грязных блоков осталось записать, и я могу дождаться, пока это значение достигнет 0, но это не так. кажется отличным способом решить, что должно быть простой проблемой, и, кроме того, если на диске работает какая-либо другая программа, я не хочу ждать, пока они синхронизируют свои данные, так как меня волнует только целостность этот конкретный файл и файл stat не различаются.

ИЗМЕНИТЬ В соответствии с предложением я дважды попытался выполнить fsync (), сначала в файле, а затем в каталоге:

int main () {
    FILE *output;
    int fd = open("/logs/data", O_WRONLY | O_CREAT, 660);
    output = fdopen(fd, "w");

    [fwrite several GiB of data to output]

    fsync(fd);
    int fdo = open("/logs", O_RDONLY);
    fsync(fdo);

    fclose(output);
    close(fd);
    close(fdo);

    return 0;
}

Это произвело интересный результат. При использовании файла размером 4 ГБ (4294967296 байт) фактический размер данных на диске составлял 4294963200, что как раз на 1 страницу файла (4096 байт) отличается от общего значения. Кажется, что это очень близкое к рабочему решению, но оно все еще не гарантирует каждый байт данных.


person J. Doe    schedule 07.01.2021    source источник
comment
Вы пробовали использовать fsync(fileno(output))? Открытие второго экземпляра файла может не завершить передачу. Ссылка: stackoverflow.com/questions/3167298/   -  person Halt State    schedule 07.01.2021
comment
@ 4386427 Я добавил их для наглядности, но на результат это не влияет. Изначально я исключил их из своего примера, поскольку они не казались важными для проблемы, поскольку буферы очищались и записывались непосредственно перед этим, а питание отключалось, в любом случае выгружая память.   -  person J. Doe    schedule 07.01.2021
comment
@HaltState Я не открываю второй экземпляр файла, я открываю содержащий его каталог и выполняю для него fsync в соответствии со спецификацией fsync, которую я цитировал в своем сообщении. Кроме того, как я упоминал в своем сообщении, я также попытался создать свой тип FILE, используя fd для местоположения fdopen (fdo, w) и fsync'd на fdo, так что в тот раз это был единственный экземпляр   -  person J. Doe    schedule 07.01.2021
comment
Прочтите это от Раймонда Чена и помните, что устройства хранения лгут.   -  person Mark Benningfield    schedule 07.01.2021
comment
@MarkBenningfield эта статья освещает проблему, но она специфична для Windows, тогда как моя - для Linux. более того, мне не нужны какие-то глобальные настройки, просто в этом конкретном случае для этого файла я хочу, чтобы диск был на самом деле на физическом носителе.   -  person J. Doe    schedule 07.01.2021
comment
Я думаю, дело в том, что fsyncing каталога - это то, что вы должны сделать в дополнение к fsync'у самого файла, чего не делает ваш текущий код.   -  person Nate Eldredge    schedule 07.01.2021
comment
@NateEldredge см. Отредактируйте вопрос, двойной fsyncing почти работает, но все еще не совсем   -  person J. Doe    schedule 07.01.2021
comment
Возможно, в буфере stdio output есть незаписанные данные, которые не записываются до вызова fclose. Попробуйте позвонить fflush(output); до fsync(fd);.   -  person Ian Abbott    schedule 07.01.2021
comment
Для меня манипуляции с каталогом выглядят как дурацкие. Вам нужно сделать ровно три вещи: fflush (вывод), fsync (fd) и fclose (вывод) в указанном порядке, ни с чем между ними.   -  person n. 1.8e9-where's-my-share m.    schedule 07.01.2021
comment
Корпоративный SSD имеет защиту от потери питания, которая гарантирует запись в кэш записи. Если у вас его нет, вам понадобится поддержка FUA, чтобы гарантировать запись данных перед отключением питания.   -  person stark    schedule 07.01.2021
comment
Как вы отключаете питание (безопасно, ожидая записи буферизованной записи, или опасно / быстро с большой опрометчивостью)?   -  person Brendan    schedule 07.01.2021
comment
Если это работает, но вы не можете этого объяснить, скорее всего, это действительно не работает. Возможно, попробуйте syncfs(fd) после (или вместо) fsync(fd) вместо того, чтобы возиться с каталогом.   -  person n. 1.8e9-where's-my-share m.    schedule 07.01.2021
comment
@IanAbbott поцарапал мой предыдущий комментарий, кажется, что он исправлен иногда. С тех пор я запускал его много раз, к сожалению, не слишком быстро, так как включение и выключение питания и запись 4GiB происходит медленно, но это примерно 80/20 в зависимости от того, записывает ли он каждый байт или отсутствует ~ 200 МБ   -  person J. Doe    schedule 07.01.2021
comment
@ n.'pronouns'm. Я пробовал использовать syncfs ранее, но кажется, что он недоступен в системе, которую я использую   -  person J. Doe    schedule 07.01.2021
comment
Не уверен, актуально ли это, но какую файловую систему вы используете?   -  person Nate Eldredge    schedule 07.01.2021
comment
@NateEldredge Ext4   -  person J. Doe    schedule 07.01.2021
comment
синхронизация и сброс только гарантируют, что данные будут записаны из блочного кеша на устройство. Это не гарантирует, что устройство записало данные на носитель. Вот для чего нужны FUA (принудительный доступ к устройствам) и предотвращение потери питания на SSD. Ничто из вышеперечисленного не гарантирует, что запущенные процессы записали все свои данные в блочный кеш. Для этого и предназначена команда выключения Linux. Он сообщает всем запущенным процессам о завершении. Если вы просто выключаете питание, не завершая работу, значит, вы намеренно теряете данные.   -  person stark    schedule 07.01.2021
comment
@stark, так что делает команда выключения Linux для принудительной записи и как я могу сделать это в своей программе? Если я не ошибаюсь, команда unmount заставит то же самое произойти и с смонтированным диском.   -  person J. Doe    schedule 07.01.2021
comment
Вы не можете размонтировать диск с открытыми файлами. Цель размонтирования - пометить файловую систему как чистую, чтобы ее не нужно было проверять при монтировании.   -  person stark    schedule 08.01.2021
comment
@stark хорошо, но, как вы можете видеть, я закрываю все файлы, которые использовал, поэтому после этого почему я не могу выполнить любой процесс, который unmount использует для блокировки, пока данные не достигнут энергонезависимого хранилища?   -  person J. Doe    schedule 08.01.2021
comment
umount не предполагает, что вы выключаете питание, поэтому не делает этого. Смотрите мой ответ ниже.   -  person stark    schedule 08.01.2021


Ответы (3)


Рассматривали ли вы возможность передачи флагов O_DIRECT и / или O_SYNC в open ()? Из руководства по open () :

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

O_SYNC
Операции записи в файл будут завершены в соответствии с требованиями завершения целостности синхронизированного ввода-вывода ...

В этой статье о LWN (уже довольно старой) также представлены некоторые рекомендации по обеспечению целостности данных.

person Rachid K.    schedule 07.01.2021
comment
В моей системе нет O_DIRECT, но я попытался использовать O_SYNC. проблема, с которой я столкнулся, заключается в том, что он просто снижает производительность (из-за моего количества записей). Даже после пакетирования всех операций записи в блоки различного размера прирост производительности, казалось, ограничивался примерно 7 минутами для 4 ГиБ данных независимо от насколько больше я сделал свои блоки. Теперь, похоже, он работает нормально, но это похоже на половину времени, чтобы сделать то, что я сейчас делаю, а затем добавить команду сна 10-15 секунд, чтобы `` убедиться '', что все написано, поэтому, пока он работает, это намного хуже, чем взлом - person J. Doe; 08.01.2021

Чтобы гарантировать, что все данные записываются в энергонезависимое хранилище, команда shutdown выдает вызов sd_shutdown для каждого диска. См. https://elixir.bootlin.com/linux/v4.10.17/source/drivers/scsi/sd.c#L3338

Это вызывает две команды SCSI: SYNC_CACHE и START_STOP_UNIT, которые преобразуются в соответствующее действие на базовом устройстве. Для устройств SATA это означает перевод диска в режим ожидания, в котором диск замедляется.

person stark    schedule 07.01.2021

В вашем скрипте:

  • Необязательно: запустите /bin/sync, чтобы сбросить изменения в кэше страниц в хранилище.

  • Отмонтируйте целевую файловую систему (umount /mountpoint) или перемонтируйте ее только для чтения.

    Если целевая файловая система включает корень (/) и / или системные двоичные файлы или библиотеки (/usr), вы не можете размонтировать файловую систему. В этом случае перемонтируйте целевую файловую систему только для чтения (mount -o remount,ro /mountpoint).

  • Запустите shutdown -h now, чтобы выключить систему

Это стандартная последовательность, которая гарантирует, что файловые системы находятся в чистом состоянии при завершении работы и что все изменения попадают на носитель.

person Glärbo    schedule 08.01.2021