テクニカルいんふぉ

戻る

注意

これは独自の調査結果および寄せ集めの資料からの情報であり、 どこまで真実の情報であるかは定かではありません。 DiskExplorerはこの調査結果を元に作成しているので、 「こりゃやべぇ!」と思った人はjunnnoに連絡してくれると非常にありがたく思います。

マスターブートレコードの領域情報

MBR : Master Boot Record (マスターブートレコード)

ハードディスクが起動されたときに最初に読み込まれるプログラムとデータです。 最初に領域選択画面を表示するあのプログラムです。 領域情報もここに保存されています。 わずか512KBの領域ですが、ここがちょっとダメージを受けるだけで簡単にハードディスクはお亡くなりになります。

詳しいことはよくわかりません。 わかりませんが、DiskExp.では単に開始シリンダと終了シリンダさえわかればいいので、 わかるところだけ利用して領域情報を得ています。

98で使われるハードディスクではマスターブートレコードのプログラムは0シリンダ0ヘッド1セクタ (セクタ番号だけは1からはじまる)にあり、領域情報はその次の2セクタに必ずあります。 下記のPARTENTRYがひとつの領域を示し、サイズは32bytesです。 領域は最大で8個になります。

struct PARTENTRY_98
{
    BYTE BootA;         // ブート種別(詳細不明)
    BYTE BootB;         // ブート種別(詳細不明、上記と共にアクティブ、スリープ、起動可不可、容量などで変化)
    BYTE ReservedA[6];  // 不明(0フィルでもない?)
    WORD ReservedB;     // 不明
    WORD StartCylinder; // 開始シリンダ
    WORD ReservedC;     // 不明
    WORD EndCylinder;   // 終了シリンダ
    BYTE Name[16];      // 領域名(16文字に満たない場合の残りの部分は0x20)
};

AT互換機で使われるハードディスクでは、 マスターブートレコードのプログラムと領域情報が0シリンダ0ヘッド1セクタに詰め込んであります。 通常の領域は最大4個になります。

struct MASTER_BOOT_RECORD_AT
{
    BYTE         Program[446];	// プログラム
    PARTENTRY_AT parts[4];		// 領域情報
    BYTE         reserved2[2];	// 予約
}

struct PARTENTRY_AT
{
    BYTE status;    // 領域状態(0x00:スリープ、0x80:アクティブ)
    BYTE head;      // 領域の開始ヘッド
    WORD cylsec;    // 領域の開始シリンダ・セクタ
    BYTE type;      // 領域の種類
    BYTE headend;   // 領域の終了ヘッド
    WORD cylsecend; // 領域の終了シリンダ・セクタ
    DWORD sec;      // MBRの先頭から領域の先頭までのセクタ数
    DWORD nsecs;    // 領域のセクタ数
};

FATファイルシステムで使われるtypeの値は次のとおりです。

Typeの値
0x00:不明
0x01:FAT12
0x04:FAT16(32MB以下)
0x05:拡張MSDOS領域
0x06:FAT16(32MBより大きい)
0x0B:FAT32
0x0C:FAT32(LBA 拡張int13h)
0x0E:FAT16(LBA 拡張int13h)
0x0F:拡張MSDOS領域(LBA 拡張int13h)

シリンダ・セクタはで次の形式で詰め込んであります。

[ (15) cylinder low (8) | (7) cylinder high (6) | (5) sector (0) ]

Cなら次のようなマクロを使って分解します。 結合のほうはよきに計らって下さい。

#define CYLSEC_CYL(x)   (((x)>>8)|(((x)&0xC0)<<2))
#define CYLSEC_SEC(x)   ((x)&0x3F)

ちなみにDiskExp.ではこんな計算は使っていません。 単にsecにセクタ長を掛けて領域の開始位置を算出しています。 ATではセクタ長以外のパラメータが不要なわけですよ。

4つしかない領域エントリを有効に使うため、 MSDOSは起動領域以外の領域をひとつの「拡張MSDOS領域」として登録します。

拡張MSDOS領域の先頭にはなんとマスターブートレコードがあります。 実際にはProgramが0フィルされているので、ダミーMBRです。 そのダミーMBRの1個目のエントリが論理MSDOS領域の情報になります。 secの位置は、ダミーMBRからの相対数になり、シリンダ、ヘッド、セクタは絶対数になります。 これは要注意です。

次の論理MSDOS領域がある場合は2個目のエントリに拡張MSDOS領域のマークがついています。 すなわち、ダミーMBRから2個目のエントリのsec進めた位置にさらに次のダミーMBRがあります。 次のダミーMBRは先のものと全く同じ構造をしており、 1個目のエントリが論理MSDOS領域の情報、 2個目のエントリが次のダミーMBRの情報を示します。 論理MSDOS領域がある限りこれが延々と続きます。 要するにリスト構造です。 次の領域がない場合、2個目のエントリは0になります。

ブートレコードとIPL

BR : Boot Record (ブートレコード)
IPL : Initial Program Loader (イニシャルプログラムローダ)

僕の知っているブートレコードの情報は下記のとおりです。 DiskExp.でも、まずこの構造体にブートレコードを読み込んでから必要な情報を得ています。

