ストレージ技術

このページの目次

設定値の保存には数バイトしか必要ありませんが、裏側では数 KiB の消去が必要であり、書き込み中は命令フェッチがブロックされる可能性があり、電源断によって半分のレコードが残ることもあります。ストレージの選択は容量や読み書き速度だけでなく、読み取り粒度、プログラミングおよび消去粒度、いつを永続化成功とみなすか、最悪の書き込みレイテンシと寿命を誰が管理するかといった点をまず問うべきです。

データの用途最初に考慮すべきもの省略してはならない条件
実行バッファSRAM、外部 RAMDMA アクセス可能性、タイミング、揮発性
プログラムとリソース内蔵または外部 FlashXIP、消去中のアクセス、起動パス
少量の設定EEPROM、ログ形式 Flash、キーバリューストアチェックサム、バージョン、更新の原子性
高頻度・小規模レコードF-RAM または適切なログ方式バススループット、有限の耐久性、マルチバイト整合性
大容量レコード/エクスポートSD、eMMC などの管理型ストレージ長尾レイテンシ、コントローラの動作とファイルシステムの同期

メディア、バス、コントローラ、ファイルシステムを階層で見る

SRAM/DRAM は記憶素子の種類を記述し、SPI/QSPI はインターフェースを記述し、SD/eMMC はプロトコルと管理機能を含むストレージ製品形態であり、FAT/littlefs はデータを整理します。異なる層の名称を単に「どちらが速いか」というランキングに並べることはできません。読み取りレイテンシ、継続帯域幅、プログラミング時間、消去時間は、それぞれ異なる速度指標です。

メディアまたは形態典型的な組織方法ホストが気にすべき点
SRAM / DRAM揮発性ランダムアクセス;DRAM はリフレッシュ必要電源供給、コントローラ、タイミングとバスアクセス
NOR Flashランダム読み出し可能、規定単位でのプログラミングとブロック消去1→0 のプログラミング、消去制限、XIP サポート
生 NANDページ読み書き、ブロック消去ECC、不良ブロック、読み出し擾乱、ウェアレベリング
EEPROMバイト/ページ書き込み、内部で関連する消去/書き込みを完了ページ境界、ビジー時間、耐久性と保持条件
F-RAMFlash 式ブロック消去のない不揮発性読み書きインターフェース速度、寿命、電源断時の書き込みウィンドウ
SD / eMMCコントローラが論理ブロックインターフェースとして提示キャッシュ/同期、長尾レイテンシと電源断復旧能力

XIP にはプロセッサのメモリマッピングインターフェース、起動設定、および場合によってはキャッシュサポートが必要です。SPI Flash を接続しただけで即座に実行できるわけではありません。一部のプラットフォームでは、まずコードを RAM にコピーすることもあります。内蔵 Flash の消去中に別の領域を同時に読み取れるかどうかは、バンク、バス、およびチップの制限によります。

NOR のプログラミングと消去を分ける理由

典型的な NOR は消去後に全 1 となり、プログラミングによって選択されたビットが 0 に変わります。0 を 1 に戻すには、所属するブロックの消去が必要です。したがって、空白のレコード領域に新しいデータを追記する場合、すぐに消去せずにプログラミングを行うことができます。任意のバイトをその場で変更する場合、消去とデータ移行が発生する可能性があります。具体的なデバイスには最小プログラミング単位、ページ境界、アライメント、内蔵 ECC、および繰り返しプログラミングの制限があり、ビット論理だけで許可される操作を推測することはできません。

例:4 KiB 消去ブロック:16 個の 256 B プログラミングページページ 0256 Bページ 1256 Bページ 2256 Bページ 3256 Bページ 4256 Bページ 5256 Bページ 6256 Bページ 7256 B続くページ 8 … 15;具体的なサイズはデバイス仕様によるNOR:プログラミングページと消去ブロックは異なる粒度消去済み 1111 → プログラミング 1010;1111 に戻すには所属ブロックの消去が必要1 バイトの更新を即座のブロック消去と同一視することはできません。ページ境界、最小プログラミング単位、および繰り返しプログラミングの制限を無視することもできません。

デバイスのプログラミングページが 256 B と仮定し、ページ内オフセット 250 から 20 B を書き込む場合、ドライバの要件に従って、現在のページに 6 B、次のページに 14 B と分割する必要があります。境界を越えた書き込みは、一部のデバイスではページ内でラップアラウンドする可能性があり、自動的に次ページへ続くと仮定してはいけません。典型的なコマンドフローには、書き込みイネーブル、プログラミング/消去コマンドの発行、ビジー状態のポーリング、およびエラー処理が含まれます。SPI 転送の終了が内部操作の完了を意味するわけではありません。

AT25SF041 は 4 Mbit SPI Flash です。デバイス名の bit と Byte も区別する必要があります。Renesas AT25SF041 データシート

EEPROM と F-RAM:高速書き込みが自動的にトランザクションの原子性を意味しない

