OS・実行環境

ファイルシステム

データを管理する仕組み。フラッシュ向けもある(ext4/FAT/UBIFS)。

概要

ファイルシステムとは、記憶媒体(フラッシュメモリ、HDDなど)上のデータを「ファイル」と「ディレクトリ」という階層構造で管理する仕組みだ。OSはファイルシステムを通じて、どのデータがどこに格納されているかを追跡し、読み書き・削除・名前変更などの操作を提供する。

組み込みLinuxシステムでは、使用する記憶媒体の種類によって適切なファイルシステムが異なる。HDDやeMMCのようにブロックデバイスとして扱えるものにはext4やFAT32が使えるが、rawのNANDフラッシュにはUBIFS・JFFS2のようなフラッシュ専用ファイルシステムが必要だ。フラッシュメモリは書き込み回数に上限があり(ウェアレベリングが必要)、ブロック単位でしか消去できないという特性があるため、HDDと同じファイルシステムを直接使うとすぐに劣化する。

RTOSやベアメタル環境では、FatFsのような軽量なファイルシステムライブラリをマイクロコントローラ上で動かし、SDカードやSPI Flashに対してファイル操作を行うケースも多い。

歴史・背景

UNIXのファイルシステムはケン・トンプソンとデニス・リッチーが1969年に設計したUFS(Unix File System)に起源を持つ。「すべてはファイルである」というUNIXの哲学により、デバイスや名前付きパイプもファイルとして扱われ、統一されたインターフェースが実現した。

MS-DOSとともに普及したFAT(File Allocation Table)は1977年にビル・ゲイツとマーク・マクドナルドが設計し、FAT12→FAT16→FAT32と拡張されてきた。シンプルな構造で多くのOSがサポートしていることから、USBメモリやSDカードの標準フォーマットとして現在も広く使われる。

Linuxで主流のext2は1993年にRemy Cardが設計し、ext3(ジャーナリング追加、2001年)、ext4(大容量・高性能化、2008年)へと発展した。組み込みLinuxではeMMCとext4の組み合わせが現在の標準的な構成だ。

フラッシュ専用ファイルシステムはNANDフラッシュの普及とともに発展した。JFFS2(Journalling Flash File System version 2)は2001年にRedHatが開発し、2006年にUBIFS(UBIFS: Unsorted Block Image File System)がその後継として登場した。UBIFSはウェアレベリングをUBIレイヤーに分離することで性能と信頼性を大幅に改善した。

技術仕様

ファイルシステムの種類と特性

ファイルシステム用途ジャーナリングフラッシュ対応最大ファイルサイズ主な使用先
ext4汎用Linuxあり間接(eMMC/SDのFTL経由)16TBRaspberry Pi, BeagleBone, SBC一般
FAT32互換性重視なし間接(FTL経由)4GBSDカード, USBメモリ, ブートパーティション
exFAT大容量可搬媒体なし間接16EB大容量SDカード(SDXC)
UBIFSraw NANDあり直接対応非制限産業用機器の raw NAND
JFFS2raw NAND/NORあり(ログ構造)直接対応非制限古い組み込みシステム
YAFFS2raw NANDあり直接対応非制限Androidの初期版、組み込み
squashfs読み取り専用圧縮なし間接非制限ルートファイルシステム(読み取り専用)
tmpfsRAM上の一時領域なしなし(RAM)RAM容量依存/tmp, /run
overlayfs差分レイヤーなし(下位依存)間接下位依存Dockerコンテナ, ルートfs重ね合わせ
FatFs組み込みライブラリなし間接(FTL経由)4GB(FAT32)STM32, Arduino + SDカード
LittleFS組み込みフラッシュログ構造直接対応2GBESP32 SPI Flash, MCUフラッシュ

rawフラッシュとブロックデバイスの違い

NANDフラッシュの構造:
┌─────────────────────────────────────┐
│ NANDフラッシュチップ(ハードウェア)    │
│  ┌──────┐┌──────┐┌──────┐┌──────┐ │
│  │Block0││Block1││Block2││Block3│ │  ← 消去単位(128KB〜512KB)
│  │ Page0│└──────┘└──────┘└──────┘ │
│  │ Page1│                           │
│  │ Page2│                           │  ← 書き込み単位(4KB〜16KB)
│  └──────┘                           │
└─────────────────────────────────────┘

