ホーム 主筆 その他ソフト その他情報 Syuhitu.org English

Windows関連

スクリーンセーバー作成法

半透明ウインドウの性能

bootfont.bin

キャビネット形式

ウインドウスタイルをいじる

Java製ソフトをServiceに登録する

イベントログにメッセージを出力する

コントロールパネルにアイコンを追加する

スクリプトによる拡張1

スクリプトによる拡張2

ガジェットの作成

大容量メモリ

メモリ搭載量の下限に挑む

スパースファイルにする

表示されるアイコンの種類を調べてみた

メモリマップIOとエラー処理

ファイルを作る順番と速度の関係

Cryptography API: Next Generationを使う

Windows 10のアクセントカラー

iSCSIディスクにバックアップを取る

サーバプロセスを分離して実装する

サーバプロセスを分離して実装する - F#

レジストリに大量に書き込む

Solaris関連

OpenGL

Solaris設定

ディレクトリの読み込み

主筆プラグイン開発

マルチスレッドでの開発

door

音を出す

Blade100の正しい虐め方

パッケージの作成

画像入出力

BMPファイル

ICOファイル

ANIファイル

JPEGファイル

減色アルゴリズム

減色アルゴリズムの並列化

その他アルゴリズムなど

自由軸回転

Base64

文字列操作

CPU利用率の取得

正規表現ライブラリ

メタボールを作る

メタボールを作る2

正規表現とNFA・DFA

C言語の構文解析

液晶ディスプレイを解体してみた

iSCSIの理論と実装

単一フォルダにファイルを沢山作る

USB-HUBのカスケード接続

SafeIntの性能

VHDファイルのフォーマット

USBメモリに書き込み続けてみた

メモリマップIOとエラー処理

2016年1月20日公開

WindowsでもメモリマップI/Oは当然のことながら可能である。

だが、メモリマップI/Oでディスクにアクセスしているときにエラーが発生したらどうなるのだろうか? でもって、そのエラーを捕まえてリトライするとか、エラーメッセージを表示するとかしたい場合にはどうすればいいのだろうか? 調べてみた。

メモリマップI/O

まずは普通に、メモリマップI/Oでファイルにアクセスする方法を示す。

#include <Windows.h>
#include <stdlib.h>

int _tmain(int argc, _TCHAR* argv[])
{
  // ファイルを開く
  HANDLE hFile = CreateFile(
    _T( "I:\\a.txt" ),
    GENERIC_READ | GENERIC_WRITE,
    0,
    nullptr,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    nullptr
  );

  // マッピングオブジェクトを取得する
  HANDLE hMap = CreateFileMapping(
    hFile,
    NULL,
    PAGE_READWRITE,
    0,
    128 * 1024 * 1024,
    nullptr
  );

  // メモリ空間に割り当てる
  UCHAR *pData = (UCHAR*)MapViewOfFile(
    hMap,
    FILE_MAP_ALL_ACCESS,
    0,
    0,
    128 * 1024 * 1024
  );

  // 先頭の方を参照・更新してみる
  pData[0]++;

  // 真ん中ぐらいを参照・更新してみる
  pData[ 64 * 1024 * 1024 ]++;

  // 後ろのほうを参照・更新してみる
  pData[ 128 * 1024 * 1024 - 1 ]++;

  // 割り当てを解除する
  UnmapViewOfFile( pData );

  // ハンドルを閉じる
  CloseHandle( hMap );
  CloseHandle( hFile );

  return 0;
}

大した処理ではない。

だが、ここで問題になるのは、実際にファイルにアクセスしているのが黄色で示した3行の処理だということである。

メモリマップI/Oに慣れないとメモリ上の配列にアクセスしているだけにしか見えず、この処理のどこがファイルへのアクセスなのかがわかりにくいのだが、しかし、実際にそうなのだから仕方がない。でもって、ファイルにアクセスしている最中にエラーが発生したら何が起きるのかというのがここでの主眼である。

普通のエラー処理

