設定値の保存には数バイトしか必要ありませんが、裏側では数 KiB の消去が必要であり、書き込み中は命令フェッチがブロックされる可能性があり、電源断によって半分のレコードが残ることもあります。ストレージの選択は容量や読み書き速度だけでなく、読み取り粒度、プログラミングおよび消去粒度、いつを永続化成功とみなすか、最悪の書き込みレイテンシと寿命を誰が管理するかといった点をまず問うべきです。
データの用途 最初に考慮すべきもの 省略してはならない条件
実行バッファ SRAM、外部 RAM DMA アクセス可能性、タイミング、揮発性
プログラムとリソース 内蔵または外部 Flash XIP、消去中のアクセス、起動パス
少量の設定 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-RAM Flash 式ブロック消去のない不揮発性読み書き インターフェース速度、寿命、電源断時の書き込みウィンドウ
SD / eMMC コントローラが論理ブロックインターフェースとして提示 キャッシュ/同期、長尾レイテンシと電源断復旧能力
XIP にはプロセッサのメモリマッピングインターフェース、起動設定、および場合によってはキャッシュサポートが必要です。SPI Flash を接続しただけで即座に実行できるわけではありません。一部のプラットフォームでは、まずコードを RAM にコピーすることもあります。内蔵 Flash の消去中に別の領域を同時に読み取れるかどうかは、バンク、バス、およびチップの制限によります。
NOR のプログラミングと消去を分ける理由
典型的な NOR は消去後に全 1 となり、プログラミングによって選択されたビットが 0 に変わります。0 を 1 に戻すには、所属するブロックの消去が必要です。したがって、空白のレコード領域に新しいデータを追記する場合、すぐに消去せずにプログラミングを行うことができます。任意のバイトをその場で変更する場合、消去とデータ移行が発生する可能性があります。具体的なデバイスには最小プログラミング単位、ページ境界、アライメント、内蔵 ECC、および繰り返しプログラミングの制限があり、ビット論理だけで許可される操作を推測することはできません。
例:4 KiB 消去ブロック:16 個の 256 B プログラミングページ ページ 0 256 B ページ 1 256 B ページ 2 256 B ページ 3 256 B ページ 4 256 B ページ 5 256 B ページ 6 256 B ページ 7 256 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で変わるのは最終コミットバイトだけでも回復選択が変わる。データ破損ならコミット済みでも新記録を拒否。旧領域を消すと、未コミット新記録では削除済み旧記録を代替できないことが分かる。
図を準備しています 再読み込み
どのバイトで電断するとどの版が残る? チェックサム完了はコミットではない。独立旧領域を残すことで未コミットや破損から回復できる。
{"id":"flash-prefix-commit","title":"どのバイトで電断するとどの版が残る?","summary":"チェックサム完了はコミットではない。独立旧領域を残すことで未コミットや破損から回復できる。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"prefix\" data-i18n=\"prefix\"></label><output for=\"prefix\" id=\"prefix-value\"></output><input id=\"prefix\" type=\"range\" min=\"0\" max=\"12\" value=\"7\" step=\"1\"></div><label class=\"check\"><input type=\"checkbox\" id=\"old\" checked><span data-i18n=\"old\"></span></label><label class=\"check\"><input type=\"checkbox\" id=\"corrupt\"><span data-i18n=\"corrupt\"></span></label><div class=\"track-row\"><span data-i18n=\"bytes\"></span><div id=\"bytes\" class=\"track\"></div></div><div class=\"metrics\"><div><span data-i18n=\"commit\"></span><strong id=\"commit\"></strong></div><div><span data-i18n=\"crc\"></span><strong id=\"crc\"></strong></div><div><span data-i18n=\"chosen\"></span><strong id=\"chosen\"></strong></div></div><p id=\"eq\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:550;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:550 20px ui-monospace,monospace}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:ui-monospace,monospace;font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px ui-monospace,monospace;background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px ui-monospace,monospace}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 ui-monospace,monospace;white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:ui-monospace,monospace}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px ui-monospace,monospace;padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 ui-monospace,monospace;overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n.chart-wrap{margin:18px 0}.chart-label{font-size:12px;color:var(--muted);min-height:3.2em}.curve{display:block;width:100%;height:auto;max-height:320px;overflow:visible}.curve text{font:11px ui-monospace,monospace;fill:var(--muted)}.gridline{stroke:var(--rule);stroke-width:1}.metrics strong{font-size:20px;min-height:3.2em}.formula{font:12px/1.8 ui-monospace,monospace;min-height:9em}.readout{min-height:8em}.presets button{flex-basis:30%}.scope p{overflow-wrap:anywhere} .unit-note{font-size:12px;color:var(--muted);margin:10px 0}\n\n.metrics span{word-break:break-all;line-break:strict}.metrics>div{padding-right:6px}\n\n#bytes{display:grid;grid-template-columns:repeat(6,1fr)}#chosen{min-height:5em}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));function crc32(bytes){let c=0xffffffff;for(const b of bytes){c^=b;for(let i=0;i<8;i++)c=(c>>>1)^((c&1)?0xedb88320:0);}return(c^0xffffffff)>>>0}function draw(){let p=n('prefix'),head=[1,8,4,8,16,32,64],crc=crc32(head),record=[...head,...[0,8,16,24].map(s=>(crc>>>s)&255),165],mem=record.map((v,i)=>i<p?v:255);if($('#corrupt').checked&&p>3)mem[3]^=1;const stored=(mem[7]|mem[8]<<8|mem[9]<<16|mem[10]<<24)>>>0,ok=crc32(mem.slice(0,7))===stored,commit=mem[11]===165,valid=commit&&ok&&mem[0]===1&&mem[2]===4,chosen=valid?'new':$('#old').checked?'previous':'none';show('prefix-value',p);cells('bytes',mem.map((v,i)=>({text:v.toString(16).padStart(2,'0').toUpperCase(),cls:i>=p?'empty':i===11?'gold':'on'})));set('commit',+commit);set('crc',+ok);set('chosen',t(chosen));$('#chosen').dataset.state=chosen;set('eq','CRC32 = 0x'+crc.toString(16).padStart(8,'0')+'\\nvalid = format ∧ length ∧ CRC ∧ commit\\nsequence(new) = 8; sequence(old) = 7');}inputs(draw);","audio":false,"strings":{"bytes":"新領域12バイト(16進)","chosen":"回復選択","commit":"コミットマーカー有効","corrupt":"新記録のデータ1ビットを破損","crc":"CRC一致","heading":"どのバイトで電断するとどの版が残る?","intro":"電断位置を直接選び、コミットとCRC、旧領域の有無を確認。","new":"新版8","none":"有効記録なし","old":"旧領域の有効記録を保持","prefix":"電断前の完了バイト数","previous":"旧版7","reset":"リセット","scope":"12バイト:版、連番、長さ、データ4、CRC32、コミットA5。前方からバイト書込み、独立旧領域有効、連番周回や実破損範囲なし。","scopeLabel":"モデルの範囲と前提"}}
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 ( ' 容量単位、ページ跨ぎ分割、消去予算、バッファ、保持キャパシタの計算が通過' )
本稿はモデルとブレークポイント復旧ロジックを検証するものであり、実際のストレージを消去/書き込みしたり、実際の電源断テストを行ったりしたものではありません。
参照