ホーム 主筆 その他ソフト その他情報 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メモリに書き込み続けてみた

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

2016年3月9日公開

今となってはかなり古くなってしまったが、手元に色あせた資料が転がっているから、VHD(Virtual Hard Disk)ファイルのフォーマットについてまとめておく。

種類

VHDファイルには下記の3種類が存在し、それぞれ別のフォーマットを使用する。

  1. 固定ハードディスクイメージ
  2. 動的ハードディスクイメージ
  3. 差分ハードディスクイメージ

固定ハードディスクイメージは、あらかじめディスク領域長のファイルを事前に作成しておくフォーマットである。1GBの仮想ディスクを作ると、その時点で1GBのファイルが作られる。

動的ハードディスクイメージは、ディスクにデータが書き込まれた時点でファイルサイズが大きくなるフォーマットである。

差分ハードディスクイメージは、親ハードディスクイメージとの差分を保持するフォーマットである。親のハードディスクイメージが無ければ意味をなさない。親のハードディスクイメージとしては、上記3種類のいずれのフォーマットでも使用することが可能である。

固定ハードディスクイメージ

これはかなり単純なフォーマットである。

下記2つの部分から構成される。

ディスクイメージ
フッター(512バイト)

ディスクイメージの部分は、仮想マシンが認識するディスクイメージそのものであり、ここにフォーマットされたファイルシステムが記録される。

フッターはすべてのフォーマットで共通である。共通だから、このページの後の方でまとめて書く。

動的ハードディスクイメージ

動的ハードディスクの場合は、概要で下記のような構造となる。

フッターの複製(512バイト)
動的ディスクヘッダ(1024バイト)
ブロック割り当て表
データブロック1
データブロック2
……
データブロックn
フッター(512バイト)

データブロックは書き込みに応じて徐々に追加される。その際、フッターは常にファイルの末尾に来るよう位置が移動される。

また、冗長性を確保するため、フッターの複製がファイルの先頭部分に格納される。

差分ハードディスクイメージ

差分ハードディスクイメージのフォーマットは、動的ハードディスクイメージのフォーマットとほぼ同じである。詳細は後述する。

差分ハードディスクイメージ

すべてのハードディスクイメージにはフッターが存在する。

フッターは以下のフォーマットである。

フィールドバイト数
クッキー8
特性4
ファイルフォーマットバージョン4
データオフセット8
タイムスタンプ4
作成したアプリケーション4
作成者のバージョン4
作成者のホストOS4
オリジナルサイズ8
カレントサイズ8
ディスクジオメトリ4
ディスク種別4
チェックサム4
識別子16
保護ステータス1
予約427

なんでも、Virtual PC 2004以前のものでは、フッターが511バイトだったりするらしい。だから、実のところファイル末尾の512バイトか、あるいは511バイトのところに存在するという、非常にエッチィことになっていたりするらしい。

フッターの各フィールドは、下記のようになっている。

クッキー

ハードディスクイメージのオリジナルの作者を識別するために使用される、ASCIIの文字列(8バイト)が格納される。

この値は、大文字・小文字を区別する。

特性

サポートされる特性を表すビットフィールドである。下記の値が設定される。

特性説明
特性なし0x00000000当該のハードディスクイメージでは、特別な特性を使用しないことを示す。
テンポラリ0x00000001当該のハードディスクイメージが一時的なものであることを示す。テンポラリのディスクは、使用するゲストOS等がシャットダウンされたときに削除されることを許容するようなアプリケーションのために規定されている。
予約済み0x00000002予約済みのビットは常に1にしておくこと。そのほかのビットも予約済みであり、それらについては常に0にしておくこと。

ファイルフォーマットバージョン

ファイル作成時に使用されたフォーマットを洗わずバージョン番号を格納する。

上位2バイトにメジャーバージョンが格納され、下位2バイトにマイナーバージョンが格納される。

このページで記載しているフォーマットの場合は0x00010000が格納されなければならない。

メジャーバージョンは、古いバージョンとの互換性が無くなるようなフォーマット変更を行う場合にのみ加算する。

データオフセット

次に構造体が格納されている位置を示す、ファイル先頭からの絶対バイトオフセットが格納される。