rawとして使う場合(MTDデバイス):
  /dev/mtd0 → UBI → UBIFS  (ウェアレベリング・バッドブロック管理はUBIが担当)

eMMCや内蔵FTL付きNANDとして使う場合(ブロックデバイス):
  /dev/mmcblk0 → ext4/FAT  (ウェアレベリングはeMMCコントローラが担当)

ext4の内部構造

ext4ディスクレイアウト:

┌──────────────┬──────────────┬──────────────┬──────────────┐
│ ブートセクタ  │ ブロックグループ0 │ ブロックグループ1 │ ...         │
└──────────────┴──────────────┴──────────────┴──────────────┘

各ブロックグループ:
┌───────────┬──────────┬───────────┬──────────┬──────────────┐
│ スーパーブロック│ GDT      │ ブロックBmap│ iノードBmap│ データブロック │
│(FS全体情報)│(グループ記述)│(空き追跡)  │(iノード空き)│(ファイルデータ)│
└───────────┴──────────┴───────────┴──────────┴──────────────┘

iノード(ファイルのメタデータ):
  - ファイルサイズ、パーミッション、タイムスタンプ
  - データブロックへのポインタ(直接/間接/二重間接/三重間接)
  - 拡張属性(xattr)

UBIとUBIFSの階層構造

raw NANDフラッシュ上でUBIFSを使う場合、以下の階層になる。

アプリケーション(ファイルシステムAPI)

UBIFS(ファイルシステム層)

UBI(Unsorted Block Images)← ウェアレベリング・バッドブロック管理

MTD(Memory Technology Device)← ハードウェア抽象化

NANDフラッシュコントローラ(ハードウェア)
# UBI/UBIFSのセットアップ(組み込みLinuxでの典型的な手順)

# MTDデバイスにUBIをアタッチ
ubiattach /dev/ubi_ctrl -m 0 -d 0

# UBIボリューム作成
ubimkvol /dev/ubi0 -N rootfs -s 200MiB

# UBIFSとしてマウント
mount -t ubifs ubi0:rootfs /mnt/root

# フォーマット(初回またはリセット時)
ubiformat /dev/mtd0 -f rootfs.ubifs

動作原理

ジャーナリングと電源断耐性

突然の電源断(組み込みでは一般的なリスク)でもファイルシステムの一貫性を保つために、ジャーナリングが重要だ。ジャーナリングは書き込み前に変更内容を「ジャーナル(ログ領域)」に記録し、完全に書き終わった後にコミットする。

電源断が起きる可能性のある操作シーケンス(ジャーナルなし):
1. データブロックを書き込む     ← ここで電源断
2. iノードを更新する           ← 未実行
3. ディレクトリエントリを追加   ← 未実行
→ ファイルシステムが不整合になり、fsckが必要

ジャーナリングあり:
1. ジャーナルに操作を記録(トランザクション開始)
2. データブロックを書き込む     ← ここで電源断
(次回起動時にジャーナルから未完了トランザクションを検出→ロールバック)
→ ファイルシステムの一貫性を素早く回復

ext4のジャーナリングモード:

モード速度データ保護説明
orderedメタデータのみデータを書いてからメタデータをジャーナル(デフォルト)
writeback速いメタデータのみ順序保証なし(データ損失リスクあり)
journal遅いデータ+メタデータ全データをジャーナル(最も安全)

LittleFSのログ構造(MCU向け)

ESP32やSTM32などのMCU内蔵フラッシュ用に設計されたLittleFSは、電源断安全性を重視したログ構造ファイルシステムだ。

// LittleFSの使用例(ESP32 Arduino/ESP-IDF)
#include "littlefs/lfs.h"

lfs_t lfs;
lfs_file_t file;

// LittleFS設定
const struct lfs_config cfg = {
    .read  = flash_read,
    .prog  = flash_prog,
    .erase = flash_erase,
    .sync  = flash_sync,
    .read_size      = 16,
    .prog_size      = 16,
    .block_size     = 4096,   // SPIフラッシュのページサイズ
    .block_count    = 128,    // 128ブロック × 4KB = 512KB
    .cache_size     = 16,
    .lookahead_size = 16,
    .block_cycles   = 500,    // ウェアレベリング目安サイクル数
};

