Windowsプログラミングの問題

かなり複雑なプロジェクトの開発に参加する場合、間違ったコードを書くのは簡単です。 全体的な障害は、プロジェクトの最も暗い部分、最も複雑な部分で間違いを探し始めることです。 同時に、プロジェクト全体の基盤である最も単純なコード、つまりフレームワークが機能しない可能性があるということも頭には入りません。

この投稿では、実際に発生した2つの問題について説明します。別のストリームを作成できず、ファイルの名前を変更できないことです。 使用されるプログラミング言語:C / C ++。



スレッドを取る、恐れるな



だから、状況は最初です。



与えられた :オフィスのラップトップ(開発者ではない)の1つで、プログラムの実行中に、ある時点でCreateThread()関数が戻りコード「スレッドの作成に失敗しました。 このコマンドを処理するのに十分なストレージが利用できません。



このエラーが発生したとき、機能のかなりの部分が対象のラップトップで機能しなかったことは注目に値します。 すべてのエラーが整理され、ログを使用して結果がリモートでチェックされましたが(ラップトップにIDEをインストールしてプログラムをデバッグする可能性はありませんでした)、かなり長い時間がかかりました。 そして今、問題はストリームを作成することだけでした。



プログラムにこのタスクのための十分なメモリがないことが判明すると(リターンコードによる判断)、魔女の狩りが始まりました:すべての複雑なライブラリに疑いがかかり始めました。 これは、プログラムがGPUを使用したときに問題が明らかになったという事実によって促進されました。 CPUのみを使用した場合、問題はありませんでした。

当然、GPUでの作業を提供するライブラリーを注意深く調べ始めました。 途中で、メモリリークにつながるいくつかの重要なエラーが見つかり、修正されました。 ただし、これは元の問題を解決しませんでした。 とにかく、ある時点でストリームが作成されなくなった。



別の障害は、記憶が終わらないことでした! タスクマネージャーは、ラップトップが8 GBのRAMを搭載している間、プログラムが重要な瞬間に約220 MBを使用することを示しました。



負荷試験



プログラムが90を超えるスレッドを作成すると、問題が明らかになることが判明しました。 彼らはスレッドプールで罪を犯し始めました:彼らは彼が無限にスレッドを作成し始めたと考えました。 プログラムによって一度に作成されるスレッド数の制限を削除し、たとえば1000スレッドを割り当てることが決定されました。 デバッグマシンでは、1000個のスレッド全体がバタンと作成されましたが、ターゲットのラップトップではさらに多くの問題がありました。GUIが崩壊し始めました。



しかし、問題の原因が見つかったというすべての希望は、SysinternalsのTestLimitによって破られました。ターゲットラップトップでは、問題のないこのテストプログラムは1000以上のスレッドを作成しました。 私たちのプログラムでは、デフォルトよりも多くのメモリが各スレッドに割り当てられるという希望でさえ、かすみのように溶けてしまいました。 エラーはまだプログラムにありました。



RTFM



この問題は、ターゲットのラップトップにのみ関連しており、特定のハードウェアでのみ発生すると考えられ始めています。 ただし、ここで不発があります。新しいユーザーを作成すると、その下でプログラムは失敗せずに動作します。



手が落ち始めたとき、従業員の1人が(何度も)MSDNのCreateThread()のヘルプを読みました。 そして、がちょうど開きました:

CreateThread を使用して作成されたスレッド がCRTを呼び出すと、CRTは メモリ不足の状態で プロセス 終了する場合があります 。」証明



解決策: CreateThread()の代わりに_beginthreadex()を使用してスレッドを作成します。 これは、ヘルプページのコメントに「_beginthread vs CreateThread:どちらを使用するべきですか?」というトピックで書かれています。



すぐに、重いアプリケーションがターゲットのラップトップをオンにしたことを思い出しました。これは、 低メモリ状態に対応します。 スレッドプールは_beginthreadex()を使用するように書き直され、最終的には問題が残ります。 しかし、それほど頻繁には現れませんでした。 すべては、プログラムの一部ではスレッドが手動で作成され、プールから取得されていないためです。

