トップ回答者
IRQL_NOT_LESS_OR_EQUAL ブルースクリーン時の書き込みファイルの挙動について

質問
-
バックグラウンドでファイルを加工し、元ファイルを消すという処理を実装しています
表題のエラーが発生すると加工ファイルが壊れ、元ファイルもなくなっているという現象が発生します
疑問1)ファイルの中身は壊れているのに、ファイルエントリ操作は正しく完了しているということは、NTFS では当たり前なのでしょうか
疑問2)Windows7 では、ファイルの中身がすべて NUL(='\0' )か、ファイルの後半がNULかのどちらかですが、Windows10 では、なぜかヘッダ部分の一部までは書き込まれ、それ以降がNULという現象になります(ヘッダは可変長のため、サイズが決まっているわけではなく、ある可変長テキストを書き込んだ部分までは書かれていて、それ以降が決まってNULになります)
この処理実装では、一旦作成した加工ファイルを改めてオープンし、内容が正しいかどうかを検証してから元ファイルを削除しています
ロジック的には、ファイルが消えているということから、ファイル内容の検証もOKだったと考えますが、ファイルのエントリ操作だけは正しくて、内容の書き込みが正しくない、という現象が不可解です
どなたか、これらの疑問に心当たりのある方もしくはわかる方はいらっしゃいますでしょうか
回答
-
FlushFileBuffers 関数の引数には何を渡していますでしょうか?
FILE* 型はそのままでは渡せませんので、下記のようにファイルハンドルに変換する必要があるようです。
FILE *fp = fopen("ファイル名", "w"); ...略... FlushFileBuffers(reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(fp)))); fclose(fp); // ファイルを閉じる
また、_commit 関数を使うと下記のようにもかけます。
(_commit 関数の内部では FlushFileBuffers 関数が呼び出されています)
FILE *fp = fopen("ファイル名", "w"); fwrite("test", 1, 4, fp); fseek(fp, 0, SEEK_SET); fwrite("abc", 1, 3, fp); fflush(fp); _commit(_fileno(fp)); fclose(fp);
上記のようなコードでディスクにフラッシュしても、やはりファイルは正しく書き込まれないのでしょうか?参考サイト: https://msdn.microsoft.com/ja-jp/library/17618685.aspx
- 編集済み kenjinoteMVP 2017年12月12日 6:57
- 回答としてマーク ishizawa 2017年12月12日 10:53
すべての返信
-
Bug Check Code 0xA (IRQL_NOT_LESS_OR_EQUAL) の意味、ちゃんと理解してますか?
--------------------------------------
Bug Check 0xA: IRQL_NOT_LESS_OR_EQUAL
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/bug-check-0xa--irql-not-less-or-equal
--------------------------------------そもそもその壊れたファイルへのアクセス時に、IRQL_NOT_LESS_OR_EQUAL での BSOD が発生しているのでしょうか?
(その対象ファイルへの I/O 処理の延長上で落ちたのか、それとも全く別のところで落ちたのか?)
またファイルの中身が壊れているとは、どーやって確認したのでしょうか?
なぜ IRQL_NOT_LESS_OR_EQUAL での BSOD と、ファイル破壊を同一線上に考えるのか、その理由が私には全く分かりませんでした。ファイル I/O を行っている最終に BSOD が発生したなら、そのファイルは正常にクローズされないことになるので、中身がおかしくなる可能性はありますが、それと IRQL_NOT_LESS_OR_EQUAL の BSOD に直接的な関係があるかどうかは、まったく別次元の話です。
極端な話ファイルの書き込み処理中であれば、BSOD 時の Bug Check Code に関係無く、ファイルの中身がおかしくなる可能性はあると思います。IRQL_NOT_LESS_OR_EQUAL での BSOD が起きているなら、その時の Call Stack の状態を提示してみては?
(ファイル I/O を気にすということは、File Syatem Mini Filter Driver を作成している?)P.S.
第三者が読んでも状況が理解できるよう、質問文を書かれることをお勧めします。 -
状況を補足しておきます(本文より長いかも)
まずここで実装された当該プログラムは、ユーザがログオンしたらバックグラウンドで常駐し、指定されたフォルダ内にあるファイルを順次、加工ファイルに置き換えるというものです
実際の運用環境ではネットワークデバイスでこのエラーが発生し、その当時稼働していた当該プログラムが生成したファイルが軒並み壊れているという状況が発生します(この現象以外で壊れたことは一度もありません)
これはたまにしか起こらないのでテスト環境では、NotMyFault.exe を使って強制的にエラーを起こしています
プログラムのロジック上、元ファイルを消す処理は、加工ファイルの内容が正しい場合(ヘッダチェックや内容がNUL埋めになっていないなどをチェック)に実行されます
仮に、加工ファイルを作成中にBSODが起こったのであれば、元ファイルは残るはずなので、元ファイルが消えているということから、チェックが通過したという判断ができます(実際、処理は複数ファイルに渡って進行し、複数のファイルが壊れるという現象になっています)
BSODから自動復帰した後で、これらの加工ファイルが格納されているフォルダを見ると、元ファイルがなくなっているファイルに対応する加工ファイルが、そもそもチェックでエラーとなるファイルになってしまっています
そもそも、プログラムで加工ファイルをチェックしてOKだったからファイルを削除しているのに、ファイルが消えることだけは再現し、加工ファイルの内容が正しく反映されていない、というのが(疑問1)です(逆に言うと、ファイル内容が正しく反映されていないようなら、ファイルの削除も反映されないということがあってもよさそうだということ)
更にこのチェックエラーを詳細に調べてみると、Windows10とWindows7では壊れ方に違いにあることがわかり、(疑問2)となっています
仮にこの加工ファイルのフォーマットを(ヘッダ、ボディ、フッタ)構成だとすると、Windows7では、ファイル先頭からすべてのデータがNULになっているか、ボディの途中からNULになっている、という壊れ方をしています(正しいフォーマットではボディとフッタはNUL埋めになることはありません)
これが、Windows10だと、ヘッダの途中からNULになるという壊れ方をします
それも、固定バイト位置からNULになるのではなく、ヘッダ内に格納する可変長文字列を書き込んだ直後からNULになるという壊れ方です
これがどうも不思議だということです
何らかの情報をお待ちしています
-
そもそも、プログラムで加工ファイルをチェックしてOKだったからファイルを削除しているのに、ファイルが消えることだけは再現し、加工ファイルの内容が正しく反映されていない、というのが(疑問1)です(逆に言うと、ファイル内容が正しく反映されていないようなら、ファイルの削除も反映されないということがあってもよさそうだということ)
Windows はディスクの遅延書き込みがデフォルト有効となっています。
「加工先ファイルを読み出して OK だったから、加工元ファイルを消す」としたいのであれば、CreateFile で FILE_FLAG_WRITE_THROUGH を指定するか、FlushFileBuffers を使うなど、書き込み完了の保証を得ておく必要がありそうです。(ファイル削除のオペレーションはディスクアクセスとしてかなり軽量な操作なので、キャッシュされないのだと予想しますが、裏付けは取っていません)
更にこのチェックエラーを詳細に調べてみると、Windows10とWindows7では壊れ方に違いにあることがわかり、(疑問2)となっています
推測に過ぎませんが、ディスクの遅延書き込みの実装が違うのでしょうね。
- 編集済み AzuleanMVP, Moderator 2017年12月11日 10:39
-
「加工先ファイルを読み出して OK だったから、加工元ファイルを消す」としたいのであれば、CreateFile で FILE_FLAG_WRITE_THROUGH を指定するか、FlushFileBuffers を使うなど、書き込み完了の保証を得ておく必要がありそうです。
FILE_FLAG_WRITE_THROUGH については、ファイルIOにCランタイム(要はFILE*)を使っているため、採用が難しいと考えています(実際、CreateFile で作成した HANDLE から FILE* を作成した手順でやってみると、正常処理でも、ファイル書き込みができませんでした)
FlushFileBuffers を最後にクローズする直前で実施してみると、変化がありました
ファイル内容は(上記のたとえでは、ボディとフッタ部分)は正しく書き込まれるようになりましたが、ヘッダの一部分は正しく書き込まれておらず0埋めとなっていました
その部分はファイル全体のハッシュ値を格納しているのですが、フッタを書いた後、ヘッダに戻って書き出すという処理が無視されたような恰好です
まるで、ファイルIOの単位がレイヤー化していて、シーケンシャルに出力した部分は先にフラッシュされ、シークして書いたものは後からフラッシュされる、という処理になっているかのようです
いずれにせよ、ご提案頂いたことに感謝します
- 編集済み ishizawa 2017年12月11日 16:13
-
FILE_FLAG_WRITE_THROUGH については、ファイルIOにCランタイム(要はFILE*)を使っているため、採用が難しいと考えています(実際、CreateFile で作成した HANDLE から FILE* を作成した手順でやってみると、正常処理でも、ファイル書き込みができませんでした)
CランタイムではBSODなどシステム停止は考慮されていないはずです。Cラインタイムなど高レベルレイヤーでふわっと開発するのか、OSの提供するなるべく低レベルレイヤーで可能な限り細かく制御するのか、まず開発方針を明確にすることをお勧めします。
一応、Windows VistaからはCreateFileTransacted / DeleteFileTransactedといったAPIも登場はしています。(廃止の方向だそうですが。)
-
Cランタイムを使用しているのは、内部でオープンソースのフレームワークを利用しており、それがランタイム依存だからです(対応環境が Win, Linux, Mac 等、幅広くカバーしているため、標準ライブラリで書かれているということです)
もともとの方針自体はランタイム中心で、どうしてもOSごとの機能が必要な場合だけ、#ifdef でソースを切り分けるという方針だったのです
ところが、Windowsだけ不安定なOS状態になっており、その運用環境でも最低限加工元ファイルが残るようにしろというお達しがでたので、壊れ方の特徴がどこに由来するのか原因の特定と、Windows専用の対応策を講じるという状況になっているわけです
一応、Windows VistaからはCreateFileTransacted / DeleteFileTransactedといったAPIも登場はしています。(廃止の方向だそうですが。)
廃止予定のAPIは採用しないという開発方針です -
ヘッダーの部分は「fopen しなおしているので、別途 Flush が必要」ということはないですよね…。
同じ FILE* に対して、fseek してから fwrite しています
厳密に言うと、2種類のハッシュを書き込んでいるのですが、そのうち1つ目はちゃんと書き込まれていて、2つ目はNULのままだったということです
本当に不思議な現象です
また、FlushFileBuffers がディスクにフラッシュされたことの保証にはならないということが証明されてしまい、決め手にならなかったのはとても残念です
- 編集済み ishizawa 2017年12月12日 1:11 言い回しが変だった
-
今回のケースではファイルの書き込み中であったため、その対象ファイル内のデータが破壊されたと考えられますが、その時に物理ストレージへのファイルの書き込みが完了していたか否かは、検証されている方法だけでは不十分だと思います。
すでに Azulean さんが指摘されていますが、一般的なファイル I/O は System Cache 経由で行われるため、アプリケーションからのファイル書き込み処理も System Cache に書き込まれた時点で「完了した」とみなされます。
その後 Lazy Write (遅延書き込み) により物理ストレージへの書き込みが行われますが、その段階では既にアプリケーション側から見ればファイルの書き込み処理は完了している訳ですから、その場合はご指摘されている現象が起きても全く不思議ではない、と私には思えます。
(この辺の動作に関しては、その概要が CreateFile API の "Caching Behavior" で説明されています。)今回のケースでは、ファイル I/O は Win32 API セットではなく C Run-time を使用されているとのことですが、この場合 CreateFile API での FILE_FLAG_WRITE_THROUGH および FILE_FLAG_NO_BUFFERING フラグに相当する制御を行うのは、かなり難しいかも。。。
とりあえず思いつくのは setbuf や setvbuf 関数を使っての制御ですが、これらは基本的にバッファ制御用のものだと思うので、System Cache の制御まではできないのでは。。。と思います。
(正直、やったことが無いのでわかりません。)そもそも。。。。
先の返信でも少し触れました、ファイルの書き込み中に BSOD が起きた場合、アプリケーション側からできることはほとんど無いと思うのですが、具体的に何を求めているのでしょうか?
(仮に FlushFileBuffers() が期待通りの挙動をしたとしても、その直前で BSOD が起きたら同じことでは。。。と思うのですが。) -
FlushFileBuffers 関数の引数には何を渡していますでしょうか?
FILE* 型はそのままでは渡せませんので、下記のようにファイルハンドルに変換する必要があるようです。
FILE *fp = fopen("ファイル名", "w"); ...略... FlushFileBuffers(reinterpret_cast<HANDLE>(_get_osfhandle(_fileno(fp)))); fclose(fp); // ファイルを閉じる
また、_commit 関数を使うと下記のようにもかけます。
(_commit 関数の内部では FlushFileBuffers 関数が呼び出されています)
FILE *fp = fopen("ファイル名", "w"); fwrite("test", 1, 4, fp); fseek(fp, 0, SEEK_SET); fwrite("abc", 1, 3, fp); fflush(fp); _commit(_fileno(fp)); fclose(fp);
上記のようなコードでディスクにフラッシュしても、やはりファイルは正しく書き込まれないのでしょうか?参考サイト: https://msdn.microsoft.com/ja-jp/library/17618685.aspx
- 編集済み kenjinoteMVP 2017年12月12日 6:57
- 回答としてマーク ishizawa 2017年12月12日 10:53
-
回答ではありません。あしからず。
ファイルの壊れ方にご興味をお持ちのようですが、
対象システムの役割だけに注目すると、それが詳しくわかっても意味がないかもしれません。
本件の場合、簡単に言うと処理中にデバイスが死ぬと処理結果がめちゃくちゃになるのでなんとかしたい。
というのが、主眼ではないでしょうか。
現在において他の構成では問題ないことはわかりましたが、Windows上で問題が発生したということは、
他の構成で、将来にわたって似たような障害が発生しないという断定はできないのではないでしょうか。
端的に言うと、本質的なアルゴリズムの問題を抱えている可能性が否定できません。本件の場合
1.「入力側ファイルA群」と「出力側ファイルB群」がある。
2.B群はA群を加工して作成される。で、上記の加工過程で対象デバイスに回復不能なエラーが発生した場合に全滅する
ということがわかっていて、最終的にどうしたいのかというのが課題かと思います。
一般には、その場合でも被害を最小にして、できるなら自動回復したいと考えると思いますが、
実際にはそういうアルゴリズムにはなっていない、ということだとの感想を持ちました。- 編集済み 仲澤@失業者 2017年12月12日 7:01
-
まさしくその通りでした
fflush を怠っていたため、 FlushFileBuffers を実行しても歯抜けのようになっていたようです
fflsuh を実行後に FlushFileBuffers を実行するようにしたところ、今のところ NotMyFault でのクラッシュでは、加工ファイルが破損しなくなりました
問題としてはほぼ解決です
ただ、(疑問2)の説明がつけば、このパターンの破損は、「IRQL_NOT_LESS_OR_EQUAL ブルースクリーン」由来だから、この対策で万全ですと言いやすいな~と思ってもいるわけです
重要なご指摘、大変ありがとうございました
-
FlushFileBuffer の代わりに _commit を使うという手があったとは、また新しい手法をご教授頂き、大変ありがとうございます
実際、fflush, _commit の手順で実装したところ、NotMyFault でのクラッシュでは、ファイル破損が起こらなくなりました
先に、fllush, FlushFileBuffers の手順で、破損が起こらなくなったことを確認しましたので、_commit の内部的では FlushFileBuffers が実行されているというのは合点がいきます
目前の対策としては解決したかと思います
ただ、欲を言えば、(疑問2)にあるように、壊れ方の特徴から、キャッシュの問題だということを明示的に言えれば、ユーザさんも納得させやすいかな、と思っています
-
ファイルの壊れ方にご興味をお持ちのようですが、
対象システムの役割だけに注目すると、それが詳しくわかっても意味がないかもしれません。実は、ファイル破損現象が起こるようになってきたのは、WindowsPC の置き換えがあり、その後、CreateorsUpdate を実施した前後くらいからだったのですが、原因がなかなかわからない状況で暫定対処に追われていたのです
そこで、特徴的な壊れ方から、加工アルゴリズムの不具合を疑われ、加工ファイルの検証アルゴリズムを何度も機能追加し強化したという経緯があるのですが、それでも検証OKで元ファイル削除まで進んでしまい、結果、破損した加工ファイルだけ残るという現象だったのです
その後、各種ログを収集し、ようやく、前後でBSODが発生しているらしいことを突き止め、今回の質問に至ったのです(現象が発生するPCは、近くの施設内ではなく、別会社の施設内にいたりする人のため、ログ収集などに協力してもらうのが困難だったという経緯もあります)
ただ、それを報告しても、BSODが原因とは考えられない、という指摘があり、その根拠が壊れ方の特徴にあったのです(現在も、BSODだけがこれを引き起こすとは考えていない人たちがいるのも事実です)
それで(疑問2)に何らかの答えが見つかれば、そうした人たちをも納得させやすい、と考えていたというのが真相です
-
ファイル システムが NTFS だからと言って、バージョンが異なる Windows Platform 間で NTFS ドライバが全く同一の挙動をするわけではありません。
そもそも NTFS ドライバのバイナリが異なるのだから、異なる Windows Platform 間で処理過程が同じであると考えるほうが間違いだと思います。さらに言えば、Windows 7 と Windows 10 では、NTFS ドライバにアタッチしているフィルタ ドライバ構成が全く異なります。
以下は、Windows 7 と Windows 10 で稼働しているファイル システム フィルタ ドライバの一覧です。++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
<Windows 7 で動作しているファイル システム フィルタ ドライバ>
C:\>fltmcフィルター名 インスタンス数 階層 フレーム
-------------------- ------------- ------------ -----
MpFilter 2 328000 0
luafv 1 135000 0
FileInfo 2 45000 0
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
<Windows 10 で動作しているファイル システム フィルタ ドライバ>
C:\>fltmcフィルター名 インスタンス数 階層 フレーム
-------------------- ------------- ------------ -----
WdFilter 6 328010 0
storqosflt 0 244000 0
wcifs 1 189900 0
CldFlt 0 180451 0
FileCrypt 0 141100 0
luafv 1 135000 0
npsvctrig 1 46000 0
Wof 5 40700 0
FileInfo 6 40500 0
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++上記のように、フィルタ ドライバ構成が異なるので、ファイル I/O の最終的な結果が同じであっても、その処理過程は異なる挙動になっても不思議なことではありません。
さらにさらに上記ファイル システム フィルタ ドライバの一覧では、Microsoft 製のみの構成となっていますが、大抵のセキュリティ対策ソフトはファイル システムにカスタム フィルタ ドライバをアタッチさせるので、同一の Windows Platform であっても、使用するセキュリティ対策ソフトが異なれば、(最終的な結果は同じであっても)異なる処理過程になる可能性があります。
さらにさらにさらに、3rd ベンダー製のディスク ユーティティ等では、disk ドライバに対してカスタム フィルタ ドライバをアタッチさせるケースもあるので、Windows Platform およびファイル システム フィルタ ドライバが同じであっても、ファイル I/O からディスク I/O に変換された以降の処理で、その挙動が変化する可能性もあります。
さらにさらにさらにさらに、Lazy Write の挙動はアプリ側で制御できるものではないし、その発生タイミングも気まぐれです。
ファイル I/O に関連するフラッシュ機能を呼び出したとしても、それは Lazy Write の延長上の処理として実行されるはずで、フラッシュ要求よりもリクエスト レベル (IRQL) の高い。。。例えば何かのデバイスの割り込み要求等の処理が実行されている場合は、そのフラッシュ要求は待機されるはずです。
なので、BSOD の発生タイミングを制御できない限り (制御できたら誰も苦労しないけど)、フラッシュが完了していることも保証できないはずです。以上のことから、今回の問題が fflush() や FlushFileBuffers() をコールすることで解決できたとしても、それはあくまでもそれら関数が呼び出された後に BSOD になることが前提のはずで、それらコールを行う前に BSOD が発生すれば結局同じ問題に逆戻り。。。。ということではないかと。
そもそも不定期に発生するエラー (BSOD) に対して、特定のタイミングを前提とする対策が本当に有効なのか。。。と個人的には考えています。
(質問では、IRQL_NOT_LESS_OR_EQUAL のBSOD 発生時に加工ファイルが壊れ元ファイルもなくなったとのことですが、そうならないケースもあったのでは?)
- 編集済み お馬鹿 2017年12月13日 9:05 誤記訂正
-
フィルタ構成の情報、ありがとうございました
大変参考になりました以上のことから、今回の問題が fflush() や FlushFileBuffers() をコールすることで解決できたとしても、それはあくまでもそれら関数が呼び出された後に BSOD になることが前提のはずで、それらコールを行う前に BSOD が発生すれば結局同じ問題に逆戻り。。。。ということではないかと。
そこで言われているコールが FlushFileBuffers のことを指しているのであれば、問題はありません(その場合は元ファイルが残るからです)そうではなく、フィルタ層のどれかが非同期処理であった場合であれば、またこの問題が再燃する可能性はありますので、ファイル削除タイミングを変えるというのも検討しています
(質問では、IRQL_NOT_LESS_OR_EQUAL のBSOD 発生時に加工ファイルが壊れ元ファイルもなくなったとのことですが、そうならないケースもあったのでは?)
念のため再度申し上げておきますが、元ファイルが消えるのは、プログラムが消しているのであって、BSODの結果消えるわけではありません
また、そのファイルを消せるのは、加工工程が終わり、加工ファイルの厳密なレイアウトチェックが完了・OKとなった場合だけです
加工チェックの際は、再度ファイルをオープンしなおしているので、その際はシステムキャッシュの中身を読んでいるということになろうかと思いますが、FlushFileBuffers によってキャッシュが実際にフラッシュされていれば、ディスクの内容と同じはずです
ご質問の答えですが、加工ファイルが壊れていて、元ファイルが残っているというケースは皆無でした
加工ファイルができていない場合(存在しないか破棄された場合)に元ファイルが消えるというケースも皆無でした- 編集済み ishizawa 2017年12月13日 12:28 誤記
-
> そこで言われているコールが FlushFileBuffers のことを指しているのであれば、
> 問題はありません(その場合は元ファイルが残るからです)そうではなく、
> フィルタ層のどれかが非同期処理であった場合であれば、
> またこの問題が再燃する可能性はありますので、
> ファイル削除タイミングを変えるというのも検討しています私の指摘がおかしかったようで、申しわけありませんでした。
FlushFileBuffers() API のドキュメントには同期性につての言及がなかった (少なくとも私には見つけられなかった) ので先の指摘をした訳ですが、kernelbase.dll での FlushFileBuffers() の実装を確認したところ最終的に ZwFlushBuffersFile() を呼び出しており、ZwFlushBuffersFile() のドキュメントでは同期処理であることが明記されていました。
なので FlushFileBuffers API コール後に元ファイルを消しているなら、問題無いと思います。
(きちんと確認もせずにテキトーなことを言って、申しわけありませんでした。)ただ。。。
FlushFileBuffers() API の説明にもあるように、この API を多用すると System Cache を無効化させてしまう副作用があるので、使用には注意する必要があると思います。
(System Cache が無効化されると、システム全体のパフォーマンスに影響を与える可能性があるので。)また世の中にはファイル転送を効率化し、HDD アクセス スピードを向上させるツールもあるようです。
そういった製品はデバイスへの I/O を極力抑えるために、独自のキャッシュ機能が実装されていることが考えられると思いますが、そういった製品との組み合わせも考慮する必要があるなら、フラッシュ機能の同期性がどこまで保証されるのか、あらかじめ検証しておいた方がいいかもしれません。