24LC256 を例に挙げます。これは 256 Kbit、つまり 32 KiB の EEPROM で、64 B のページ書き込みバッファを持っています。ページ書き込みは境界に従う必要があり、内部書き込みサイクル中の就绪検出と ACK セマンティクスはデータシートに従って実装され、バス ACK をデータが完全に書き込まれたものと見なしてはいけません。Microchip 24LC256

F-RAM は NOR 式のブロック消去を必要とせず、頻繁な小規模書き込みタスクに適していますが、耐久性は依然として有限であり、条件付きの仕様です。チップ内部での書き込みが速くても、I²C/SPI シリアル転送が瞬時に完了するわけではありません。複数のバイトで構成される1つのビジネスレコードが途中で電源断されると、部分的にしか更新されない可能性があります。FM25V02A は 256 Kbit、32 KiB であり、2 Mbit ではありません。Infineon FM25V02A

寿命は実際の消去/書き込みアンプで計算する

同じ消去ブロックを 10 秒ごとに消去する場合、定格 100,000 回の消去は約 11.57 日のカウント予算に対応します。重要なのは「毎回同じブロックを消去して保存する」という条件であり、「10 秒ごとに1回のビジネス書き込みがある」ことではありません。空白領域に複数のレコードを追記してから消去する場合、消去頻度は大幅に低下させることができます。

ブロックが 4096 B、1つの完全なレコードが 64 B と仮定すると、理想的には1ブロックに 64 件のレコードが収まります。16 ブロックを均等にラウンドロビンする場合、1024 件のレコードを書き込むごとに、各ブロックが1回の消去を経験します。100,000 回の消去制限で、理想的なスループット予算は約 1.024 億件のレコードとなりますが、メタデータ、回収による移動、スペア領域、不良ブロック、および具体的なプログラミングの制限により、これは減少します。これは製品の耐用年数の保証ではなく、保持時間、温度、およびその他のデバイスの寿命が先に限界に達する可能性があります。

動的ウェアレベリングは、書き込み対象のブロックに摩耗を分散させ、静的ウェアレベリングは長期間変更されないデータを移動してより多くのブロックを利用します。アルゴリズムも書き込みアンプを発生させるため、ファイルシステムが「ウェアレベリング付き」と主張しているからといって、すべてのアドレスが均等な摩耗を受けるわけではないことを前提にせず、実際の物理操作回数を測定する必要があります。

電源断後にデュアルパーティションで有効なバージョンをどのように選択するか

準備A:旧レコード、有効B:空白 / 安全に消去可能書き込みA:依然として保持B:本文 + チェックサム、未コミットコミットA:後で回収可能B:完全コミット、シーケンス番号更新デュアルパーティション復旧:新しいレコードの完全なコミット前、旧レコードが復旧の根拠となる起動時に2つのパーティションをスキャン:フォーマット、長さ、コミットフラグ、チェックサムがすべて有効な場合にのみシーケンス番号を比較原子性はメディアの書き込み/電源断の動作とドライバの順序に依存します。同じ消去ブロック内に2つのデータを配置しても、独立したデュアルパーティションにはなりません。

2つのパーティションは独立して消去できる必要があります。新しいレコードの準備中は旧レコードを保持し、フォーマットバージョン、シーケンス番号、長さ、本文、およびチェックサムを先に書き込み、順序と完了条件を確認してから、メディアが許す独立したプログラミング粒度に従ってコミットフラグを書き込みます。起動時、未コミット、境界外長さ、フォーマットエラー、またはチェックサム失敗のレコードを拒否し、有効なレコードの中からより新しいシーケンス番号を選択します。新しいレコードが確実にコミットされてから、旧パーティションを回収します。

電源断の位置復旧時に観測される状態取るべき結果
新パーティションの消去中、または本文書き込み中旧パーティションは有効、新パーティションは不完全旧バージョンを保持
チェックサム書き込み完了、未コミット新パーティションはまだコミット済みレコードではない旧バージョンを保持
コミット完了、旧パーティション未消去両パーティションとも有効新しいシーケンス番号を選択
旧パーティションの回収中新パーティションは有効新バージョンを使用

コミット書き込み自体が中断される可能性もあるため、有効なエンコーディングとチェックサムルールを定義する必要があります。CRC は一般的なエラーの多くを検出できますが、数学的な任意の損傷を絶対的に保証するものではなく、悪意のある改ざんの認証も提供できません。シーケンス番号のラップアラウンド、フォーマットのアップグレード、およびデバイス電源断時にターゲットパーティション以外のデータに影響を与える可能性の処理も行わなければなりません。単純なデュアルパーティションモデルでは、SD コントローラ内部のすべての失敗モードを直接カバーすることはできません。

以下に、新しいレコードをバイト順に書き込む各ブレークポイントを示し、新しいレコードの半分だけを選択しないことを検証します。モデルでは、旧パーティションは独立して損傷しておらず、書き込みは指定されたプレフィックスのみを形成し、シーケンス番号はラップしないと仮定します。実際の Flash の最小プログラミング単位と tearing write の動作は別途検証する必要があります。

import struct
import zlib

def encode(seq, payload):
    body = struct.pack('<IH', seq, len(payload)) + payload
    return body + struct.pack('<I', zlib.crc32(body)) + b'DONE'

