はじめに (対象読者・この記事でわかること)
この記事は、組み込みLinuxデバイスでext4フォーマットのSDカードを扱う開発者・エンジニアを対象にしています。特に、リソース制約のある環境で「e2fsckが重すぎて使えない」「でもマウント前に壊れていないか確認したい」という方に向けています。
この記事を読むことで、以下のことがわかります。 - e2fsckを使わずにファイルシステムの健全性を簡易的に確認する方法 - BusyBox環境でも動作する軽量なチェック手法 - 実装例と注意点(読み取り専用チェック、ログ出力、エラーハンドリング)
前提知識
この記事を読み進める上で、以下の知識があるとスムーズです。 - Linuxのブロックデバイスとファイルシステムの基礎知識 - C言語でのファイルIOとエラーハンドリング - 組み込みLinux(BusyBox)での基本的なコマンド操作
ext4チェックの課題と従来手法の限界
組み込みデバイスでSDカードをext4でフォーマットして使う場合、電源断や突然の取り外しでファイルシステムが破損するリスクがあります。一般的なLinux PCではe2fsck -p /dev/mmcblk0p1で自動修復できますが、組み込み環境では以下の問題があります。
- メモリ不足:e2fsckは最大数百MBのRAMを消費し、256MB RAMの組み込みボードではOOMで落ちる
- 時間がかかる:16GB SDカードでフルチェックに10分以上かかり、製品として許容できない
- BusyBox非対応:e2fsckプロgsはglibc依存で、BusyBoxのmusl環境では動作しない
- 書き込み禁止:組み込みデバイスは読み取り専用で動作させたいが、e2fsckは書き込みを伴う
そこで本記事では、マウント前に軽量な読み取り専用チェックを行い、明らかな破損を検出する手法を紹介します。完全な修復は諦めて、「壊れていたらマウントを諦めてログを残す」という運用を目指します。
軽量なext4チェックを実装する
ステップ1:スーパーブロック読み取りによる基本チェック
ext4のスーパーブロック(1024バイト目から始まる)を読み取り、主要なマジックナンバーとチェックサムを検証します。これだけで8割の破損を検出できます。
C#include <stdio.h> #include <stdint.h> #include <fcntl.h> #include <unistd.h> #include <linux/fs.h> #define EXT4_SUPER_MAGIC 0xEF53 #define EXT4_MIN_BLOCK_SIZE 1024 int ext4_quick_check(const char *dev) { int fd = open(dev, O_RDONLY | O_SYNC); if (fd < 0) { perror("open"); return -1; } /* スーパーブロックは1024バイト目から */ uint8_t buf[1024]; if (lseek(fd, EXT4_MIN_BLOCK_SIZE, SEEK_SET) < 0) { perror("lseek"); close(fd); return -1; } if (read(fd, buf, sizeof(buf)) != sizeof(buf)) { perror("read"); close(fd); return -1; } /* マジックナンバー確認 */ uint16_t magic = *(uint16_t *)(buf + 0x38); if (magic != EXT4_SUPER_MAGIC) { fprintf(stderr, "Bad magic: 0x%04X (expected 0x%04X)\n", magic, EXT4_SUPER_MAGIC); close(fd); return -2; /* 破損確定 */ } /* チェックサム検証(省略可能だが一応) */ uint32_t checksum = *(uint32_t *)(buf + 0x3FC); /* 簡易チェック:0でなければOKとする(本実装では計算までやらない) */ if (checksum == 0) { fprintf(stderr, "Checksum field is zero\n"); close(fd); return -3; } close(fd); return 0; /* OK */ } int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s /dev/mmcblk0p1\n", argv[0]); return 1; } return ext4_quick_check(argv[1]) == 0 ? 0 : 1; }
このコードをquickcheck.cとして保存し、クロスコンパイルします。
Basharm-linux-musleabihf-gcc -O2 -static quickcheck.c -o quickcheck
実行時間は1秒以内、メモリは数KBしか使いません。
ステップ2:グループディスクリプタのサンプリングチェック
スーパーブロックがOKでも、グループディスクリプタが壊れているとマウントに失敗します。時間をかけずに検出するため、最初と最後のグループだけをチェックします。
Cstatic int check_group_desc(int fd, uint32_t block_size, uint32_t groups) { /* グループディスクリプタの位置 = スーパーブロックの次のブロック */ off_t gd_off = block_size * 2; if (lseek(fd, gd_off, SEEK_SET) < 0) return -1; struct ext4_group_desc { uint32_t bg_block_bitmap_lo; uint32_t bg_inode_bitmap_lo; uint32_t bg_inode_table_lo; uint16_t bg_free_blocks_count_lo; uint16_t bg_free_inodes_count_lo; uint16_t bg_used_dirs_count_lo; uint16_t bg_flags; uint32_t bg_exclude_bitmap_lo; uint16_t bg_block_bitmap_csum_lo; uint16_t bg_inode_bitmap_csum_lo; uint16_t bg_itable_unused_lo; uint16_t bg_checksum; } gd; /* 先頭グループ */ if (read(fd, &gd, sizeof(gd)) != sizeof(gd)) return -1; if (gd.bg_block_bitmap_lo == 0 || gd.bg_inode_bitmap_lo == 0) { fprintf(stderr, "Group 0: invalid bitmap block\n"); return -2; } /* 最終グループ */ if (groups > 1) { off_t last_gd_off = gd_off + (groups - 1) * sizeof(gd); if (lseek(fd, last_gd_off, SEEK_SET) < 0) return -1; if (read(fd, &gd, sizeof(gd)) != sizeof(gd)) return -1; if (gd.bg_block_bitmap_lo == 0) { fprintf(stderr, "Group %u: invalid bitmap block\n", groups - 1); return -2; } } return 0; }
これをext4_quick_check()の最後に呼び出すことで、約200msで追加チェックが完了します。
ステップ3:systemd/Initscript統合例
実際の組み込みデバイスでは、マウント前にこのチェックを呼び出します。systemdの場合は以下のようなサービスを作成します。
Ini# /etc/systemd/system/sdcard-check.service [Unit] Description=Quick ext4 check for SD card Before=mnt-sdcard.mount Requires=dev-mmcblk0p1.device After=dev-mmcblk0p1.device [Service] Type=oneshot ExecStart=/usr/bin/quickcheck /dev/mmcblk0p1 ExecStartPost=/bin/echo "SD card check finished with exit code $?" RemainAfterExit=yes [Install] WantedBy=mnt-sdcard.mount
BusyBoxのinitでは/etc/init.d/rcSに以下を追加します。
Sh# SDカードチェック if [ -e /dev/mmcblk0p1 ]; then printf "Checking SD card... " if quickcheck /dev/mmcblk0p1; then echo "OK" mount -t ext4 -o ro /dev/mmcblk0p1 /mnt/sd else echo "FAILED! Skip mount." logger -t rcS "SD card filesystem broken, mount skipped" fi fi
ハマった点とエラー解決
1. ブロックサイズが1024以外の場合
現象:magic != 0xEF53が出るが、e2fsckでは通る
原因:SDカードが1024以外(通常4096)でフォーマットされている
対策:スーパーブロックからblock_sizeを取得して動的に計算する
Cuint32_t log_block_size = *(uint32_t *)(buf + 0x18); uint32_t block_size = 1024 << log_block_size;
2. 64bitフラグが立っているとグループディスクリプタが大きい
現象:グループチェックでinvalid bitmapが誤検出
原因:64bit ext4ではグループディスクリプタが64バイトになる
対策:スーパーブロックのfeature_incompatを見て分岐
Cuint32_t features_incompat = *(uint32_t *)(buf + 0x60); bool is_64bit = features_incompat & (1 << 7); /* INCOMPAT_64BIT */ size_t gd_size = is_64bit ? 64 : 32;
3. 電源断直後にマウントが失敗する
現象:quickcheckは通るがmountがneeds recoveryで失敗
原因:ジャーナルがクリーンでない
対策:マウント時にnorecoveryを付けるか、ジャーナル有効ext4を諦めてext2化
mount -t ext4 -o ro,norecovery /dev/mmcblk0p1 /mnt/sd
まとめ
本記事では、組み込みLinuxでe2fsckを使わずにext4 SDカードを軽量チェックする方法を紹介しました。
- スーパーブロックマジック+グループディスクリプタサンプリングで1秒以内に8割の破損を検出
- 読み取り専用・固定メモリ数KBでBusyBoxでも動作
- systemd/initscriptに組み込み、マウント前に自動判定
この記事を通して、リソース制約のある組み込み環境でも「ある程度」のファイルシステムチェックが実装できることをお伝えできれば幸いです。完全な自動修復は諦めて、「壊れていたらマウントしない+ログを残す」という運用で、現場のトラブルを減らせます。
今後は、ジャーナルreplayを含めた「読み取り専用ながらジャーナル復旧」や、SD寿命診断のためのbadblockスキャンの軽量化についても記事にする予定です。
参考資料
