Post on 03-Jul-2015
【幕張読書会】
UNIXカーネルの設計
3章 バッファキャッシュ担当: @ktateish
前回の復習(1) カーネルアーキテクチャ
ユーザプログラム→システムコール→ファイルシステム→バッファキャッシュ (今日はココ)→ブロックデバイス→デバイスドライバ→ディスクコントローラ
→物理ディスク
前回の復習(2) プロセスの状態遷移
● 実行中(ユーザモード)● 実行中(カーネルモード)● 休眠状態● 実行可能状態
本書のカーネルはカーネルモード実行中はコンテキストスイッチしない。割り込みはかかるが必要に応じて禁止することも。いずれもカーネル空間のデータ破壊を防ぐため
バッファキャッシュ概要
● ブロックデバイスの内容をメモリに保持● データをブロック単位で管理● ヘッダ部とデータ部。ヘッダ重要● ハッシュと双方向リンクリストで管理● LRU(Least Recently Used)
意識しておくこと
● ディスクに対してメモリは圧倒的に高速● 非同期書込してよければそうしたい(高効率)● ディスクとバッファキャッシュの整合性● ディスクへの非同期書き込み完了は割り込みコ
ンテキストで処理される(随所で割り込まれ、バッファが操作される)
バッファヘッダ
● デバイス番号(簡単のため以後省略)● ブロック番号● データ部へのポインタ● バッファ状態変数(ロック中、要遅延書込等)● ハッシュ用リストポインタ*2(prev,next)● 自由リスト用ポインタ*2
バッファキャッシュ概観
● ブロック番号をキーとしたハッシュ● ひとつの自由リスト
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …16ハッシュ
自由リスト
ハッシュ(1)● デバイス番号、ブロック番号をキーとした高速な
アクセス用● ブロック番号 mod ハッシュサイズ でリストヘッド
(番兵)を特定
m1
m6
m0
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16ブロック92にアクセスしたい場合は 92 mod 7 = 1 なので、このリストを探す
ハッシュ(2)● リスト内は線形検索。順序なし● ふたつ以上のバッファが同じブロック内容を保
持することはできない(整合性のため)● (補足)リストの最後は番兵にループ
m1
m6
m0
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16このリストをたどって (線形検索して)92に到達。存在しなければキャッシュミス。
自由リスト(1)● {再,新規}割当て可能なバッファのリスト● System起動時、全バッファはここに繋がる● 一度有効なデータ(ブロック)を保持するとハッ
シュにもつながれる(下図)
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
自由リスト(2)● バッファを使用中(プロセス/ディスクとの読み書
き)は自由リストから外される● バッファの使用がおわったら自由リストの最後
尾に繋がれる
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
使用が終わった=再利用可能になったバッファは最後尾に追加
自由リスト(3)● ハッシュを検索してヒットした場合は自由リスト
の途中からとりだされる● {新規,再}割り当ての際、先頭から取り出す● つまり古いバッファから再利用 → LRU!
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
キャッシュヒットの場合は途中からはずす
キャッシュミスした場合は自由リストの先頭を取り出して目的のブロックに再割当て
つまりどういうことだってばよ?
バッファキャッシュはLRU(最後に参照されてから最も時間がたったバッファを入れ替える)をハッシュとリストの組み合わせで実現している。
引き続き具体的なインターフェースについて解説する。
が、その前に・・・
注意事項(ロックについて)
● バッファを「ロックする」、という表現が多用されるが、これは「上位または下位レイヤで使用中のフラグをたてる」という意味。
● 「使用中」とは、バッファがユーザ空間(上位)との間あるいはディスク(下位)との間でコピー中であるということ。
注意事項(ロックについて)
● バッファは上下レイヤでのコピー中のみロックされることに注意が必要。
● たとえばユーザ空間にコピーされたデータはコピー元となったバッファのロックとは無関係に操作される。
インターフェース
● getblk バッファを割り当てて取得(←重要)● brelse バッファを自由リストに戻す● bread ブロック読み込み● breada ブロック先読み● bwrite ブロック書き込み
getblk (1)
● ブロックをキーとして呼び出される● 戻り値は当該ブロックに対応するバッファ● まずハッシュを検索● ロックされてなければそれをreturn● されてる場合は解除を待ってやり直し● ハッシュ内に見つからなければ、自由リストから
の取り出しへ
getblk (2)
● 自由リストの先頭を取り出す● 遅延書き込みフラグが立ってなければそれを
return● 立っていたら書き込み依頼してやりなおし
それでは擬似コードをどうぞ
Case1: ハッシュから取り出し
● ハッシュ内にバッファを見つけ、ロックされてなかったケース
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
ブロック番号72を取り出すとする。まず 72 mod 7 ≡ 2 なので m2 リストを探す
ブロック72のバッファが見つかった!
Case1: ハッシュから取り出し
● バッファをロックして● 自由リストから取り除いて● 該当バッファをreturn
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
ロック(状態変数に使用中のフラグをたてる)して
自由リストから取り除く
Case2:自由リストから再利用
● ハッシュ内にバッファが見つからず、自由リストから再利用するケース
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
ブロック番号8を取り出すとする。まず 8 mod 7 ≡ 1 なので m1 リストを探す
リストを一周してもブロック8のバッファは見つからなかった
Case2:自由リストから再利用
● ハッシュに見つからないので、自由リストの先頭を取り出して再利用する
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
自由リストの先頭を取り出す
Case2:自由リストから再利用
● ハッシュに見つからないので、自由リストの先頭を取り出して再利用する
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
自由リストの先頭を取り出す
Case2:自由リストから再利用
● 遅延書き込みフラグが立っていないことを確認(立ってたらCase3)
● ハッシュから取り除く
m1
m6
m0
FL
m2
15
8
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
遅延書き込みフラグなし
6 → 8用のバッファにする
Case2:自由リストから再利用
● ハッシュの新しい位置に挿入● バッファをreturn
m1
m6
m0
FL
m2
15 8
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
8は m1 リストにあるべきなので、ここにつなぐ
Case3:遅延書き込み
● 自由リストから再利用しようとしたが、遅延書き込みフラグが立っていたケース
● ハッシュ内にないので自由リストの先頭を取り出す
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
ブロック番号8を取り出すとする。自由リストの先頭を取り出すところまでは一緒
Case3:遅延書き込み
● とりだした6のバッファには遅延書き込みフラグが立っていた=再利用できない
● ディスクへの書き込みを依頼してハッシュを探すところからやり直し
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
遅延書き込みフラグON=再利用不可。ディスクへ書き込み依頼
Case3:遅延書き込み
● 再び自由リストの先頭から取り出したが、これも遅延書き込みフラグが立っていた
● 見つかるまで繰り返す
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
またハッシュにないので自由リスト先頭から取り出したがまた遅延書き込みフラグ
書き込み中
次はこいつ
Case3:遅延書き込み
● 遅延書き込みフラグがないバッファを見つけたら、Case2で見たように再利用
m1
m6
m0
FL
m2
15
6
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
ハッシュの位置更新など
書き込み中
書き込み中
8
Case3:遅延書き込み
● 書き込みが終わったバッファは自由リストの先頭に戻される(書き込み直後だが、必要とされたのはだいぶ前なので)
m1
m6
m0
FL
m2
15
6
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
8
自由リストの先頭に戻す
Case3:遅延書き込み(ポイント)● ディスクの書き込みは遅いので完了を同期的に
待つより別のバッファを探したほうが速い● それに、スリープしてしまうと目覚めたときに別
のプロセスコンテキストによりバッファがロックされている可能性があり、書き込まれたバッファをそのまま使えない
● → 結局while文をやりなおすしかない
Case4:自由リストが空
● 自由リストから再利用しようとしたが、自由リストにバッファがないケース
● この場合は自由リストにバッファが追加されるまでスリープする
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
ブロック番号8を取り出すとする。自由リストの先頭を取り出そうとするが、空だった。
Case4:自由リストが空
● このケースも目覚めたらwhile文やりなおし● このプロセスコンテキストが目覚める前に、一緒
に目覚めた別のコンテキストが実行可能状態になり、バッファを同じブロックに割り当てる可能性に対応するため
● 言い換えると、目覚めた後ハッシュを確認せずに自由リストを使うと二重割り当てしてしまうから
Case5: ハッシュから取り出し
● ハッシュ内にバッファを見つけたが、ロックされていたケース
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
ブロック番号72を取り出すとする。
ブロック72のバッファが見つかった!でもロックされてる
Case5: ハッシュから取り出し
● 要求ありのフラグをバッファに立てて、アンロックを待ってスリープ
● 目覚めた後はwhile文をやり直し● 理由はCase3,4と同様
m1
m6
m0
FL
m2
15
6
7
44
92
41
14
2
29
13
35
58
20
42
72
… … … …
16
要求あり
インターフェース
● getblk バッファを割り当てて取得● brelse バッファを自由リストに戻す● bread ブロック読み込み● breada ブロック先読み● bwrite ブロック書き込み
brelse (1)
● ロックされたバッファを引数に呼び出される● 戻り値はなし● 自由リストのバッファ追加まちのプロセスを起こ
す● 引数バッファのアンロックまちのプロセスを起こ
す
brelse (2)
● 割り込みを禁止して● 有効なデータが入っていて、古くないバッファは
自由リストの最後に● そうでない(IOエラーや遅延書き込み後)バッファ
は自由リストの先頭につなぐ● 割り込み禁止を解除してバッファをアンロック
インターフェース
● getblk バッファを割り当てて取得● brelse バッファを自由リストに戻す● bread ブロック読み込み● breada ブロック先読み● bwrite ブロック書き込み
bread
● ブロック番号を引数にして、データを読み込んだバッファを返す
● バッファの取得にはgetblk()を使用● バッファに有効なデータが乗っていればそのま
まもどし、乗ってなければディスクから読み込んで戻す
● シーケンシャルに読み込む場合は次のブロックをバッファに載せたい→breada
インターフェース
● getblk バッファを割り当てて取得● brelse バッファを自由リストに戻す● bread ブロック読み込み● breada ブロック先読み● bwrite ブロック書き込み
breada
● ブロック番号を引数にして、データを読み込んだバッファを返す(breadと同じ)
● ただし、引数のブロック番号の次のブロックにも非同期で読み込みをディスクに依頼する
● 詳細は擬似コードで
インターフェース
● getblk バッファを割り当てて取得● brelse バッファを自由リストに戻す● bread ブロック読み込み● breada ブロック先読み● bwrite ブロック書き込み
bwrite
● バッファを引数にして、そのバッファのデータをディスクに書き込む
● 同期書き込みの場合はバッファのアンロックbrelse()まで実施
● 遅延書き込みの場合は「内容が古いフラグ」をたてて、割り込みコンテキストからbrelse()でアンロックされた場合に自由リストの先頭に追加されるようにする
まとめ&感想
● バッファキャッシュは最近アクセスしたものをキャッシュに載せておく(LRU)
● 自由リストの順番が「長く使ってない順」そのもの
● ハッシュと二重リンクリストの性質をうまく使っている
● コンテキストスイッチと割り込み怖い