LeavaTailの日記

LeavaTailの日記

Linuxエンジニアを目指した技術者の備忘録

Raspberry Pi 4 でファイル書き込み中の USBフラッシュドライブの強制挿抜を試してみる

背景

USB(Universal Serial Bus) は、デバイス同士を接続するためのシリアルバス規格の1つであり、ストレージデバイスや入力デバイス、通信機器、マルチメディア機器など、多岐にわたる用途で利用されている。

その手軽さや汎用性の高さから、近年では家庭用や業務用のコンピュータだけでなく、組み込みシステムやIoTデバイスにも採用されている。一方で、USBデバイスには以下のような問題が発生する可能性がある。

  • 意図しない切断
  • 電力供給不足
  • ソフトウェアの不具合

これらの問題は再挿入やリセットで復旧可能な場合が多いが、USBストレージデバイスではデータ損失のリスクが高まる。 ただし、USBストレージデバイスのような不揮発性メモリを使用している場合、復旧が難しくなることもある。

例えば、USBストレージデバイスがファイルシステムに書き込んでいる最中に切断すると、以下のような問題が発生する恐れがある。

これらの問題を未然に防ぐためには、USBデバイスの不意の切断や再接続によるリスクを理解し、それを考慮したソフトウェア設計が必要である。 本記事では、Raspberry Pi 4を使用してUSBストレージデバイスを対象とした強制挿抜テストを実施し、その影響を評価した。

目的

本記事では、Raspberry Pi 4に接続されたUSBフラッシュドライブを対象に強制挿抜テストを行い、以下の3つの観点からシステムへの影響を確認した。

  1. システムの復旧性: デバイスを再接続後、システムが正常に認識し、動作を再開できるか
  2. ファイルシステムの復旧性: デバイスを再接続後、ファイルシステムが利用可能な状態であるか
  3. データの復旧性: ファイルシステム内のファイルやディレクトリを回収できるか

これにより、LinuxシステムにおけるUSBデバイスの強制挿抜リスクを理解し、安全性と信頼性を向上させるための運用設計の指針を提供することを目指した。

実行環境

Raspberry Pi 4はmicroSDカード経由でRaspberry Pi OSを起動する。 評価対象のUSBフラッシュドライブはUSB 3.0ポートに接続する。

計測環境の概要

以下に、使用するRaspberry Pi 4と関連コンポーネントのスペックを示す。

é …ç›® Raspberry Pi 4
CPU Cortex-A72 (ARM v8) 1.5GHz
メモリ 4GB LPDDR4-3200
micro SD card KTHN-MW016G
OS Raspberry Pi OS (Oct 22nd 2024)
Linux kernel 6.6.51
fsck.vfat dosfstools 4.2
fsstress ltp@ec41611
USB 3.0 (1) USM32GU
ファイルシステム FAT32

実験概要

Raspberry Pi 4に接続したUSBストレージデバイスへのファイル書き込み中に強制抜去し、そのときのストレージ状態からシステムへの影響を評価する。

以下に、今回の実験でポイントとなる要素を挙げる。

  1. USBデバイスの論理的な挿抜: 物理的な負担を避けるため、ソフトウェアレベルでUSBデバイスの接続や切断をシミュレートした。
  2. 負荷プログラム: ファイルシステムに対するランダムな操作をシミュレートするため、fsstressを使用した。
  3. システムへの影響度の評価方法: システムの状態を5段階に分類し、それぞれの影響を評価した。

USBデバイスの論理的な挿抜

物理的な抜き差しはデバイスやポートに負担を与えるため、LinuxのUSBサブシステムを利用して論理的に挿抜をシミュレートした。 今回の実験では、USBデバイスを頻繁に挿抜する必要がある。 しかし、これをすべて手作業で行うのは非現実的であり、デバイスやポートへの物理的な負担も懸念される。 そこで、実際にUSBデバイスを物理的に抜き差しするのではなく、ソフトウェアレベルで挿抜をシミュレートする方法を用いる。

LinuxのUSBサブシステムでは、USBデバイスが検出されると、自動的に適切なドライバにバインド(bind)され、Linuxから操作可能な状態にセットアップされる。 一方、Linuxではユーザ空間から特定のUSBデバイスを手動でドライバにバインドしたり、逆にアンバインド(unbind)したりすることができる。 この操作により、USBデバイスを論理的に挿抜(接続または切断) するような制御ができる。