struct BOOT_RECORD
{
    BYTE  Jump[3];              // Jump命令(起動プログラムまでJumpしているらしい)
    BYTE  OEMName[8];           // OEM名(フォーマットプログラムの名前とか)
    WORD  BytesPerSector;       // セクタ長(HDDでは論理セクタ長の場合もある)
    BYTE  SectorsPerCluster;    // 1クラスタのセクタ数
    WORD  ReservedSector;       // ブートレコードの予約セクタ数
    BYTE  FATCount;             // FATの数(通常2)
    WORD  RootDirEntriesCount;  // ルートディレクトリエントリ数(1ディレクトリの最大ファイル数)
    WORD  SectorsCount;         // ディスク領域の総セクタ数(0x10000以上の場合は0)
    BYTE  MediaType;            // メディア種別(詳細不明)
    WORD  FATSectors;           // FAT1個のセクタ数
    WORD  BIOSSectors;          // BIOSの認識するセクタ数
    WORD  BIOSHeaders;          // BIOSの認識するヘッダ数
    DWORD HiddenSectors;        // 隠しセクタ数(この領域の開始セクタ)
    DWORD DoubleSectorsCount;   // ディスク領域の総セクタ数(0x10000以上の場合に利用)
    BYTE  OSData[3];            // OSデータ(詳細不明)
    DWORD VolumeSerial;         // ボリュームシリアル(実際はシリアルではなく乱数)
    BYTE  VolumeLabel[11];      // ボリュームラベル(任意、未定義の時もある)
    BYTE  FileSystem[8];        // ファイルシステム(FAT12またはFAT16、8文字に満たないときは空白、未定義の時もある)
};

クラスタはアロケーションユニットと呼ばれたりもするようです。

ディレクトリ

DE : Directory Entry (ディレクトリエントリ)

ディレクトリはファイル情報を保持する構造体(DE)の配列です。 サブディレクトリの場合はファイル名が.のDEが必ず1個目にあり、そのディレクトリの情報を示します。 またファイル名が..のDEが必ず2個目にあり、親ディレクトリの情報を示します。 親ディレクトリがルートの場合には開始クラスタは0を示します。

ディレクトリが保持できるの最大のDEの数はディスク容量によって決まり、 ルートディレクトリとして確保された容量に等しくなっています。

struct DIRENT{
    BYTE  FileName[8];  // ファイル名(削除ファイルは1文字目が0xE5、未使用エントリは1文字目が0x00)
    BYTE  Extension[3]; // 拡張子(3文字に満たない場合の残りの部分は0x20、ファイル名も同様)
    BYTE  cAttribute;   // 属性(R:0x01、H:0x02、S:0x04、A:0x20、Dir:0x10、Vol:0x08)
    BYTE  Reserved[10]; // 予約(VFATで利用、利用しない場合は0フィル)
    FTIME ftTime;       // 作成時間
    FDATE fdDate;       // 作成日
    WORD  wCluster;     // ファイルまたはディレクトリの先頭クラスタ
    DWORD dwFileSize;   // ファイルサイズ(Dir、Volは常に0、それ以外のファイルで0のとき、クラスタも0)
};

struct FDATE{
    unsigned    ft_day   : 5;   // 日
    unsigned    ft_month : 4;   // 月
    unsigned    ft_year  : 7;   // 年
};
// 0-4ビットが日、5-8ビットが月、9-15ビットが年

struct FTIME{
    unsigned    ft_tsec  : 5;   // 秒(2倍して利用する)
    unsigned    ft_min   : 6;   // 分
    unsigned    ft_hour  : 5;   // 時
};
// 0-4ビットが秒、5-10ビットが分、11-15ビットが時

長いファイル名

LFN : Long File Name (ロングファイルネーム)
LNDE : Long Name Directory Entry (ロングネームディレクトリエントリ)

通常のFATファイルシステムとの互換性を保ちつつ最大260文字のパス名を使えるようにした規格です。 DiskExp.ではまだ対応していませんけど。

struct LNDE
{
    BYTE Flag;          // 終端マーカー(0x40)と結合順序
    BYTE FileName1[5];  // ファイル名部分1
    BYTE Attribute;     // 属性(LNDEでは常にReadOnly + Hidden + System + VolumeLabel)
    BYTE Reserved;      // 予約(0フィル)
    BYTE CRC;           // 8.3ファイル名のCRC
    BYTE FileName2[5];  // ファイル名部分2
    BYTE Cluster;       // クラスタ(LNDEでは常に0)
    BYTE FileName3[5];  // ファイル名部分3
}

いくら互換性を保つためとはいえ、なんて美しくないんだ…。 ひとつのディレクトリエントリに、3パートに分けられた文字列が26bytes使えます。 文字は2バイトで格納する決まりです。ASCII文字は上位バイトを0で埋めます。 日本語はShift-JISがそのまま使えます。

名前は13文字づつ、複数のディレクトリエントリに分けて保存されます。 その結合順序はFlagが示します。 すなわち1なら1個目、2なら2個目、3なら3個目です。 最後のひとつは0x40をORして終端であることを宣言します。

ファイル名の終端は0(もちろん2バイトの)で、 余った部分は0xFFFFで埋めます。

分割されたエントリの格納方法も決まっています。

---------------
最後のLNDE
…
3番目のLNDE
2番目のLNDE
1番目のLNDE
8.3形式のDE
---------------

これでひとつのファイルになるわけです。

DEを見つけてからLNDEを探してファイル情報を構成するか、 終端のLNDEを見つけ次第、親のDEを探してファイル情報を構成するか。

どっちのほうが効率的でしょう?ね?


戻る