一覧OS-03-3 · 入出力と記憶装置

SSDの書き込みとデータの永続化

論理アドレスと物理ページの対応を更新し、回収・TRIM・揮発バッファの状態を追います。

説明

フラッシュSSDではページへ書き込み、より大きな消去ブロック単位で消去します。既存ページの上書きをその場で行わず、空きページへ新しい内容を置いて論理アドレスの対応を変える方式が使われます。FTLはこの論理・物理対応や空き領域を管理します。

1消去ブロック4ページ、各ページに一つの小さい値を置くモデルで、同じ論理ページの更新と空き領域の回収を行います。不要領域の通知であるTRIMを反映し、最後に書込み受付と不揮発領域への保存を分けて確認します。

1論理ページから物理ページへ

物理ページ0〜3をブロック0、4〜7をブロック1とします。Noneは消去済みで未書込みのページです。論理ページ0・1を物理0・1へ置きます。

pages = [None]*8二つの消去ブロック; mapping = {0:0,1:1}論理から物理への対応; pages[0],pages[1] = 'A','B'初回のページ書込み; pages,mapping物理内容と対応表

論理0の値Aは物理0、論理1の値Bは物理1にあります。OSが使う論理位置と、フラッシュ上の物理位置は同じ番号であり続ける必要はありません。

2同じ論理ページを更新する

論理0をA2へ変えるため、空き物理ページ2へ書いて対応を2へ更新します。古い物理0は無効になります。

pages[2] = 'A2'別の空きページへ書く; old = mapping[0]; mapping[0] = 2対応を新ページへ; invalid = {old}旧ページを無効化; pages,mapping,sorted(invalid),pages[mapping[0]]古い内容・対応・現在値

読出しは対応表を経由するためA2を返します。物理0には古いAが残っていますが、現在の論理0としては使いません。無効になったことと消去済みであることは異なります。

3有効ページと空きページを数える

ブロック0には有効な物理1・2、無効な0、空き3があります。

live = set(mapping.values())対応表から有効ページを取得; [(i,'free' if pages[i] is None else 'valid' if i in live else 'invalid') for i in range(4)]各ページの状態

無効な0へそのまま新ページを書けるとは限りません。このモデルでは、再利用する前にブロック全体の消去が必要です。空き3と無効0を同じ空き容量として扱わないようにします。

4有効なデータを別ブロックへ退避

ブロック0を消去する前に、物理1のBを4へ、物理2のA2を5へ移します。対応表も移動先へ更新します。

pages[4],pages[5] = pages[1],pages[2]有効な2ページをコピー; mapping[1],mapping[0] = 4,5論理対応を移動先へ; copied_pages = 2内部コピーの数; pages,mapping退避後の内容と対応

有効データの退避に2ページの追加書込みが発生します。ホストから新しい更新を受けていなくても、装置内の回収処理で書込みが増える理由です。

5ブロックを消去して再利用する

現在の対応がブロック0を指していないことを確認し、物理0〜3をまとめて消去します。

safe_to_erase = all(p >= 4 for p in mapping.values())有効対応が残っていないか確認; pages[:4] = [None]*44ページを一括消去; erase_count = [1,0]ブロックごとの消去回数; safe_to_erase,pages,[(logical,pages[physical]) for logical,physical in sorted(mapping.items())]空きと論理データを照合

論理0=A2、論理1=Bを保ったまま、ブロック0に4ページの空きが戻ります。消去回数はブロックごとに増えるため、同じ場所ばかりを使わないwear levelingも必要になります。

6ホスト書込みと内部書込み

ここまでホストが書いたのはA・B・A2の3ページ、装置はさらに回収用2ページを書いています。

host_pages = 3ホストのページ書込み; programmed_pages = host_pages+copied_pages内部コピーも加算; programmed_pages,host_pages,programmed_pages/host_pages物理書込み・ホスト書込み・比率

書込み増幅は5/3です。今回の固定記録の比率であり、装置の恒常的な値ではありません。空き領域や更新の局所性、回収方針によって変わります。

7不要になった論理領域を通知する

論理1が不要になったと通知された状態を、対応表から削除して表します。物理4の内容はその瞬間には残っています。

trimmed = mapping.pop(1)論理1を不要とする; live_after_trim = set(mapping.values())残る有効ページ; trimmed,pages[trimmed],sorted(live_after_trim)旧物理位置・残存内容・有効集合

TRIM相当の通知により、Bを今後の回収で保存する必要がなくなります。不要通知は即時の物理消去や機密データの確実な消去と同義ではありません。通知後の読出し値の扱いは装置・プロトコルの仕様に依存します。

8書き込みの受け付けと永続化の完了を区別する

電源断保護のない揮発バッファを持つモデルで、A3を受け付けた直後と、永続化処理の完了後を比べます。

persistent = {0:'A2'}; volatile = {0:'A3'}保存済みと受付中の値; lost_on_power_failure = persistent.copy()受付直後に電源断した場合; persistent.update(volatile); volatile.clear()永続化完了後の状態; lost_on_power_failure,persistent,volatile二つの時点を比較

受付直後に電源が切れれば保存済みA2が残り、永続化の完了後ならA3が残ります。書込み完了通知、flushやFUAの意味、電源断保護の有無を合わせて保存保証を確認します。ファイルの更新では、OSやファイルシステム側の同期手順も関係します。

まとめ

更新は空き物理ページへ行い、論理対応を切り替えて古いページを無効にします。

回収には有効データのコピーとブロック消去が必要で、内部の書込みを増やします。

TRIMは不要領域の通知であり、保存完了や安全な消去の保証とは分けて確認します。

操作履歴と解説を振り返れます。

Python 3

1
pages = [None]*8; mapping = {0:0,1:1}; pages[0],pages[1] = 'A','B'; pages,mapping
(['A', 'B', None, None, None, None, None, None], {0: 0, 1: 1})
2
pages[2] = 'A2'; old = mapping[0]; mapping[0] = 2; invalid = {old}; pages,mapping,sorted(invalid),pages[mapping[0]]
(['A', 'B', 'A2', None, None, None, None, None], {0: 2, 1: 1}, [0], 'A2')
3
live = set(mapping.values()); [(i,'free' if pages[i] is None else 'valid' if i in live else 'invalid') for i in range(4)]
[(0, 'invalid'), (1, 'valid'), (2, 'valid'), (3, 'free')]
4
pages[4],pages[5] = pages[1],pages[2]; mapping[1],mapping[0] = 4,5; copied_pages = 2; pages,mapping
(['A', 'B', 'A2', None, 'B', 'A2', None, None], {0: 5, 1: 4})
5
safe_to_erase = all(p >= 4 for p in mapping.values()); pages[:4] = [None]*4; erase_count = [1,0]; safe_to_erase,pages,[(logical,pages[physical]) for logical,physical in sorted(mapping.items())]
(True, [None, None, None, None, 'B', 'A2', None, None], [(0, 'A2'), (1, 'B')])
6
host_pages = 3; programmed_pages = host_pages+copied_pages; programmed_pages,host_pages,programmed_pages/host_pages
(5, 3, 1.6666666666666667)
7
trimmed = mapping.pop(1); live_after_trim = set(mapping.values()); trimmed,pages[trimmed],sorted(live_after_trim)
(4, 'B', [5])
8
persistent = {0:'A2'}; volatile = {0:'A3'}; lost_on_power_failure = persistent.copy(); persistent.update(volatile); volatile.clear(); lost_on_power_failure,persistent,volatile
({0: 'A2'}, {0: 'A3'}, {})

演習完了