void littlefs_example(void) {
    lfs_mount(&lfs, &cfg);

    // ファイルに書き込む
    lfs_file_open(&lfs, &file, "config.txt", LFS_O_RDWR | LFS_O_CREAT);
    const char *data = "sensor_interval=1000\n";
    lfs_file_write(&lfs, &file, data, strlen(data));
    lfs_file_close(&lfs, &file);

    lfs_unmount(&lfs);
}

FatFs(組み込みFATライブラリ)

ChaN氏が開発したFatFsは、RTOSやベアメタルで使える軽量FAT実装だ。SDカードやSPI Flashへのアクセスに広く使われる。

// FatFs使用例(STM32でのSDカード書き込み)
#include "ff.h"  // FatFsヘッダ

FATFS fs;
FIL fil;
UINT bw;

void fatfs_write_example(void) {
    // マウント
    if (f_mount(&fs, "", 1) != FR_OK) {
        Error_Handler();
    }

    // ファイルオープン(作成または追記)
    if (f_open(&fil, "log.csv", FA_WRITE | FA_OPEN_APPEND) != FR_OK) {
        Error_Handler();
    }

    // データ書き込み
    char buf[64];
    snprintf(buf, sizeof(buf), "%lu,%.2f,%.2f\r\n",
             HAL_GetTick(), temperature, humidity);
    f_write(&fil, buf, strlen(buf), &bw);

    // 重要:f_sync()またはf_close()しないとバッファがフラッシュされない
    f_sync(&fil);  // 電源断対策
    f_close(&fil);
    f_unmount("");
}

用途・ユースケース

組み込みLinuxのパーティション構成

典型的なEmbedded Linuxデバイス(eMMC搭載SBC)のパーティション構成。

eMMC(例: 8GB)のパーティション構成:

┌──────────────────────────────────────────────────────────┐
│ /dev/mmcblk0                                              │
│  /dev/mmcblk0boot0  4MB  ← ブートROM用(u-bootなど)    │
│  /dev/mmcblk0p1   128MB  ← FAT32 /boot(カーネル・DTB)│
│  /dev/mmcblk0p2     2GB  ← ext4  / (rootfs)           │
│  /dev/mmcblk0p3     4GB  ← ext4  /data(ユーザーデータ) │
│  /dev/mmcblk0p4   残り   ← ext4  /var(ログ等可変データ)│
└──────────────────────────────────────────────────────────┘

マウント構成例(/etc/fstab):
/dev/mmcblk0p1  /boot      vfat  defaults,ro          0  2
/dev/mmcblk0p2  /          ext4  defaults,noatime      0  1
/dev/mmcblk0p3  /data      ext4  defaults,noatime      0  2
/dev/mmcblk0p4  /var       ext4  defaults,noatime      0  2
tmpfs           /tmp       tmpfs defaults,size=64m     0  0
tmpfs           /var/run   tmpfs defaults,mode=0755    0  0

読み取り専用rootfsによる信頼性向上

産業用機器や無人運用デバイスでは、ルートファイルシステムを読み取り専用(read-only)にすることで、電源断時のファイルシステム破損リスクを排除する手法が有効だ。

# /etc/fstabでrootfsをread-onlyにマウント
/dev/mmcblk0p2  /  ext4  defaults,ro,noatime  0  1

# 書き込みが必要なディレクトリにはtmpfsかoverlayfsを使う
tmpfs  /var/log  tmpfs  defaults,size=32m  0  0
tmpfs  /tmp      tmpfs  defaults,size=64m  0  0

# overlayfsで/etcを読み書き可能にする(再起動でリセット)
# mount -t overlay overlay -o lowerdir=/etc,upperdir=/tmp/etc_rw,workdir=/tmp/etc_work /etc

A/B パーティション(信頼性の高いOTAアップデート)

A/Bパーティション構成でのOTAアップデート:

現在: パーティションA(稼働中)→ 通常動作
アップデート: パーティションBに新ファームウェアを書き込む

再起動: パーティションBから起動
確認成功: BをActiveにマーク
失敗: 自動的にAにフォールバック

実装例(U-Boot + fw_setenv):
fw_setenv boot_part b  # 次回ブートをBパーティションに切り替え
reboot