def decode(blob):
    if len(blob) < 14:
        return None
    seq, size = struct.unpack('<IH', blob[:6])
    end = 6 + size
    if end+8 > len(blob) or blob[end+4:end+8] != b'DONE':
        return None
    if zlib.crc32(blob[:end]) != struct.unpack('<I', blob[end:end+4])[0]:
        return None
    return seq, blob[6:end]

old = encode(7, b'old configuration')
new = encode(8, b'new configuration')
for cut in range(len(new)+1):
    target = new[:cut] + bytes([255])*(len(new)-cut)
    valid = [x for x in (decode(old), decode(target)) if x is not None]
    recovered = max(valid, key=lambda x: x[0])
    assert recovered[0] == (8 if cut == len(new) else 7)
assert decode(b'bad') is None
assert decode(old)[1] == b'old configuration'
print('すべてのプレフィックス電源断ポイント:コミット前は旧レコードを復元、コミット完了後は新レコードを選択')

どのバイトで電断するとどの版が残る?

断点11→12で変わるのは最終コミットバイトだけでも回復選択が変わる。データ破損ならコミット済みでも新記録を拒否。旧領域を消すと、未コミット新記録では削除済み旧記録を代替できないことが分かる。

図を準備しています
どのバイトで電断するとどの版が残る?

チェックサム完了はコミットではない。独立旧領域を残すことで未コミットや破損から回復できる。

SD / eMMC:論理ブロックの裏側にはコントローラがいる

管理型ストレージは NAND の多くの詳細をカプセル化していますが、ホストは論理ブロック読み書きを扱っても、内部にはキャッシュ、マッピング、ガーベジコレクション、およびウェアレベリングがあります。同じサイズの書き込みでも、ビジー時間が異なる場合があります。継続的な収集バッファは、許容される最長の停止時間をカバーし、タイムアウトとサンプリドロップポリシーを定義する必要があります。

例えば、160 kB/s のデータストリームが 200 ms の書き込み停止に遭遇した場合、少なくとも 32,000 B の追加バッファが必要であり、スケジューリングやその他の余裕は含まれていません。カードのピーク MB/s だけを見ていても、サンプリドロップゼロを保証するものではありません。SD ストレージバスと SDIO 拡張機能は同じ用語に混同すべきではありません。SPI またはネイティブバスの使用は、カードとホストのサポートと初期化要件に応じて実装され、固定の「クロックを数個追加する」というパッチに依存してはいけません。

ブロックデバイスにはカスタムの生フォーマットを格納でき、必ずしもファイルシステムを使用する必要はありません。PC とファイルを交換する場合は、ターゲットフォーマットをサポートする実装を選択できます。電圧、プルアップ、およびデカップリングは、対応するインターフェースとハードウェア資料に従って設計され、すべての信号線に一律にプルアップを追加してはいけません。

ファイルシステムの整合性がビジネスコミットの完了を意味しない

FAT は広範なファイル交換機能を提供しますが、複数のメタデータ更新と電源断のセマンティクスはアプリケーションで慎重に処理する必要があります。littlefs は制約のあるデバイスと Flash の電源断復旧を対象としており、対応するメタデータとコピーオンライト設計を使用しています。ただし、依然として下位層の読み取り、プログラミング、消去、および同期の契約が正しく必要とされます。littlefs デザイン

例えば、新しい設定ファイルの書き込み後、API に従って同期/クローズし、適切なアトミック置換フローを使用する必要があります。2つのファイルの個別の成功した更新が、自動的にファイル間トランザクションを構成するわけではありません。littlefs のドキュメントは、キャッシュ付きストレージドライバが sync 時に同期を確定させることを明確に要求しており、そうでなければデータ整合性を保証できません。マウントの成功が、最後に同期されていないレコードが必ず保持されることを意味するわけではありません。littlefs インターフェースと同期の説明

電源保持キャパシタは設計の一部となり得ますが、電圧降下を検出し、新しいトランザクションを停止し、最悪の待機操作にエネルギーを残す必要があります。定電流で簡略化すると C ≥ I·Δt/ΔV となり、例えば 50 mA、10 ms、許容降下 0.3 V で約 1667 μF となります。ESR、許容差、変換効率、および書き込み最低電圧も考慮する必要があります。未知の長尾書き込み時間は、適当にキャパシタを追加するだけでは保証できません。

粒度と予算の再計算

from math import isclose
assert 256*1024//8 == 32768
page, offset, count = 256, 250, 20
first = min(count, page-offset%page)
assert (first, count-first) == (6, 14)
assert isclose(100000*10/86400, 11.574074074074074)
records = (4096//64)*16*100000
assert records == 102400000
assert 160000*0.2 == 32000
assert isclose(0.05*0.010/0.3*1e6, 1666.6666666666667)
print('容量単位、ページ跨ぎ分割、消去予算、バッファ、保持キャパシタの計算が通過')

本稿はモデルとブレークポイント復旧ロジックを検証するものであり、実際のストレージを消去/書き込みしたり、実際の電源断テストを行ったりしたものではありません。

参照