このフィールドは動的ハードディスクイメージと差分ハードディスクイメージで使用されるが、固定ハードディスクイメージでは使用されない。固定ハードディスクイメージの場合は0xFFFFFFFFが格納されるべきである。

タイムスタンプ

ハードディスクイメージを作成した日時が格納される。

グリニッジ標準時で2000年1月1日0時0分0秒からの経過秒数の値を設定する。

作成したアプリケーション

ハードディスクイメージを作成したアプリケーションを識別する、ASCII文字列を格納する。

左揃えのテキストフィールドである。

作成者のバージョン

ハードディスクイメージを作成したアプリケーションのバージョンを表す値を格納する。

作成者のホストOS

当該のハードディスクイメージを作成したホストOSを識別する値が格納される。

OSの種別
Windows0x5769326B(Wi2k)
Macintosh0x4D616320(Mac )

オリジナルのサイズ

ゲストOSから見えるハードディスクのサイズをバイト数で格納する。参考情報として扱われる。

カレントサイズ

ゲストOSから見えるハードディスクの現在のサイズをバイト数で格納する。

この値は、ハードディスクが作成された時点ではオリジナルのサイズと同じ値が設定される。ハードディスクのサイズが拡張される場合に、この値が更新される。

ディスクジオメトリ

この値には、ハードディスクのシリンダ数・ヘッダ数・トラック当たりのセクタ数が格納される。

ディスクジオメトリ内のフィールドサイズ(バイト数)
シリンダ数2
ヘッダ数1
セクタ数1

ハードディスクがATAディスクとして構成されている場合、CHS(シリンダ、ヘッダ、セクタ)の値はATAコントローラによりディスクサイズを確定するために使用される。

ディスク種別

以下の値が格納される。

ディスク種別内のフィールド
なし0
予約済み(使用不可)1
固定ハードディスクイメージ2
動的ハードディスクイメージ3
差分ハードディスクイメージ4
予約済み(使用不可)5
予約済み(使用不可)6

チェックサム

フッターのチェックサムを格納する。

フッター内のチェックサムフィールドを除く全バイトの合計値を求め、1の補数をとったものである。

識別子

ハードディスクイメージを識別する、一意な識別子を格納する。128ビットのUUIDを使用する。

差分ハードディスクイメージで、親のディスクイメージを特定するために使用する。

保護ステータス

システムが保護状態にあるのか否かを示す値が格納される。

ハードディスクが保護状態にある場合は1が設定される。保護状態にあるハードディスクでは、縮小や拡張といった操作を行ってはならない。

予約済み

すべて0を格納する。

動的ディスクヘッダ

動的ハードディスクイメージないし差分ハードディスクイメージでは、フッターに存在するデータオフセットのフィールドに、ディスクイメージの追加の情報を保持する2つ目の構造体のバイト位置が格納される。

動的ディスクヘッダは512バイト境界に格納されるべきである。

動的ディスクヘッダのフィールドは下記のとおりである。

動的ディスクヘッダのフィールドサイズ(バイト数)
クッキー8
データオフセット8
テーブルオフセット8
ヘッダバージョン4
最大テーブルエントリ数4
ブロックサイズ4
チェックサム4
親識別子16
親タイムスタンプ4
予約済み4
親ユニコード名512
親ロケータデータエントリ124
親ロケータデータエントリ224
親ロケータデータエントリ324
親ロケータデータエントリ424
親ロケータデータエントリ524
親ロケータデータエントリ624
親ロケータデータエントリ724
親ロケータデータエントリ824
予約済み256

動的ディスクヘッダの各フィールドは、以下の通りとなる。

クッキー

このフィールドにはヘッダーを識別する"cxsparse"の値が格納される。

データオフセット

ハードディスクイメージの次の構造体の位置を示す、絶対バイトオフセットを格納する。しかしこのフィールドは使用されず0xFFFFFFFFが格納される。

テーブルオフセット

ブロック割り当てテーブルが存在する、ファイル内での絶対バイトオフセットが設定される。

ヘッダバージョン

動的ディスクヘッダのバージョンを保持する。

このフィールドには、メージャーとマイナーの2種類の値が格納される。最下位2バイトにマイナーバージョンが格納され、最上位2バイトにはメジャーバージョンが格納される。