以下は、Bus 1, Port 1, Config 1, Interface 2に接続されたUSBデバイスを手動でドライバにバインドする例である。

pi@raspberrypi:~$ echo -n "1-1:1.2" > /sys/bus/usb/drivers/usb/bind

次に、同じデバイスを手動でドライバからアンバインドする例を示す。

pi@raspberrypi:~$ echo -n "1-1:1.2" > /sys/bus/usb/drivers/usb/unbind

負荷プログラム

ストレージにどのようなデータを保存するか、そのデータをどのように取り扱うかはユーザによって異なる。 今回の実験で、すべてのユースケースを網羅することは現実的でないため、負荷プログラムを利用することで代用する。

fsstressはXFSテストスイートとして開発されたファイルシステム負荷プログラムであり、Linux Test Project (LTP) の一部として提供されている。 このプログラムは、ファイルシステムの耐久性や信頼性をテストするために、さまざまなファイル操作をシミュレートして負荷をかけるツールである。 多くのオプションが提供されており、アクセスパターンや操作回数などを自由に調整できるため、初心者でも簡単に利用できるのが特徴である。

Raspberry Pi 4 では4つのコアを持つため、プロセスを4つ生成して負荷プログラムをかける。 以下は、ディレクトリ/mnt/testに対して、4つのプロセスが並列してランダムなファイル操作を行う例である。

pi@raspberrypi:~$ fsstress -p 4 -z -n 1000 -d /mnt/test

システムへの影響度の評価方法

データの重要性や扱い方はユーザによって異なるため、システム破損状態の深刻度を定量的に測定することはできない。 そこで、今回の実験では次の5つの状態を独自に定義し、それぞれの状態からシステムへの影響度を評価する。

  • 致命的なエラー(Fatal Error): デバイスが物理的または論理的に重大な破損を受け、システムから完全に認識されなくなる状態
  • 重大なエラー(Serious Error): デバイス上のファイルシステムがマウントできなくなるくらい破損してしまった状態
  • 中度なエラー(Moderate Error): デバイス上のファイルシステムにある一部のファイルやディレクトリが読み取りできなくなった状態
  • 軽度なエラー(Mild Error): ファイルシステム内のファイルやディレクトリがすべて読み取り可能であるものの、一部のファイルが正しく動作しない、あるいはファイル名や内容が破損している状態。
  • 問題なし(Fine): fsck.vfatによる整合性チェックで問題ないと確認された状態

システム破損状態の評価

それぞれの状態を判断する際に使用する具体的な手順は以下の通りである。

  1. デバイスの認識確認: 次のコマンドでデバイスファイルの存在を確認する。

     pi@raspberrypi:~$ test -e /dev/sda
    
  2. ファイルシステムのマウント確認: 読み取り専用でマウントを試みる。

     pi@raspberrypi:~$ mount -o ro /dev/sda /mnt
    
  3. ファイルやディレクトリの読み取り確認: ファイルシステム内の全ファイルとディレクトリを再帰的に読み取る。

     pi@raspberrypi:~$ ls -laR /mnt
     pi@raspberrypi:~$ find /mnt -type f -exec cat {} +
    
  4. ファイルシステムの整合性確認: fsck.vfatコマンドを使用する。この際、FATファイルシステムが設定するdirty volume flagsに関する情報は無視するものとする。以下は具体例である。

     pi@raspberrypi:~$ fsck.vfat -n /dev/sda
     There are differences between boot sector and its backup.
     This is mostly harmless. Differences: (offset:original/backup)
       65:01/00
       Not automatically fixing this.
     Dirty bit is set. Fs was not properly unmounted and some data may be corrupt.
      Automatically removing dirty bit.
    

実験準備

実験を実施するために、以下の手順で環境を整備した。

サンプルプログラムのビルド

開発マシンでLTP(Linux Test Project) のビルドを行い、fsstressをRaspberry Pi 4上で実行可能にした。

  1. 開発マシンにLTPのビルドに必要なパッケージと AArch64用のクロスコンパイラをインストールする

     user@builder:~$ sudo apt install gcc git make pkgconf autoconf automake bison flex m4 linux-headers-$(uname -r)
     user@builder:~$ sudo apt install libc6-dev crossbuild-essential-arm64
    
  2. 開発マシンにLTPをダウンロードし、AArch64用のビルドの設定する

     user@builder:~$ git clone --recurse-submodules https://github.com/linux-test-project/ltp.git
     user@builder:~$ cd ltp
     user@builder:~$ make autotools
     user@builder:~/ltp$ ./configure --host=aarch64-linux-gnu
    
  3. 開発マシンでfsstressをビルドする

     user@builder:~/ltp$ cd testcases/kernel/fs/fsstress
     user@builder:~/ltp/testcases/kernel/fs/fsstress$ make
    

