Win32: записывать в файл без буферизации?

Мне нужно создать новый дескриптор файла, чтобы любые операции записи с этим дескриптором немедленно записывались на диск.

Дополнительная информация: дескриптор будет унаследованным STDOUT дочернего процесса, поэтому мне нужно, чтобы любой результат этого процесса сразу записывался на диск.

Изучая документацию CreateFile, флаг FILE_FLAG_WRITE_THROUGH выглядел как именно то, что мне нужно:

Операции записи не пройдут любой промежуточный кеш, они пойдут непосредственно на диск.

Я написал очень базовую тестовую программу и, ну, она не работает. Я использовал флаг в CreateFile, а затем использовал WriteFile(myHandle,...) в длинном цикле, написав около 100 МБ данных примерно за 15 секунд. (Я добавил несколько Sleep()).

Затем я создал профессиональную среду мониторинга, состоящую из непрерывного удара "F5" в проводнике. Результаты: файл остается на 0 КБ, а затем переходит на 100 МБ примерно в момент окончания тестовой программы.

Следующее, что я пробовал, - это вручную очистить файл после каждой записи, FlushFileBuffers(myHandle). Это делает наблюдаемый размер файла хорошим и стабильным, как и ожидалось.

Мой вопрос в том, должен ли FILE_FLAG_WRITE_THROUGH сделать это без вручную, очистив файл? Я что-то упускаю? В программе "real world" я не могу очистить файл, потому что у меня нет никакого контроля над дочерним процессом, который его использует.

Также существует флаг FILE_FLAG_NO_BUFFERING, который я не могу использовать по той же причине - без контроля над процессом, использующим дескриптор, поэтому я не могу вручную выровнять записи, как требуется этим флагом.

EDIT: Я сделал отдельный проект специально для просмотра того, как изменяется размер файла. Он использует класс .NET FileSystemWatcher. Я также пишу меньше данных - всего около 100 кБ.

Здесь вывод. Проверьте секунды на отметках времени.

Версия встроенного no-buffers:

25.11.2008 7:03:22 PM: 10230 bytes added.
25.11.2008 7:03:31 PM: 10240 bytes added.
25.11.2008 7:03:31 PM: 10240 bytes added.
25.11.2008 7:03:31 PM: 10240 bytes added.
25.11.2008 7:03:31 PM: 10200 bytes added.
25.11.2008 7:03:42 PM: 10240 bytes added.
25.11.2008 7:03:42 PM: 10240 bytes added.
25.11.2008 7:03:42 PM: 10240 bytes added.
25.11.2008 7:03:42 PM: 10240 bytes added.
25.11.2008 7:03:42 PM: 10190 bytes added.

... и "принудительная (ручная) флеш-версия (FlushFileBuffers() вызывается каждые ~ 2,5 секунды):

25.11.2008 7:06:10 PM: 10230 bytes added.
25.11.2008 7:06:12 PM: 10230 bytes added.
25.11.2008 7:06:15 PM: 10230 bytes added.
25.11.2008 7:06:17 PM: 10230 bytes added.
25.11.2008 7:06:19 PM: 10230 bytes added.
25.11.2008 7:06:21 PM: 10230 bytes added.
25.11.2008 7:06:23 PM: 10230 bytes added.
25.11.2008 7:06:25 PM: 10230 bytes added.
25.11.2008 7:06:27 PM: 10230 bytes added.
25.11.2008 7:06:29 PM: 10230 bytes added.

Ответ 1

Я тоже был укушен этим в контексте регистрации аварий.

FILE_FLAG_WRITE_THROUGH гарантирует, что отправляемые данные будут отправлены в файловую систему до возврата WriteFile; он не гарантирует, что он действительно отправлен на физическое устройство. Так, например, если вы выполняете ReadFile после WriteFile в дескрипторе с этим флагом, вам гарантируется, что чтение вернет байты, которые вы написали, независимо от того, получили ли они данные из кеша файловой системы или из базовое устройство.

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

База знаний имеет краткую, но информативную статью о различии.

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

  • Используйте API CreatePipe для создания наследуемого анонимного канала.
  • Используйте CreateFile для создания файла с FILE_FLAG_NO_BUFFERING.
  • Предоставить записываемый дескриптор канала дочернему элементу как его STDOUT.
  • В родительском процессе прочитайте с читаемого дескриптора канала в выровненных буферах и напишите их в файл.

Ответ 2

Это старый вопрос, но я подумал, что могу добавить немного к нему. На самом деле все здесь, я считаю, ошибаются. Когда вы пишете в поток с записью и небуферизованным-io, он записывает на диск, но НЕ обновляет метаданные, связанные с файловой системой (например, какой исследователь показывает вам).

Вы можете найти хорошую ссылку на этот материал здесь http://winntfs.com/2012/11/29/windows-write-caching-part-2-an-overview-for-application-developers/

Приветствия,

Грег

Ответ 3

Возможно, вы можете быть достаточно удовлетворены FlushFileBuffers:

Сбрасывает буферы указанного файла и заставляет все буферизованные данные записываться в файл.

Обычно WriteFile и WriteFileEx записывают данные во внутренний буфер, который операционная система записывает на диск или канал связи на регулярной основе. Функция FlushFileBuffers записывает всю буферную информацию для указанного файла на устройство или канал.

Они предупреждают, что вызов flush, чтобы очистить буферы много, неэффективен - и лучше просто отключить кеширование (т.е. Tim answer):

Из-за взаимодействия с кэшированием диска внутри системы функция FlushFileBuffers может быть неэффективной при использовании после каждой записи на устройство с дисководом, когда многие записи выполняются отдельно. Если приложение выполняет множественную запись на диск, а также необходимо обеспечить, чтобы критические данные записывались на постоянный носитель, приложение должно использовать небуферизованный ввод-вывод, а не часто называть FlushFileBuffers. Чтобы открыть файл для небуферизованного ввода-вывода, вызовите функцию CreateFile с помощью FILE_FLAG_NO_BUFFERING и FILE_FLAG_WRITE_THROUGH флаги. Это предотвращает кеширование содержимого файла и сбрасывает метаданные на диск при каждой записи. Для получения дополнительной информации см. CreateFile.

Если это не высокопроизводительная ситуация, и вы не будете слишком часто промываться, тогда FlushFileBuffers может быть достаточно (и проще).

Ответ 4

Размер, который вы просматриваете в Проводнике, может быть не полностью синхронизирован с тем, что файловая система знает о файле, поэтому это не лучший способ его измерения. Так получилось, что FlushFileBuffers заставит файловую систему обновлять информацию, на которую смотрит Explorer; закрытие и повторное открытие могут в конечном итоге сделать то же самое.

Помимо проблем с кэшированием диска, упомянутых другими, писать через делает то, что вы надеялись сделать. Это просто, что выполнение "dir" в каталоге может не отображаться в актуальном состоянии.

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

Ответ 5

Возможно, вы захотите рассмотреть возможность сопоставления памяти этим файлом. Как только вы напишете область памяти, файл будет обновлен.

Отображение файлов API Win API