この値はファイルフォーマットの仕様と一致していなければならない。このページに記述するフォーマットに従う場合は0x00010000で初期化されなければならない。

メジャーバージョンは、古いフォーマットとの互換性が失われるような方法でヘッダフォーマットが更新される場合に加算される。

最大テーブルエントリ数

ブロック割り当てテーブルで保持する最大エントリ数を格納する。これは、ディスクのブロック数と一致するべきである。すなわち、ディスクサイズをブロック長で割った値になるはずである。

ブロックサイズ

ブロックは、動的/差分ハードディスクイメージを拡張する単位のことである。ここフィールドには、その1ブロックのサイズをバイト数で格納する。

このサイズにはブロックビットマップのサイズを含まない。データセクションのブロックのサイズのみである。

ブロック当たりのセクタ数は常に2の累乗でなければならない。デフォルトの値は0x00200000(つまり2MB)である。

チェックサム

動的ヘッダのチェックサムを格納する。

チェックサムフィールドを除く、動的ヘッダ内のすべてのバイトを合計して1の補数をとった値が使用される。

親識別子

このフィールドは、差分ハードディスクイメージで使用される。差分ハードディスクイメージでは、親ハードディスクの128ビットUUIDが格納される。

親タイムスタンプ

このフィールドには、親ハードディスクの更新日時を格納する。

グリニッジ標準時の2000年1月1日0時0分0秒からの経過秒数が格納される。

予約済み

0を格納する。

親ユニコード名

ユニコード(UTF-16)で親ハードディスクイメージのファイル名を格納する。

親ロケータエントリ

差分ハードディスクイメージのための、親ロケータが格納された絶対バイトオフセットを格納する。

このフィールドは差分ハードディスクイメージでのみ使用される。動的ハードディスクイメージでは0を格納する。

各ロケータエントリのフィールドを下記に示す。

親ロケータエントリ内のフィールドサイズ(バイト数)
プラットフォームコード4
プラットフォームデータスペース4
プラットフォームデータ長4
予約済み4
プラットフォームデータオフセット8

プラットフォームコード

プラットフォームコードは、ファイルロケータとしてどのプラットフォーム指定のフォーマットが使用されるのかを示す。例えばWindowsであれば、ファイルロケータはパス名として格納される。Macintoshであれば、ファイルロケータはエイリアスを含むblobである。

プラットフォームコードを下記に示す。

プラットフォームコード説明
なし0x00000000
Wi2r0x57693272使用不可
Wi2k0x5769326B使用不可
W2ru0x57327275Windows上で差分ディスクの相対パス名を、ユニコード(UTF-16)で保持する。
W2ku0x57326B75Windows上で差分ディスクの絶対パス名を、ユニコード(UTF-16)で保持する。
Mac0x4D616320blobに格納されたMacOSのエイリアス
MacX0x4D616358RFC2396準拠のURLをUTF-8で保持する。

プラットフォームデータスペース

親ハードディスクロケータを保持するのに必要となる、512バイトセクタの個数を保持する。

予約済み

0を設定する。

プラットフォームデータオフセット

プラットフォーム依存のファイルロケータがどこに格納されているのかを示す、絶対ファイルオフセットのバイト数を保持する。

予約済み

0を設定する。

ブロック割り当て表とデータブロック

ブロック割り当て表は、ブロックの割り当て状況を表す、ファイル内での絶対セクタオフセットのテーブルである。これは動的ディスクヘッダのテーブルオフセットにより格納位置が示される。

ブロック割り当て表のサイズはハードディスクの構築時に計算される。ブロック割り当て表のエントリ数は、ディスクを最大限拡張した際に、内容を格納するために必要となるブロック数に等しい。

例えば、2MBのブロックを使用する2GBのディスクイメージは、1024個のブロック割り当て表のエントリが必要となる。

各エントリは4バイトである。未使用のテーブルエントリはすべて0xFFFFFFFFで初期化される。

ブロック割り当て表は常にセクタ境界まで拡張される。動的ディスクヘッダの最大テーブルエントリ数フィールドは、いくつのエントリが有効であるかを示す。

ブロック割り当て表の各エントリは、ディスクイメージのブロックを参照する。

データブロックには、セクタビットマップを格納する。

