MSFファイルフォーマット

ファイルレイアウト

MSFファイルフォーマットは、以下のコンポーネントで構成されています。

  1. スーパーブロック

  2. フリーブロックマップ(フリーページマップ、またはFPMとも呼ばれます)

  3. データ

各コンポーネントはインデックス付きブロックとして格納され、その長さはSuperBlock::BlockSizeで指定されます。ファイルは、以下のパターン(「インターバル」と呼ばれることもあります)を1つ以上繰り返して構成されています。

  1. 1ブロックのデータ

  2. フリーブロックマップ1(SuperBlock::FreeBlockMapBlock 1に対応)

  3. フリーブロックマップ2(SuperBlock::FreeBlockMapBlock 2に対応)

  4. SuperBlock::BlockSize - 3ブロックのデータ

最初のインターバルでは、最初のデータブロックを使用してスーパーブロックが格納されます。

次の図は、ファイルの一般的なレイアウトを示しています(|はインターバルの終わりを示し、可視化のためのみです)

ブロックインデックス

0

1

2

3 - 4095

|

4096

4097

4098

4099 - 8191

|

意味

スーパーブロック

フリーブロックマップ1

フリーブロックマップ2

データ

|

データ

FPM1

FPM2

データ

|

ファイルは、FPM1の直後など、任意のブロックの後に終了する可能性があります。

注記

LLVMは4096バイトのブロック(「BigMsf」バリアントと呼ばれることもあります)のみをサポートしているため、このドキュメントの残りの部分では、ブロックサイズを4096と仮定します。

スーパーブロック

MSFファイルのファイルオフセット0には、MSF *スーパーブロック*があり、以下のレイアウトになっています。

struct SuperBlock {
  char FileMagic[sizeof(Magic)];
  ulittle32_t BlockSize;
  ulittle32_t FreeBlockMapBlock;
  ulittle32_t NumBlocks;
  ulittle32_t NumDirectoryBytes;
  ulittle32_t Unknown;
  ulittle32_t BlockMapAddr;
};
  • **FileMagic** - "Microsoft C / C++ MSF 7.00\\r\\n"とバイト1A 44 53 00 00 00が続く必要があります。

  • **BlockSize** - 内部ファイルシステムのブロックサイズ。有効な値は、512、1024、2048、および4096バイトです。MSFファイルレイアウトの特定の側面は、ブロックサイズによって異なります。LLVMに関しては、4KiBのブロックサイズのみを処理し、以降の説明では4KiBのブロックサイズを前提とします。

  • **FreeBlockMapBlock** - ファイル内のブロックのインデックス。このインデックスから、ファイル内のすべてのブロックの集合を表すビットフィールドが始まり、「フリー」(つまり、そのブロック内のデータは使用されていない)なブロックを示します。フリーブロックマップの詳細については、を参照してください。**重要**:FreeBlockMapBlock1または2のみです!

  • **NumBlocks** - ファイル内のブロックの総数。NumBlocks * BlockSizeは、ディスク上のファイルサイズと等しくなければなりません。

  • **NumDirectoryBytes** - ストリームディレクトリのサイズ(バイト単位)。ストリームディレクトリには、各ストリームのサイズと、そのストリームが占めるブロックの集合に関する情報が含まれています。後で詳しく説明します。

  • **BlockMapAddr** - MSFファイル内のブロックのインデックス。このブロックには、ストリームディレクトリが存在するブロックをリストするulittle32_tの配列があります。大規模なMSFファイルの場合、ストリームディレクトリ(各ストリームのブロックレイアウトを記述)は1つのブロックに完全に収まらない場合があります。その結果、この間接参照の追加レイヤーが導入され、このブロックにはストリームディレクトリが占めるブロックのリストが含まれ、ストリームディレクトリ自体はそれに応じて連結できます。ulittle32_tの数はceil(NumDirectoryBytes / BlockSize)で与えられます。

フリーブロックマップ