メモリマップI/Oではない通常のファイルアクセスだと、参照であれば下記のような処理になる。

   // ファイルを開く
  HANDLE hFile = CreateFile(
    _T( "I:\\a.txt" ),
    GENERIC_READ | GENERIC_WRITE,
    0,
    nullptr,
    CREATE_ALWAYS,
    FILE_ATTRIBUTE_NORMAL,
    nullptr
  );

  UCHAR buf[128];
  DWORD ReadSize = 0;
  if ( !ReadFile( hFile, buf, sizeof( buf ), &ReadSize, nullptr ) ) {
    // 失敗
  }
  else if ( ReadSize != sizeof( buf ) ) {
    // 期待するバイト数分だけ読み込んでない
  }
  else {
    // たぶん成功
  }

  CloseHandle( hFile );

ファイルへのI/Oで何が起きるかはわからないが、とりあえずReadFile関数が成功したのか失敗したのか、あるいは実際に何バイト読み込むことができたのかを返してくれる。だから、その結果をもとに成否を判断すればいい。

だが、メモリマップI/Oを使っていると、肝心のエラーを返してくれる主体がどこにもいない。何分、字面上は配列にアクセスしているだけなのだから、どこからもエラー値が取得できないのである。

とりあえずエラーを起こしてみる

考えていても始まらないので、まずは実際にファイルI/Oでエラーを発生させてみて何が起きるのか、実践してみることにする。

前提

最初に、Iドライブにa.txtという128MBのファイルが存在するものとする。(ここでファイルが存在しないと、最後のCloseHandleで一気にファイルを書き出すという挙動になり、配列にアクセスしている風のところではI/Oが発生しない)

Iドライブに割り当てられているメディアとしては、下記のCFカードを使う。

今となっては、どこで何の目的で買ったのか思い出すこともできない使い古しのCFカードだ。ちょっと乱暴なことをやるから、壊れてもいいメディアとしてこいつに犠牲になってもらうことにした。

ステップ実行する

CFカードをちゃんとリーダ/ライタに差し込む。

ステップ実行を開始する。

さずがに、CreateFileを行う前ではエラーは発生しない。とりあえず、ファイルの先頭にアクセスする部分を通り過ぎて、真ん中ぐらいにアクセスするところまで進めてみる。

ここまで実行すると、おそらくファイルの先頭部分が含まれるページはメモリに読み込まれているが、それ以外の部分はまだメディアから読み込まれていない状態であると期待される。

なので、ここでいきなりCFカードをリーダ/ライタから引っこ抜いてみる。

物理的に切り離している以上、どんな魔法を使おうがこのCFカードにはこれ以上アクセスすることはできなくなったはずである。この状態で、プログラムの方をもう1ステップ進めてみる。

こんなことになった。

これで「再実行」を選んだら再実行しそうだから、とりあえず「キャンセル」を選んでみる。

落ちた。

ところでこれ、「キャンセル」「再実行」「続行」の3つのボタンだが、「再実行」を選択するとメディアへのアクセスを再実行してくれるようだが、「キャンセル」と「続行」はどちらも同じでIn page errorでプロセスが殺される。

USBメモリでやってみる

CFカードをやめて、今度はこっちのUSBメモリを使ってみる。

そうすると今度は、「キャンセル」「再実行」「続行」のメッセージが表示されず、いきなりIn page errorになる。どうやら、「キャンセル」「再実行」「続行」のメッセージは誰が出しているのかはわからないが、CFカードに依存するように思われる。

エラーを捕まえる

原因と過程は何であれ、プロセスが殺されるのは受け入れがたい。リトライするか、エラーメッセージを表示するか、ログに出力するか、アプリケーションとしてのしかるべき対応を取りたい。ということで、まずはこのIn page errorとかいう例外を捕まえることを考えてみる。

しかしこれは、C++の例外ではない。だから、下記のように記述しても、エラーを捕まえることはできない。