ログとデータ収集

センサーデータやシステムログをフラッシュメモリに記録する際のファイルシステム選択が重要だ。頻繁な書き込みが想定される場合、ウェアレベリング対応のファイルシステムを選ぶか、書き込み頻度を下げる設計(バッファリング、ラウンドロビン書き込み)が必要になる。

実装・開発のポイント

eMMC/SDカードへのext4最適化

# ext4フォーマット時のオプション(組み込み向け)
mkfs.ext4 \
  -L rootfs \           # ラベル
  -b 4096 \             # ブロックサイズ(一般的な4KB)
  -i 8192 \             # iノード間隔(多くのsmallファイルがあれば小さく)
  -O ^has_journal \     # ジャーナル無効(高速化、電源断リスクあり)
  -E lazy_itable_init=0 \  # iテーブル初期化をフォーマット時に完了
  /dev/mmcblk0p2

# マウントオプションの最適化
# noatime: アクセス時刻の更新を省略(フラッシュへの書き込み削減)
# commit=60: ジャーナルコミット間隔を60秒に(書き込み削減)
mount -o noatime,commit=60 /dev/mmcblk0p2 /

フラッシュ書き込みの最小化

組み込みフラッシュの寿命を延ばすための実装指針。

// 設定データの書き込み最小化の例
// 悪い例:毎回書き込む
void bad_save_config(void) {
    for (;;) {
        update_settings();  // 設定が変わるたびに
        lfs_file_open(...); // フラッシュに書き込む(頻繁すぎる)
        lfs_file_write(...);
        lfs_file_close(...);
    }
}

// 良い例:変更があった時のみ、かつ最小間隔を設ける
static bool config_dirty = false;
static uint32_t last_save_time = 0;
#define SAVE_INTERVAL_MS 5000  // 最低5秒間隔

void check_and_save_config(void) {
    if (config_dirty &&
        (HAL_GetTick() - last_save_time) > SAVE_INTERVAL_MS) {
        save_config_to_flash();
        config_dirty = false;
        last_save_time = HAL_GetTick();
    }
}

void on_config_changed(void) {
    config_dirty = true;  // 即書き込みではなくフラグを立てるだけ
}

ファイルシステムチェックと修復

# ext4のfsck(起動時の自動チェック)
# /etc/fstabの最後の数字: 0=チェックなし, 1=最優先, 2=2番目
# e2fsck手動実行(アンマウント後)
e2fsck -f /dev/mmcblk0p2

# ジャーナルを有効にして電源断耐性を高める
tune2fs -O has_journal /dev/mmcblk0p2

# NANDフラッシュのバッドブロック確認
mtd_debug read /dev/mtd0 0 1  # 先頭ブロックのテスト
nandtest /dev/mtd0             # 全ブロックテスト(破壊的!)

他技術との比較

ファイルシステムext4FAT32UBIFSLittleFSsquashfs
用途Linux汎用可搬媒体raw NANDMCUフラッシュ読み取り専用
ジャーナリングありなしありログ構造なし(必要なし)
最大ボリューム1EB2TB非制限設計依存非制限
圧縮対応なし(標準)なしあり(LZO等)ありあり(多形式)
フラッシュ特化なしなしありありなし
ウェアレベリングFTL依存FTL依存UBIが担当内蔵なし
電源断安全あり(journal)なしありあり不変
Linuxサポート標準標準標準ライブラリ必要標準

ウェアレベリングとの関係

フラッシュメモリは書き込み回数に上限があり(NANDは数千〜数万回、NORは数万〜数十万回)、特定ブロックへの集中書き込みを避けるウェアレベリングが必要だ。eMMCやSDカードはコントローラが内部でウェアレベリングを行うが、raw NANDではUBIレイヤーがこれを担当する。LittleFSはMCU内蔵フラッシュ向けに内部でウェアレベリングを実装している。

YoctoBuildrootでのファイルシステムイメージ生成

YoctoやBuildrootはターゲットデバイス向けのファイルシステムイメージ(ext4, squashfs, ubifs等)を自動生成できる。IMAGE_FSTYPES変数でイメージ形式を指定し、ビルドシステムが適切なツール(mkfs.ext4, mksquashfs, mkfs.ubifs)を使ってイメージを生成する。

関連用語

参考リンク