← OSと低レイヤのしくみ コース
20. ジャーナリング — 途中で電源が落ちても壊れない仕組み
ファイルシステムが「書いている途中で電源が落ちても壊れない」ようにジャーナルを使う仕組みを、図で追っていきましょう。
① 1つのファイル操作は「複数ブロックの更新」に分解される
アプリの操作write() でファイルを1ブロック分伸ばす
ファイルシステムが内部で3か所を更新する
ブロックビットマップ「このブロックは使用中」の印を立てる
inodei_size とブロック番号(エクステント)を更新
データブロック本体実際のファイル内容を書き込む
3つが揃って初めて「正しい状態」
整合の取れたファイルシステム
- ・ディスクへの書き込みはブロック単位で、3か所を同時に原子的に書く手段はない
- ・順番に1つずつ書くしかない、というのがこの問題の出発点です
ユーザーから見れば「1回の書き込み」でも、ディスク上では複数の独立したブロック更新に分かれます。
② 途中で電源が落ちると不整合が残る
ビットマップ 書き込み済使用中の印は立った
inode 書き込み済ファイルは伸びたことになっている
データ 未書き込み中身は古いゴミのまま
ここで電源断
再起動後のファイルシステムinode は「中身がある」と言うが実体は無い
届いたブロックの組み合わせが違えば
リークビットマップだけ届き、誰も使わないブロックが埋まる
二重割り当てinode だけ届き、未使用扱いのブロックが別ファイルにも配られる
- ・どのブロックまで届いたかはクラッシュのタイミング次第で、事前には決められない
- ・全部を走査して直す fsck は、容量が大きいほど時間がかかります
どの順番で書いても「途中まで書けた状態」は必ず存在し、それがファイルシステムの不整合になります。
③ 解決策 — 先に「これから何をするか」をジャーナルへ書く
ジャーナル領域(jbd2)これから行う更新の予告を順番に書き足す場所
ジャーナル inode は通常 inode 8予告が確定したら
本体領域inode・ビットマップ・データが実際に置かれる場所
ジャーナルの中身は3種のブロックで構成される
ディスクリプタブロックh_blocktype = 1
更新後のブロック群本体のどこへ書くかをタグが指す
コミットブロックh_blocktype = 2
- ・各ジャーナルブロックの先頭12バイトは journal_header_s で、h_magic は 0xC03B3998
- ・ディスクリプタ内の journal_block_tag_s の t_blocknr が「本体のどのブロック向けか」を示します
- ・h_sequence が同じものが1つのトランザクションとしてまとまります
本体をいきなり触らず、まずジャーナルに更新内容を追記します。追記なので途中で切れても本体は無傷です。
④ 書き込みの順序 — コミットが「境界線」になる
1. ジャーナルへ更新内容を書くディスクリプタ + 更新後ブロックを追記
ここまでは本体に一切影響しない
2. コミットブロックを書くh_blocktype = 2 が届いた瞬間に「確定」
ここが原子性の境界確定後、ゆっくり本体へ
3. チェックポイントジャーナルの内容を本体の inode・ビットマップ・データへ反映
反映が済んだら
4. ジャーナル領域を解放反映済みの区間を再利用可能にする
- ・コミットブロックは1ブロックなので、書けたか書けなかったかの二択になります
- ・ext4(5) によると commit=nrsec でコミット間隔を指定でき、既定値は5秒です
- ・ジャーナルは循環バッファで、古い区間は反映済みになれば上書きされます
「1ブロックのコミットが届いたか」という単純な判定に、複数ブロック更新の全か無かを縮約しているのが要点です。
⑤ クラッシュ後の再起動 — replay で拾うか捨てるか
再起動してマウントファイルシステムが dirty ならジャーナルを走査
各トランザクションのコミットブロックを確認
コミット有りh_sequence の順に本体へ再適用(replay)
コミット無しそこで走査を打ち切り、以降は破棄
replay の結果
整合の取れた状態に復帰操作は「全部やった」か「何もやってない」のどちらか
- ・replay は本体へ書き直すだけなので、同じ内容を何度適用しても結果は同じです
- ・revoke ブロック(h_blocktype = 5)は、後で書き換えられたブロックの再適用を省いて復旧を速めます
- ・mount(8) は「ext3 と ext4 は dirty ならジャーナルを replay する」ため ro でも書き込みが起きうると注記しています
コミット済みは再適用、コミット前は破棄。この二択で「途中まで書けた状態」が消えます。
⑥ なぜデータ本体は既定でジャーナルに入れないのか
data=ordered(既定)ジャーナルを通るのはメタデータのみ
データはコミット前に本体へ直接書き出す順序を強制する理由
データを先に本体へinode がブロックを指す前に中身を確定させる
その後にメタデータをコミット古いゴミが新ファイルから見えるのを防ぐ
他のモードと比べると
data=journalデータもジャーナルへ書いてから本体へ
同じ内容を2回書くので書き込み量が倍data=writeback順序保証なし、メタデータ確定後にデータが届きうる
- ・ext4(5) は data=ordered を「メタデータがジャーナルにコミットされる前に、全データを本体へ強制的に書き出す」既定モードと記しています
- ・kernel.org の ext4 文書は、ordered モードが XFS や JFS の既定と同様の「メタデータジャーナリング」に相当すると述べています
- ・ext4(5) の data=journal は「全データがジャーナルへコミットされてから本体へ書かれる」モードで、書き込みが二重になります
全部をジャーナルに通せば安全ですが書き込みが倍になります。既定はデータを先に書く順序制約だけで、安全性と性能を両立させています。