ビルド成果物の fsstress を、Raspberry Pi 4 の /usr/local/binにコピーする。

USBデバイスの接続

USBフラッシュドライブをRaspberry Pi 4に接続した。 以下のコマンドで接続状態を確認し、アドレス情報を取得した。

  1. Raspberry Pi 4 にUSBフラッシュドライブを接続する

     pi@raspberrypi:~$ lsusb -t
     /:  Bus 02.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/4p, 5000M
         |__ Port 1: Dev 2, If 0, Class=Mass Storage, Driver=usb-storage, 5000M
     /:  Bus 01.Port 1: Dev 1, Class=root_hub, Driver=xhci_hcd/1p, 480M
         |__ Port 1: Dev 2, If 0, Class=Hub, Driver=hub/4p, 480M    
    

USBフラッシュドライブは、Bus 2, Port 1 のUSBハブに繋がっていることが確認できる。 これらの値はこの後に使用するため、シェル変数USB_ADDRに代入しておく

pi@raspberrypi:~$ USB_ADDR=$(dmesg | awk '/usb [0-9]+-[0-9]+:/ && /Product: Storage Media/ {sub(/.*usb /, ""); sub(/:.*/, ""); print; exit}')

また、このUSBフラッシュドライブのブロックデバイスファイルもシェル変数DEVICE_FILEに代入しておく。

pi@raspberrypi:~$ DEVICE_FILE=$(readlink -f /dev/disk/by-path/platform-fd500000.pcie-pci-0000:01:00.0-usb-0:1:1.0-scsi-0:0:0:0)

実験手順

  1. マウントポイント用のテンポラリディレクトリを作成する

     TMPDIR="$(mktemp -d)"
    
  2. ストレージデバイスをFAT32ファイルシステムで初期化する

     mkfs.vfat ${DEVICE_FILE}
    
  3. FAT32フラッシュドライブでフォーマットされたUSBフラッシュドライブをマウントする

     mount -t vfat ${MNTOPT} ${DEVICE_FILE} ${TMPDIR}
    
  4. テスト用のディレクトリを作成しておく

     mkdir ${TMPDIR}/test; sync
    
  5. 負荷プログラムをバッググラウンドプロセスとして実行させる

     fsstress -p 4 -z -n 1000 -d ${TMPDIR}/test &
    
  6. ランダムな時間(0〜59秒) 経過後に、USBデバイスを強制抜去する

     sleep $((RANDOM % 60)); echo -n ${USB_ADDR} > /sys/bus/usb/drivers/usb/unbind
    
  7. 負荷プログラムを終了させる

     pkill -9 fsstress
    
  8. マウントされていたFAT32ファイルシステムをアンマウントする

     umount ${TMPDIR}
    
  9. USBデバイスを再度、挿入する

     echo -n ${USB_ADDR} > /sys/bus/usb/drivers/usb/bind
    
  10. USBフラッシュドライブ上のファイルシステム状態を確認する

    fsck.vfat -n ${DEVICE_FILE}
    mount -o ro ${DEVICE_FILE} ${TMPDIR}
    ls -lAR ${TMPDIR}
    find ${TMPDIR} -type f -exec cat {} +
    umount ${DEVICE_FILE}
    

実験結果

本実験では、マウントオプション(${MNTOPT}) を -o defaults と -o sync の2種類で設定し、それぞれ1000回の強制抜去テストを実施した。 システムへの影響度は 「実験概要」 で示した5段階(致命的なエラー、重大なエラー、中度なエラー、軽度なエラー、問題なし) に基づいて分類した。

以下のグラフは、強制抜去までの時間(横軸)とシステムへの影響度の発生回数(縦軸)を示しており、-o defaults と-o syncの結果を比較する。

デフォルトの結果

USB強制挿抜によるファイルシステムの不具合発生頻度

  • 中度なエラー(Moderate Error) ファイルやディレクトリが正常に読めなくなるケースが多発し、全体の約80%を占めた。

  • 軽度なエラー(Mild Error) 強制抜去までの時間が短い(5秒未満)場合に多く発生し、全体の約15%を占めた。

  • 問題なし(Fine) 問題なしと判定されたケースは非常に少なく、全体の約5%に留まった。