動的ハードディスクイメージでは、セクタビットマップは当該セクタに有効なデータが格納されているか(=1)、今まで一度も更新されたことがないか(=0)を保持する。差分ハードディスクイメージでは、当該セクタが差分ハードディスクイメージに存在するか(=1)、親のハードディスクイメージに存在するか(=0)を保持する。

ビットマップは512バイトのセクタ境界までパディングされる。

ブロックは2の累乗個のセクタから構成される。標準でのブロックのサイズは、512バイトのセクタが4096個(2MB)である。あるイメージに含まれるすべてのブロックは同じ大きさでなければならない。このサイズは、動的ディスクヘッダのブロックサイズフィールドで指定される。

ブロック中に存在するセクタで、ビットマップ内の対応するビットが0のものについては、ディスク内に0が512バイト格納されていなければならない。ディスクイメージにアクセスするソフトウェアは、この過程により性能向上を図ることが可能となる。

動的ハードディスクイメージの実装

ディスクは要求時に割り当てられる。動的ディスクが作られる際、初期状態ではブロックは割り当てられない。新しく作られたイメージには、今までに述べた構造体のデータ(動的ディスクヘッダとフロック割り当て表を含む)しか存在しない。

イメージにデータが書き込まれたとき、動的ディスクは新しいブロックを格納するために拡張される。ブロック割り当て表には、イメージに割り当てられた新しいブロックのオフセットが格納される。

ディスクセクタとブロック内セクタの対応付け

参照しようとしているセクタ番号から、ブロック番号を算出するには、下記の計算式を使用する。

ブロック番号 = floor( 実セクタ番号 / ブロックあたりのセクタ数 )

ブロック内のセクタ番号 = 実セクタ番号 % ブロックあたりのセクタ数

ブロック番号はブロック割り当て表のインデックスとして使用される。ブロック割り当て表は、ビットマップとそれに続くブロックデータの開始位置の絶対セクタオフセットを保持している。

データの位置を計算するために、下記の式を使用する。

実際のセクタ位置 ブロック割り当て表[ブロック番号] + ブロックビットマップセクタ数 + ブロック内のセクタ番号

このルールでは、ブロック割り当て表の順序性が維持される限りは任意の順番にブロックを割り当てることが可能である。

ブロックが割り当てられたとき、イメージのフッターはファイルの末尾に移動されなければならない。ファイルの拡張された部分は0で初期化されているべきである。

差分ハードディスクイメージの実装

差分ハードディスクイメージは、親ハードディスクイメージのロケータを保持している。仮想マシンが差分ハードディスクをオープンするとき、差分ハードディスクイメージと親ハードディスクイメージの両方がオープンされる。

親ハードディスクイメージもまた差分ハードディスクイメージである可能性がある。その場合、差分ではないハードディスクイメージ終端とするハードディスクイメージのチェインを構成する。

プラットフォーム間でハードディスクを移動できるよう、ハードディスクフォーマットは同時に異なるプラットフォームの親ハードディスクのファイルロケータを格納できるよう設計されている。

親ロケータテーブルは、「動的ディスクヘッダフォーマット」に記載される通り、差分ハードディスクのみで使用される。親ロケータテーブルは、ファイルに格納された親ファイルロケータごとに、プラットフォームコードを保持する。仮想マシンは、カレントのプラットフォームに適合する親ファイルロケータを読み込み、ハードディスクイメージのオープンを行う。

WindowsではW2kuとW2ruの2種類のプラットフォームロケータが存在する。前者は親ハードディスクへの絶対パス名であり、後者は差分ハードディスクからの相対パス名である。

例えば、一般的なWindowsマシンのルートドライブにハードディスクが格納されていた場合、下記のような値が設定される。

種別
W2kuc:\directory\parent.vhd
W2ru.\directory\parent.vhd

一般的なMacintoshベースのマシンでは、親ハードディスクイメージは下記のように格納される。

種別
Mac(Mac OS alias stored as a blob)
MacXfile://localhost/directory/parent.vhd

相対パス名の利点は、差分と親のハードディスクイメージを異なる位置に移動できることである。絶対パス名の場合は、親ハードディスクが移動されると、親と子は明示的に再リンクする必要がある。

差分ハードディスクが作成されたとき、可能であれば各プラットフォームごと、両方のプラットフォームロケータを初期化するべきである。