フリーブロックマップ(フリーページマップ、またはFPMとも呼ばれます)は、ファイル内の各ブロックのビットフラグを含む一連のブロックです。フラグは、ブロックが使用されている場合は0に、使用されていない場合は1に設定されます。

各ファイルには2つのFPMがあり、いずれか1つがいつでもアクティブになります。この機能は、基になるMSFファイルの増分およびアトミック更新をサポートするように設計されています。MSFファイルに書き込む際に、アクティブなFPMがFPM1の場合、新しい変更されたビットフィールドをFPM2に書き込むことができ、その逆も同様です。ファイルをディスクにコミットする場合にのみ、スーパーブロック内の値を交換して、新しいFreeBlockMapBlockを指す必要があります。

フリーブロックマップは、BlockSizeの間隔でファイル全体に一連の単一ブロックとして格納されています。各FPMブロックのサイズはBlockSizeバイトであるため、インターバルのブロック数の8倍のビットが含まれています。つまり、各FPMの最初のブロックはファイルの最初の8つのインターバル(最初の32768ブロック)を参照し、各FPMの2番目のブロックは次の8つのブロックを参照し、以下同様です。これにより、必要な数よりもはるかに多くのFPMブロックが存在することになりますが、下位互換性を維持するために、フォーマットはこのままである必要があります。

ストリームディレクトリ

ストリームディレクトリは、MSFファイル内の他のストリームへのすべてのアクセスのルートです。ストリームディレクトリのバイト0から始まるのは、次の構造です。

struct StreamDirectory {
  ulittle32_t NumStreams;
  ulittle32_t StreamSizes[NumStreams];
  ulittle32_t StreamBlocks[NumStreams][];
};

この構造は正確にSuperBlock->NumDirectoryBytesバイトを占めます。最後の2つの配列は可変長であり、特に2番目の配列は不規則であることに注意してください。

**例:** 4KiBのブロックサイズと、長さ{1000バイト、8000バイト、16000バイト、9000バイト}の4つのストリームを持つ仮想PDBファイルがあるとします。

ストリーム0:ceil(1000 / 4096) = 1ブロック

ストリーム1:ceil(8000 / 4096) = 2ブロック

ストリーム2:ceil(16000 / 4096) = 4ブロック

ストリーム3:ceil(9000 / 4096) = 3ブロック

合計で10ブロックが使用されます。ストリームディレクトリがどのように見えるかを見てみましょう。

struct StreamDirectory {
  ulittle32_t NumStreams = 4;
  ulittle32_t StreamSizes[] = {1000, 8000, 16000, 9000};
  ulittle32_t StreamBlocks[][] = {
    {4},
    {5, 6},
    {11, 9, 7, 8},
    {10, 15, 12}
  };
};

合計で15 * 4 = 60バイトを占めるため、SuperBlock->NumDirectoryBytes60になり、SuperBlock->BlockMapAddr60 <= SuperBlock->BlockSizeであるため、1つのulittle32_tの配列になります。

また、ストリームは不連続であり、ストリーム3の一部はストリーム2の一部の中間にあることにも注意してください。ブロックのレイアウトについては何も想定できません!

アラインメントとブロック境界

今までの説明から明らかなように、単一フィールド(上位レベルのレコード、長い文字列フィールド、または単一のuint16であっても)が別々のブロックで始まり、終わる可能性があります。たとえば、ブロックサイズが4096バイトで、uint16フィールドが現在のブロックの最後のバイトから始まる場合、次のブロックの最初のバイトで終わる必要があります。ブロックはファイル内に必ずしも連続して配置されているとは限らないため、これはMSFファイルのコンシューマーとプロデューサーの両方が、それに応じてデータを分割する準備をしなければならないことを意味します。上記の例では、uint16の上位バイトはブロックNの最後のバイトに書き込まれ、下位バイトはブロックN+1の最初のバイトに書き込まれます。これは、ストリームディレクトリの内容によっては、ファイル内で数万バイト後(または前!)になる可能性があります。