CreateThread()へのすべての呼び出しが_beginthreadex()への呼び出し(サードパーティライブラリを含むに置き換えられると、問題は最終的に解決されました。



この問題を解決する際の主な難点は、特定の用途の特定のマシンでのみ問題が再現されることでした。 同時に、すべてのコードテストは役に立たないことが判明しました。この場合、OSの負荷のエミュレーションをさらに行う必要があったためです。



ファイルの名前を変更してもよろしいですか?



2番目の状況。



与えられた :プログラムの動作の不明確な瞬間に、<filename>ファイルが削除された後、<filename>ファイルの存在をチェックする機能はtrueを返します



まず、ファイルの存在を確認する機能を見てみましょう。

bool exist( const wchar_t* filename ) { bool result = !_waccess( filename, 0 ); if( result ) { return true; } WIN32_FIND_DATAW attrs; HANDLE handle = FindFirstFileW( filename, &attrs ); if( handle == INVALID_HANDLE_VALUE ) { return false; } FindClose( handle ); return !!attrs.dwFileAttributes; }
      
      





前の例で発生したCRTの問題を思い出して、 _waccess()関数に罪を犯し始めます。 しかし、ファイルを作成/削除するための短いテストを書いた後、それが何の関係もないことが明らかになります。



_wremove()関数を使用してファイルを削除したでも、 FindFirstFileW()関数はこのファイルの属性を見つけることができます! NTFSファイルシステムドライバーがファイル属性を削除するには、ゼロ以外の時間が必要であることがわかりました。 したがって、存在チェック機能でファイル属性の存在に依存することは不可能です。

はい、問題ありません。 この属性チェックを削除するだけです。



現在、 exist()関数は正しく機能します。 問題は解決しましたか? あった!



ファイルを作成できません、ダンディ!



最初は、問題は次のアルゴリズムにありました。

つまり、名前変更機能:名前変更が開始される前に上記のexist()関数がありました。 修正後、アルゴリズムは正しく機能し始めました。 このコードのテストは次のようになりました。

 void test() { const wchar_t firstName[] = "alice.txt"; const wchar_t secondName[] = "bob.txt"; int mode = _S_IREAD | _S_IWRITE; //    int handle = _wcreat( firstName, mode ); ::close( handle ); //    handle = _wcreat( secondName, mode ); ::close( handle ); while( true ) { //    _wremove( secondName ); // ,    assert( !exist( secondName ) ); //      _wrename( firstName, secondName ); //    _wrename( secondName, firstName ); //    handle = _wcreat( secondName, mode ); ::close( handle ); } }
      
      





ただし、最初のテストは次のようになりました。

 void test() { const wchar_t fileName[] = "test.txt"; int mode = _S_IREAD | _S_IWRITE; while( true ) { //   int handle = _wcreat( fileName, mode ); ::close( handle ); // ,    assert( exist( fileName ) ); //   _wremove( fileName ); // ,    assert( !exist( fileName )); } }
      
      





そのため、最初は、 exist()関数が正しく機能しませんでした。時間が経つにつれて、ファイルが存在しないことを確認すると、誤った結果が生成されました。 これを修正するには、要求されたファイルの属性のチェックを削除しました。 そして、 _wcreat()関数が失敗し始めました! ループの約800回の繰り返し(またはそれ以前)の後、エラーで終了しました...



_Wcreat()CreateFile()のラッパーとして知られています。 したがって、エラー発生時に、最後の関数は戻りコード"ACCESS_DENIED"で終了しました! つまり、システムにファイルを削除するように指示し、さらにファイルが存在しないことを確認したにもかかわらず、実際にはファイルシステムドライバーがファイルを削除しました。 したがって、アクセス拒否。



解決策:彼が見つかりませんでした。 なぜなら 実際のプロジェクトでは、同じファイルを作成/削除するような状況は存在しなかったため、ミスを記録することにしました。 この状況では、 MoveFile()が正しく動作すること、つまり サードパーティのファイルの名前を削除されたものに変更できます!

この状況に対する別の解決策:作成されたファイルへのアクセスを要求する可能性のあるウイルス対策プログラムやその他のプログラムをオフにします。



エピローグ



私はさまざまなアルゴリズムの問​​題をプログラムして解決するのが好きです。 しかし、OS自体にバグがある場合、プログラムの問題を解決するのはどのように難しいのでしょう! まあ、OSではなく、そのためのライブラリとドライバー。 そして、そのようなエラーに出くわすそのような瞬間に、頭に浮かぶのは1つの絵だけです。





参照資料



誰かが他の興味深い事例について読みたい場合は、ここに良いブログがあります(リンクについてはunkinddragonに感謝します ):

blogs.msdn.com/b/oldnewthing



参照:

リヒターJ.「Windows。 64ビットバージョンのWindowsの詳細を考慮した効果的なWin32アプリケーションの作成»

Mark Russinovich、David Solomon、Alex Ionescu「Windows Internals(5th Edition)」



UPD 1:誰かが興味を持っているなら、ここに私のテストがあります:

テストソース
 #include "sys/stat.h" #include "stdio.h" #include "io.h" #include "Windows.h" #include "Share.h" #include "fcntl.h" #include "assert.h" bool createFile( const wchar_t* fileName ) { int mode = _S_IREAD | _S_IWRITE; int handle = _wcreat( fileName, mode ); if( handle == -1 ) { printf( "_wcreat failed with error code %u\n", errno ); } else { _close( handle ); } return handle != -1; } bool createFileSafe( const wchar_t* fileName ) { int operMode = _O_CREAT; int sharMode = _SH_DENYNO; int permMode = _S_IREAD | _S_IWRITE; int handle = 0; errno_t error = _wsopen_s( &handle, fileName, operMode, sharMode, permMode ); if( handle == -1 ) { printf( "_wsopen_s failed with error code %u\n", errno ); } else { _close( handle ); } return handle != -1; } bool deleteFile( const wchar_t* fileName ) { int result = _wremove( fileName ); if( result == -1 ) { printf( "_wremove failed with error code %u\n", errno ); } return !result; } bool renameFile( const wchar_t* src, const wchar_t* dst ) { int result = _wrename( src, dst ); if( result ) { printf( "_wrename failed with error code %u\n", errno ); } return !result; } bool exist( const wchar_t* fileName ) { int mode = 0; int result = _waccess( fileName, mode ); return !result; } void testCreateFile() { printf( "Testing _wcreate()...\n" ); const wchar_t testFileName[] = L"test.txt"; int iteration = 1; while( true ) { bool result = createFile( testFileName ); if( !result ) { printf( "Iteration = %d", iteration ); break; } assert( exist( testFileName ) ); result = deleteFile( testFileName ); if( !result ) { printf( "Iteration = %d", iteration ); break; } assert( !exist( testFileName ) ); ++iteration; } printf( "\n\n" ); } void testCreateFileSafe() { printf( "Testing _wsopen_s()...\n" ); const wchar_t testFileName[] = L"test_safe.txt"; int iteration = 1; while( true ) { bool result = createFileSafe( testFileName ); if( !result ) { printf( "Iteration = %d", iteration ); break; } assert( exist( testFileName ) ); result = deleteFile( testFileName ); if( !result ) { printf( "Iteration = %d", iteration ); break; } assert( !exist( testFileName ) ); ++iteration; } printf( "\n\n" ); } void testRenameFile() { printf( "Testing _wrename()...\n" ); const wchar_t firstName[] = L"first.txt"; const wchar_t secondName[] = L"second.txt"; createFileSafe( firstName ); createFileSafe( secondName ); int iteration = 1; while( true ) { assert( exist( firstName ) ); assert( exist( secondName ) ); bool result = deleteFile( secondName ); if( !result ) { printf( "Iteration = %d", iteration ); break; } assert( !exist( secondName ) ); result = renameFile( firstName, secondName ); if( !result ) { printf( "Iteration = %d", iteration ); break; } assert( exist( !firstName ) ); assert( exist( secondName ) ); result = renameFile( secondName, firstName ); if( !result ) { printf( "Iteration = %d", iteration ); break; } assert( exist( firstName ) ); assert( !exist( secondName ) ); result = createFileSafe( secondName ); if( !result ) { printf( "Iteration = %d", iteration ); break; } ++iteration; } printf( "\n\n" ); } int main( int argc, const char argv[] ) { testCreateFile(); testCreateFileSafe(); testRenameFile(); return 0; }
      
      







テスト結果によると、ラップトップでは2番目の状況が安定して再現されていると言えます。 アンチウイルスから:Windows Security Essentials。



UPD 2:作成されたファイルへのアクセスを要求するウイルス対策プログラムやその他のプログラムを無効にすると、状況2は回避できます(この決定を指摘してくれたコミュニティに感謝します)。



All Articles