差分ハードディスクイメージへの書き込み操作

書き込み操作時、すべてのデータは差分ハードディスクの書き込まれる。ブロックビットマップは、書くブロックの書き込まれたすべてのセクタについて、変更有りとしてマークされる。

差分ハードディスクイメージへの読み込み操作

仮想マシンが差分ハードディスクのセクタを読み込むとき、差分ハードディスクサブシステムは差分ハードディスクのブロックビットマップの内容を確認する。差分ハードディスクサブシステムは、変更ありとしてマークされているセクタを差分ハードディスクイメージから読み込み、変更なしとしてマークされているセクタを親ハードディスクイメージから読み込む。

例えば、4096から8191のセクタが親と子の両方のハードディスクイメージに存在する場合を考える。ブロックの最初のセクタにブロックのビットマップが格納される。1つのセルがビットマップのビットを表し、黒い点が仮想マシンにより書き込まれた個々のセクタを表す。

もし仮想マシンが4096〜4107のセクタに対する読み込み操作を行ったら、差分ハードディスクサブシステムは4098〜4101のセクタは親ハードディスクから読み込み、4102〜4104のセクタは子ハードディスクから読み込む。

もし仮想マシンが4102〜4106のセクタに対する書き込み操作を行ったら、すべてのデータが子ハードディスクに書き込まれる。子ハードディスクの4105〜4106のセクタのビットマップは変更ありとしてマークされる。

親ハードディスクイメージの識別

各ハードディスクイメージはフッターにUUIDを保持している。差分ハードディスクイメージが構築されたとき、差分ハードディスクイメージ内に親ハードディスクイメージのUUIDを格納する。UUIDと親ハードディスクイメージ名は、親ハードディスクイメージを認識するために使用される。

親ハードディスクイメージへの変更

差分ハードディスクイメージが構築されたら、それ以降は親ハードディスクイメージが変更されるべきではない。親ハードディスクイメージへの変更は、差分ハードディスクイメージの状態を不正にする。

この事象が発生しないことを保証するため、親変更日時が差分ハードディスクイメージの構造体に格納される。

正当な親子関係が存在することを保証するため、親ハードディスクイメージのUUIDと変更日時の両方を確認するべきである。

補足:CHSの計算

CHSはハードディスクイメージの総データセクタ数に基づいて計算される。

CHS算出

CHS算出の変数説明
totalSectorsハードディスクイメージにより提供される合計データセクタ数
Cylindersディスクのシリンダ数
headsディスクのヘッダ数
sectoresPerTrackディスクのトラック当たりのセクタ数
cylindersTimesHeadCylinders × heads

                        C      H    S
if ( totalSectors > 65535 * 16 * 255 ) {
  totalSectors = 65535 * 16 * 255;
}

if ( totalSectors >= 65535 * 16 * 63 ) {
  sectorsPerTrack = 255;
  heads = 16;
  cylinderTimesHeads = totalSectors / sectorsPerTrack;
}
else {
  sectorsPerTrack = 17;
  cylinderTimesHeads = totalSectors / sectorsPerTrack;
  
  heads = ( cylinderTimesHeads + 1023 ) / 1024;

  if ( heads < 4 ) {
    heads = 4;
  }
  
  if ( cylinderTimesHeads >= ( heads * 1024 ) || heads > 16 ) {
    sectorsPerTrack = 31;
    heads = 16;
    cylinderTimesHeads = totalSectors / sectorsPerTrack;
  }
  
  if ( cylinderTimesHeads >= ( heads * 1024 ) ) {
    sectorsPerTrack = 63;
    heads = 16;
    cylinderTimesHead = totalSectors / sectorsPerTrack;
  }
}

cylinders = cylinderTimesHead / heads;

チェックサムの計算

チェックサムは下記に基づき算出される。

チェックサム計算

チェックサム計算の変数説明
driveFooterフッターの構造体を保持する変数
checksumチェックサムの値を保持する変数
driveFooterSizedriveFooter構造体のサイズ
counterローカルカウンタ

checksum = 0;
driveFooter.Checksum = 0;

for ( counter = 0; counter < driveFooterSize; counter++ ) {
  checksum += driveFooter[ counter ];
}

driveFooter.Checksum = ~checksum;


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