FATファイルシステム(その1)
ファイルシステムとは
コンピュータの補助記憶装置(ハードディスクなど)にデータを記録する場合、データの集合を一つのファイルとして記録している。このファイルを管理する仕組みとして考案されたものがファイルシステムである。
次のような場合を考える。
- 初期状態では、ある大きさの空き領域が存在している
- File-Aを削除したことにより、空き領域が増えた(二つになった)
- File-Cは一つの空き領域では入らないため、二つの領域を使って記録した
このように、ハードディスクなどの容量を効率的に使うためには、一つのファイルを分割して記録したほうが良い場合がある。ファイルシステムのルールに従って記録することにより、上の例のFile-Cが、二つの領域に分かれて記録されていることを意識せず、使うことができるようになる。
この例はファイルシステムの必要性をわかりやすく説明したものであるが、その他にもディレクトリ構造の実現や、ファイルの追記、削除などの機能を提供してくれる。
ファイルシステムにはいくつかの種類がある。MS-DOSを起源にMicrosoft社のOSで使われているFATファイルシステム、Windowsで使われるNTFSファイルシステム、Linuxで使われるextファイルシステムなどがある。
FATファイルシステム
FATはFile Allocation Tableの略であり、ディスク内でのファイルの位置を記録したテーブルのことである。このFATを使用したファイルシステムをFATファイルシステムと呼んでいる。
MS-DOSの時代のFAT12から進化し、FAT16、FAT32がある。
FAT12は12bitのFATを持ち、フロッピーディスクの時代に使われていたものである。現在でもフロッピーディスクや、小容量のメモリカードに使われることがある。
FAT16は16bitに拡張されたFATを持ち、以前はハードディスクなどでも使用されていた。現在では小容量のメモリカードに使われることが多い。
FAT32は32bit化されたFATであり、大容量のディスクのディスクにも使用できる。
本テキストでは小容量のSDカードをターゲットとしているので、以降、特に断りがない限りFAT16について説明をする。
FATファイルシステムの構造
FATファイルシステムにおいて、情報が記録されている場所は「セクタ番号」で表す。「セクタ」とは、数バイトの情報をひとまとめにしたもので、FATファイルシステムでは通常1セクタ=512バイトである。
※ ここから推測できるように、1バイト単位のリード/ライトはできない。最小でも1セクタ(=512バイト)のリード/ライトになる。
一般のメモリの場合は「物理アドレス」を用いたが、ファイルシステムの場合は「セクタ番号」を用いるので注意が必要である。
※ セクタ0はSDカードのアドレス0x00000000、セクタ1はアドレス0x00000200である
FATファイルシステムでは、下図のように様々な領域が並んでいる。
- MBR(Master Boot Record) : マスターブートレコード。パーティション情報などが書かれている。
- BPB(BIOS Parameter Block) : FATやRDEに関する情報
- FAT(File Allocation Table) : ファイルの場所を示す情報
- RDE(Root Directory Entry) : ルートディレクトリの情報。ルートに存在するファイルのファイル名など。
- ユーザデータ : ユーザのデータ(ファイル)が存在する領域
各々は、上のような機能を持っている。
以下の説明では、次のワークスペースをダウンロード・ビルドし、実際にSDカードのセクタを読み出して、確認しながら進める。
fat1.zip
MBR(Master Boot Record)
MBRはディスクやSDカードの先頭に位置する(セクタ番号0)。
ブートストラップローダ部は、その名が示すように、ディスクから起動するためのプログラムを格納しておくことが主な目的であるが、ここでは詳しく説明をしない。
MBR内で重要なものは、パーティションテーブルである。MBR内のオフセット446以降に存在している。ここには16バイトのパーティション情報が4組格納されている(合計64バイト)。
MBRのパーティションテーブル部の抜粋
パーティションテーブル
名称 | オフセット | サイズ | 機能 |
ブートフラグ | 0x00 | 1 | 0x80:ブート可能、0x00:ブート不可 |
1st sector(CHS) | 0x01 | 3 | パーティションの最初のセクタ番号(CHS方式) |
識別子 | 0x04 | 1 | 0x01:FAT12、0x04, 0x06, 0x0e:FAT16、0x0b:FAT32 |
Last sector | 0x05 | 3 | パーティションの最後のセクタ番号 |
1st_sector(LBA) | 0x08 | 4 | パーティションの最初のセクタ番号(LBA方式) |
nmr of sectors | 0x0c | 4 | パーティション内のセクタ数 |
パーティションテーブルの抜粋
パーティションテーブルには、1st sector情報が二つ存在するが、ここではLBA方式を使用する。(CHS方式はCylinder Head Sectorの略で、古いハードディスクの時代に用いられた方式である)
この表の1st_sector(LBA)が示すセクタが、パーティションの先頭であり、そこからBPB, FAT, ...のように情報が書き込まれている。
第一パーティションの先頭セクタ番号を取り出す場合、MBR内のオフセットでは 446 + 8 = 454となり、454~457の4バイトが、1st_sector(LBA)である。この値はリトルエンディアン表記になるので、注意が必要である。
このパーティションテーブルは4組あるので、パーティションは最大4つ持つことができる。
— 演習 —
MBRをリードし、1st_sector(LBA)を計算する関数 ex_MBR() を完成せよ。
この値の示すセクタが、次に説明するBPBが存在するセクタである。
BPB(BIOS Parameter Block)
BPBは、MBRのパーティションテーブルから得られた1st_sector(パーティションの最初のセクタ)に存在する。
BPBの例
BPBには、BPB自身のサイズ、FATのサイズと数、などの情報が記録されている。
名称 | オフセット | サイズ | 機能 |
BS_jmpBoot | 0 | 3 | ブートのためのジャンプ命令 |
BS_OEMName | 3 | 8 | |
BPB_BytesPerSec | 11 | 2 | 1セクタあたりのバイト数 |
BPB_SecPerClus | 13 | 1 | 1クラスタあたりのセクタ数 |
BPB_RsvdSecCnt | 14 | 2 | BPBを含む予約エリアのセクタ数(BPBのサイズ) |
BPB_NumFATs | 16 | 1 | FATの数 |
BPB_RootEntCnt | 17 | 2 | ルートディレクトリで登録(エントリ)可能なファイル数 |
BPB_TotSec16 | 19 | 2 | 全セクタ数 |
BPB_Media | 21 | 1 | 0xf8:固定メディア、0xf0:リムーバブル |
BPB_FATSz16 | 22 | 2 | 1つのFATが占めるセクタ数 |
BPB先頭部分の抜粋
BPB_BytesPerSecは1セクタあたりのバイト数であり、通常512である。
BPB_SecPerClusは、1クラスタあたりのセクタ数である。「クラスタ」とは、いくつかのセクタをまとめたもので、ユーザデータはクラスタ単位で管理されている。1クラスタ=2セクタであれば、ユーザのデータ(ファイル)には、最小でも1クラスタ(=2セクタ)が使われる。この値は1クラスタ=2^nセクタとしたときのnである。
BPB_RsvdSecCntは、BPBを含む予約エリアが占めるセクタ数である。この値を用いることにより、BPB領域に続くFATの先頭セクタを計算することができる。
BPB_NumFATsは、FATが何組あるのかを示している。
BPB_RootEntCntは、ルートディレクトリのエントリ数(≒ファイル数)を示している。この値と、1エントリあたりのサイズ(=32バイト)を用いることにより、RDEのサイズを計算することができる(ユーザ領域の先頭セクタを計算することができる)。
BPB_FATSz16は、1つのFATが占めるセクタ数である。これとBPB_NumFATsを用いることにより、FAT領域の合計セクタ数を求めることができる(RDEの先頭セクタを計算できる)。
このように、BPBに記録されている各種パラメータは、FATファイルシステムで非常に重要な役割を持っている。
— 演習 —
BPBをリードし、BPB_RsvdSecCnt, BPB_NumFATs, BPB_RootEntCnt, BPB_FATSz16を計算する関数 ex_BPB() を完成せよ。
RDE(Root Directory Entry)
RDEの位置は、次のように計算することができる。
FirstRDESector = 1st_sector + BPB_RsvdSecCnt + (BPB_NumFATs * BPB_FATSz16)
RDEの例
RDEには、ルートディレクトリ(例えばC:)に存在するファイルやディレクトリの情報が記録されている。
上図の赤色の四角ひとつが、一つのファイルやディレクトリの情報を持っている。
ルートディレクトリに存在できるファイルやディレクトリの数(エントリ数)はBPB_RootEntCntで得られる。1つのエントリについて32バイト必要なので、RDEのサイズは
RDESz = 32 * BPB_RootEntCnt
である。
ディレクトリエントリー構造
名称 | オフセット | サイズ | 機能 |
Name | 0 | 8 | ファイル、ディレクトリ名 |
Ext | 8 | 3 | 拡張子 |
Attr | 11 | 1 | 属性 0x10:ディレクトリ、0x20:ファイル |
NTRes | 12 | 1 | 予約 |
CrtTimeTenth | 13 | 1 | ファイル作成時刻(x10ms) |
CrtTime | 14 | 2 | ファイル作成時刻 |
CrtDate | 16 | 2 | ファイル作成年月日 |
LstAccDate | 18 | 2 | 最終アクセス年月日 |
FstClusHI | 20 | 2 | 予約 |
WrtTime | 22 | 2 | 更新時刻 |
WrtDate | 24 | 2 | 更新年月日 |
FstClusLO | 26 | 2 | クラスタ番号 |
FileSize | 28 | 4 | ファイルサイズ(バイト) |
ディレクトリエントリーの例
NameとExtは、ファイル名と拡張子である。FAT16ではファイル名に8文字、拡張子に3文字が割り当てられている。これより長いファイル名は、別の方法で管理されている。
Attr(属性)は、ファイルとしてのエントリーなのか、ディレクトリとしてのエントリーなのか、等を示している。属性が0x20の場合はファイル、0x10の場合はディレクトリとなる。
CrtTimeはファイル作成時刻、WrtTimeは最終書き込み時刻を示している。2バイトのデータで、次のようなフォーマットになっている。
ビット位置 | 意味 |
15~11 | 時:00~23 |
10~5 | 分:00~59 |
4~0 | 秒:00~29 秒÷2の値を保持 |
秒のフィールドは、60秒をカウントするにはビット数が不足するので、秒/2の値となっている。
※ このフィールドだけでは30秒と31秒は区別できない。
実際の時刻データの解読
CrtDateは作成日、LstAccDateは最終アクセス日、WrtDateは最終更新日である。これらは2バイトのデータで、フォーマットは次のようになっている。
ビット位置 | 意味 |
15~9 | 1980年を0とした年:0~127 |
8~5 | 月:1~12 |
4~0 | 日:1~31 |
実際の年月日データの解読
FstClusLOは、ファイルの中身が実際に記録されているクラスタ番号を示している。「クラスタ」とは「セクタ」をいくつかまとめたもので、ユーザのデータ(ファイル)はクラスタ単位で記録・管理されている。
1クラスタあたりのセクタ数は、BPBのBPB_SecPerClusである。
FileSizeはファイルサイズを示している。
— 演習 —
- First_RDE_sect を計算し、RDE を取得する関数 get_RDE() を完成せよ。
- RDEをリードし、SDカード内に存在するファイルのファイル名、拡張子、属性、作成日、作成時刻などを取得、計算する関数 get_file_info() を完成せよ。