try {
  HANDLE hFile = CreateFile(
  ……
  // このあたりにI/Oの処理を記述する
  ……
  CloseHandle( hFile );
}
catch ( ... )
{
  // エラー処理
}

いきなりメディアを引っこ抜くような乱暴な障害が起きた場合、上記の「エラー処理」に遷移することはない。前と同様にIn page errorでプロセスが殺される。

デバッガをあてがって実行しているのではないに場合は、原因不明のプロセスの異常終了という見え方にしかならない。

これを捕まえるためには、Windowsの構造化例外(SEH)を用いる必要がある。

構造化例外を使う

構造化例外に関しては、以下のページに記載がある。

https://msdn.microsoft.com/ja-jp/library/swezty51.aspx

これを使うと、下記のような恐ろしいエラーを補足することができる。

  1. EXCEPTION_ACCESS_VIOLATION
  2. EXCEPTION_ARRAY_BOUNDS_EXCEEDED
  3. EXCEPTION_BREAKPOINT
  4. EXCEPTION_DATATYPE_MISALIGNMENT
  5. EXCEPTION_FLT_DENORMAL_OPERAND
  6. EXCEPTION_FLT_DIVIDE_BY_ZERO
  7. EXCEPTION_FLT_INEXACT_RESULT
  8. EXCEPTION_FLT_INVALID_OPERATION
  9. EXCEPTION_FLT_OVERFLOW
  10. EXCEPTION_FLT_STACK_CHECK
  11. EXCEPTION_FLT_UNDERFLOW
  12. EXCEPTION_GUARD_PAGE
  13. EXCEPTION_ILLEGAL_INSTRUCTION
  14. EXCEPTION_IN_PAGE_ERROR
  15. EXCEPTION_INT_DIVIDE_BY_ZERO
  16. EXCEPTION_INT_OVERFLOW
  17. EXCEPTION_INVALID_DISPOSITION
  18. EXCEPTION_INVALID_HANDLE
  19. EXCEPTION_NONCONTINUABLE_EXCEPTION
  20. EXCEPTION_PRIV_INSTRUCTION
  21. EXCEPTION_SINGLE_STEP
  22. EXCEPTION_STACK_OVERFLOW
  23. STATUS_UNWIND_CONSOLIDATE

委細は以下に記載がある。

https://msdn.microsoft.com/ja-jp/library/windows/desktop/ms679356(v=vs.85).aspx

ここで問題になるのは上記の14番目「EXCEPTION_IN_PAGE_ERROR」である。

まずは普通に、メモリマップI/Oでファイルにアクセスする方法を示す。

#include <Windows.h>
#include <stdlib.h>

int _tmain(int argc, _TCHAR* argv[])
{
  __try {

    // ファイルを開く
    HANDLE hFile = CreateFile(
      _T( "I:\\a.txt" ),
      GENERIC_READ | GENERIC_WRITE,
      0,
      nullptr,
      CREATE_ALWAYS,
      FILE_ATTRIBUTE_NORMAL,
      nullptr
    );

    // マッピングオブジェクトを取得する
    HANDLE hMap = CreateFileMapping(
      hFile,
      NULL,
      PAGE_READWRITE,
      0,
      128 * 1024 * 1024,
      nullptr
    );

    // メモリ空間に割り当てる
    UCHAR *pData = (UCHAR*)MapViewOfFile(
      hMap,
      FILE_MAP_ALL_ACCESS,
      0,
      0,
      128 * 1024 * 1024
    );

    // 先頭の方を参照・更新してみる
    pData[0]++;

    // 真ん中ぐらいを参照・更新してみる
    pData[ 64 * 1024 * 1024 ]++;

    // 後ろのほうを参照・更新してみる
    pData[ 128 * 1024 * 1024 - 1 ]++;

    // 割り当てを解除する
    UnmapViewOfFile( pData );

    // ハンドルを閉じる
    CloseHandle( hMap );
    CloseHandle( hFile );
  }
  __except (
    GetExceptionCode() == EXCEPTION_IN_PAGE_ERROR ?
      EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH
    )
  {
      printf( "error\n" );
  }

  return 0;
}

わかりやすく、全体を__tryのブロックに入れてみた。

これだと例外ハンドラに遷移した時、ハンドルが閉じられないという問題が生じるが、細かいことは気にしない。

同じ様にやってみる

とりあえず同じく、ファイルの真ん中にアクセスするところまで進めてみる。

ここでUSBメモリを引っこ抜いて、1ステップ進めてみる。

想定通り、「pData[ 64 * 1024 * 1024 ]++;」で失敗して、プロセスが異常終了することなく例外ハンドラに遷移する。

上にも書いたが、CloseHandleの呼び出しも含めて__tryブロックに押し込んでしまったから、このままだとハンドルが閉じられないままとなる。この例ではそのままプロセスを終了してしまうので特に問題はないはないが、実際にはハンドルをきれいに閉じたうえで、リトライするなりなんなりの対応がいるだろう。


連絡先 - サイトマップ - 更新履歴
Copyright (C)  2000 - 2016 nabiki_t All Rights Reserved.