syncオプションの結果

USB強制挿抜によるファイルシステムの不具合発生頻度(syncオプションあり)

  • 中度なエラー(Moderate Error)
    稀に発生し、全体の約0.2%に留まった。

  • 軽度なエラー(Mild Error)
    ファイルやディレクトリが正常に読み取り可能なケースが支配的であり、全体の約98%を占めた。

  • 問題なし(Fine)
    問題なしと判定されたケースは全体の約1%に留まった。

考察

Linuxでは、ファイルへの書き込みにwriteback方式が採用されている。 writeback方式では、アプリケーションからの書き込み要求が即座にストレージへ反映されるわけではなく、一時的にカーネルのページキャッシュに蓄えられ、その後適切なタイミングでまとめてディスクへ反映される。 この方式はシステム性能の向上に寄与する一方で、システムの異常終了やデバイスの強制抜去といった予期しない状況下ではリスクを伴う。

特に、以下の問題が生じる可能性がある。

  1. データのキャッシュ残存: 書き込みデータがキャッシュに残ったままストレージに反映されない場合、データ損失が発生する。
  2. メタデータの不完全更新: ファイルシステムのメタデータ更新が未完了の状態で抜去が行われると、ファイルシステム全体の不整合を招く。

writeback方式の特徴として、データがキャッシュにとどまったままストレージに反映されない場合、データ損失が発生することがある。また、ファイルシステムのメタデータ更新が未完了の状態で抜去が行われると、ファイルシステム全体の不整合を招く可能性が高い。

FAT32ファイルシステムの特性リスク

本実験で使用したFAT32ファイルシステムは、以下の3つの領域で構成されている。

  1. Reserved Region: ファイルシステム全体のメタデータを格納
  2. FAT Region: ファイルやディレクトリが占有するクラスタの繋がり(チェイン)を管理
  3. Data Region: ファイルやディレクトリ本体のデータが実際に格納される領域

FAT32ファイルシステムのボリュームレイアウトとファイル書き込みで更新される可能性のある領域

FAT32ファイルシステムはジャーナリング機能のような機能を持たないため、システムの異常終了やデバイスの強制抜去が発生した場合、以下のようなリスクが生じる。

  • Reserved Regionの破損: ファイルシステム全体がマウント不能になる重大なエラーを引き起こす。
  • FAT Regionの破損: 一部または全てのファイルやディレクトリが見えなくなる。
  • Data Regionの破損: ファイルの内容が破損し、読み取り不能になる。

これらの領域が持つ役割のいずれかが更新途中で停止すると、FAT32ファイルシステム全体に深刻な影響を及ぼす可能性が高い。

syncオプションと中度なエラー

LinuxのVFATファイルシステムで-o syncオプションを指定することで、データが即座にストレージに反映されるため、writeback方式によるキャッシュ残存リスクを低減できる。

しかし、同期I/Oであっても書き込み操作がアトミックではないため、強制抜去のタイミングによっては以下の問題が発生する。

  1. 非アトミック性による不整合

    FAT Region や Data Region の更新が途中で停止した場合、データやクラスタチェインが不完全な状態で保存される。 このため、一部のファイルやディレクトリが正常にアクセスできなくなる中度なエラーが発生する。

  2. デバイス固有の特性

    使用しているストレージデバイスの内部キャッシュ機能や、摩耗による書き込みセルの劣化もエラーの一因となる。 特に、仕様違反や低品質のデバイスでは、OSが要求する同期書き込みが正しく実行されない場合がある。

  3. 高負荷環境下での競合

    fsstressのような高負荷操作を並行実行している環境では、複数の書き込み処理が競合し、不完全な更新が発生するリスクが高まる

まとめ

本実験を通じて、USBストレージデバイスにおける異常抜去リスクに対する深い理解が得られた。 -o syncオプションは信頼性を向上させる有効な手段であるが、完全な安全性を保証するものではない。 そのため、耐障害性が求められるシステムでは、ジャーナリングなどの機能を持つファイルシステムの採用や、高品質なデバイスの利用を推奨する。また、運用ポリシーの見直しやバックアップ体制の強化が不可欠である。

変更履歴

  • 2024/12/12: 記事公開

参考文献