■ディレクトリエントリキャッシュ linuxではdentryという構造体に、ファイルパス名の情報を格納しメモリ上にキャッシングしている。 パス検索の高速化が目的である。 一度ディレクトリやファイルにアクセスすると、一つのdentryが確保され、キャッシュされる。 各dentryは、次の図のようにディスク上のパスのイメージを再現している。 パス名検索時に、dentry上でヒットしなかった場合に限り、ディスクからパス情報を読みだすこととなる。 一度読み出したパス情報は dentry上に載るため、二度目以降のパス検索は高速化される。
mount dentry(root) d_parent d_subdirs d_child dentry dentry d_parent d_vfsmnt d_subdirs d_child d_parent d_subdirs d_child vfs_mount mount dentry(root) mnt_mountpoint mnt_clash mnt_root d_parent d_subdirs d_child dentry dentry d_parent d_subdirs d_child d_parent d_subdirs d_child
“very_long_name_file” dentryは下図のようにiノードと相互リンクしており、 dentryから簡単にiノードを求めることができる。 一つのファイルが複数の名前を持っている(ハードリンク)場合は、以下のように一つのiノードに対し、 二つのdentryが割り当てられている。ファイル名が短い場合(16文字以内)は dentry内にファイル名を 格納するが、それ以上の長いファイル名の場合はファイル名を格納する領域を別途用意している。 dentry dentry dentry d_inode d_alias d_name d_iname d_inode d_alias d_name d_iname d_inode d_alias d_name d_iname “login” “short “very_long_name_file” inode inode i_dentry i_dentry dentryも、iノードやバッファと同様に、以下のような hashにキャッシュされている。hash値はファイル名と 親dentry のアドレスとを組み合わせて作っているため、ほどほどに分散した値が求まっているものと思 われる。 現時点で誰からも参照されていないdentryはdentry_unusedにリンクされており、解放の対象とされる。
dentryの参照カウントが以下の場合に1加えられる。これ以外の場合は参照数は0となり、全て dentry_unusedに登録される。ただし、登録の並びは再利用の可能性の低そうな順になる。 ディレクトリの参照カウントは0となる可能性は低くパス検索でディレクトリがミスヒットとなる可能性は低い ・まさにそのdentry経由でファイルをアクセスしているとき ・そのdentryのd_subdirsに子dentryがリンクされているとき dentry_hashtable dentry dentry d_hash d_lru d_name name, len, hash d_hash d_lru d_name name, len, hash dentry dentry dentry d_hash d_lru d_name name, len, hash d_hash d_lru d_name name, len, hash d_hash d_lru d_name name, len, hash dentry d_hash d_lru d_name name, len, hash dentry_unused
■ディレクトリエントリキャッシュ - ディレクトリエントリキャッシュの検索と登録 ■ディレクトリエントリキャッシュ - ディレクトリエントリキャッシュの検索と登録 あるディレクトリ以下にあるファイルのdentryをもとめるために、d_lookup関数が用意されている。 d_lookup関数は、ディレクトリのdentryと、これから検索するファイル名を入力とする。 d_lookup関数は、上記ディレクトリエントリキャッシュ内から一致する dentryを見つけ出し、 参照数を1増やす(dget関数)。 キャッシュ上に無かった場合はNULLを返却する。 新しいdentryをキャッシュ登録するには、d_rehash関数を使用する。 逆にキャッシュの登録を抹消するにはd_drop関数を使用する。
■ディレクトリエントリキャッシュ - dentryの参照要求と参照の終了 dentryを利用する前には必ず、dget関数を呼び出し dentryの参照数を1増やしておかねばならない。 dget関数はdentryの参照数を1増やす関数である。 また参照が終わったら、dput関数によってdentryの参照数を1減らさなければならない。 このとき参照数が0になった場合、そのdentryの状態により、処理が分かれる。 またdputによりこのdentryが解放されてしまう場合は、親ディレクトリのdentryに対するdputも呼び出し ている。 (子ファイルからの参照を一つ減らすため) dput(dentry) if (参照数 > 1) { 参照数を1減らす return } if (dentry_unusedリストにリンクされていたら) { 一旦、 dentry_unusedリストからはずす if (既にキャッシュに登録されていない) { dentryのリンクを全て切る。 対応するinodeの解放(dentry_iput関数) parent = dentry->d_parent; dentryのメモリ領域解放(d_free関数) 親ディレクトリのdentryに対してdput関数を呼びだす。 dentry_unusedリストに繋ぐ
■ディレクトリエントリキャッシュ - dentryのメモリ領域の拡張と解放 dentryは必要に応じてどんどん動的に確保していく。必ずiノードとリンクされるため、dentryの総数は iノード数に準じた値となる。 dentry領域の確保はd_alloc関数にて行われる。ファイル名が17byte以上の場合は、dentryの他に ファイル名格納用のメモリを別途確保する。確保したメモリはdentryとして初期化(他のdentryとのリンク を張り、参照数を1とする) 必要が無くなったdentry領域の解放はd_free関数にて行われる。ここでは純粋にdentryを空きメモリに 返却する。この関数は上記dput関数と、次に述べるprune_one_dentry関数から呼び出される。 d_add関数は、dentryを利用可能な状態にする。 dentryのキャッシュへの登録(d_rehash関数)と dentryとiノードとのリンクを行う。 d_delete関数は、dentryを利用不可な状態にする。この関数はファイル削除時に呼び出されるのだが、 iノードの解放のみを行い、ディレクトリキャッシュからはずしていない。 存在しないファイルのdentryがキャッシュにヒットするため、エレガントな作りとは言えない。 パス検索でも無駄なエラーチェックを行う結果となっている。 参照数が2以上の場合はopenしているファイルの削除を意味している。そのうちcloseの延長で dput関数が呼び出され、 dentry領域の解放が行われる。 d_delete(dentry) if(参照数が1) { iノードの解放(dentry_iput関数) return; } キャッシュから落す。(d_drop関数)
■ディレクトリエントリキャッシュ - dentryのメモリ領域の強制解放 上記dentry操作関数群だけだと、基本的にどんどんdentryの領域が増えて行く。増えすぎたエントリは、 prune_dcache関数により不必要そうなものから順番に解放される。 prune_dcache関数は以下の処理から呼び出される。 ・空きメモリ領域が不足した時、try_to_free_pages関数から shrink_dcache_memory関数経由で呼び出 される。 iノード領域とdentryは必ずペアで 管理されているため、iノード領域を解放するには、 dentryの 解放が必要不可欠である。 ・ディレクトリのdentry削除時に、ゴミの子ファイルの dentryが残っていた場合、select_parent関数で ゴミdentryをdentry_unusedリストの最後に繋ぎ、 prune_dcache関数を呼び出す。(shrink_dcache_parent()) ファイルの削除時にdentryをきちんと整理していない為、 面倒な処理が必要となっている。 prune_dcache(解放するdentryの希望数) while(dentry_unusedリストに継っている) { dentry_unusedリストの最後のdentryをはずす if(誰からも参照されてないなら) { dentryの解放(prune_one_dentry関数) } if(解放希望数を満たした) return; prune_one_dentry(dentry) dentryのリンクを全てきる iノードを解放(dentry_iput関数) dentryのメモリ領域解放(d_free関数) 親ディレクトリのdentryの参照の終了(dput関数)
■ディレクトリエントリキャッシュ - その他の主なdentry操作関数 dentry_iput() dentryとリンクされているiノードとのリンクを切り、iノードを解放する(iput関数) d_instantiate() dentryとiノードのリンクを行う d_invalidate() dentryを無効化する。参照数が1以上のdentryは無効化できない。 d_move() ファイルの移動(rename)に伴いdentryのハッシュを繋ぎかえる。 partial_name_hash() キャッシュ用のhash値の計算。ファイル名だけでなく、 親dentryのアドレスも利用 問題点など ディレクトリエントリキャッシュの問題点は、物理デバイス上のディレクトリからファイルが削除されても、 同期してdentryが削除されていない点にある。 このため、随所に奇妙なアルゴリズムをいれなければならない結果となっている。
■ディレクトリエントリキャッシュ - キャッシュの相互関係 ■ディレクトリエントリキャッシュ - キャッシュの相互関係 linuxはバッファキャッシュとページキャッシュという大きなデータキャッシュを持つ。 通常のファイルデータのアクセスはページキャッシュを通して行われる。 ローカルディスク用のファイルシステムでの場合、ディレクトリエントリキャッシュ, iノードキャッシュは、 バッファキャッシュの上に位置している。 ディスク上のiノード情報をアクセスするにはまずバッファ キャッシュ上に読み込み、そのうち必要なデータをiノードキャッシュ上にコピーし、アクセスする。 filesystem meta-data access filesystem data access write read super block bitmap group directory entry (name) cache inode cache buffer cache page cache I/O I/O 参考 v2.2では、read処理はページキャッシュであったが write処理はバッファキャッシュ経由であったため、 二つのデータキャッシュを持っているが、整合性がとる必要があり、バッファキャッシュとページキャッシュの 更新処理時にキャッシュ間のデータコピーを行うことでその問題を回避していた。