• 画像は Understanding_the_Linux_Kernel_Third_Editionから引用

12章 仮想ファイルシステム(VFS)

  • 仮想ファイルシステム:異なるシステム(Windows,他のUNIXなど)上のファイルを透過的に扱えるようにする
  • この章は、VFSの目的、構造、実装を述べる
  • 通常ファイル、ディレクトリ、シンボリックリンク
  • 関連 
    • デバイスファイル(13章)
    • Ext2,Ext3ファイルシステム(18章)
    • パイプ(19章)

12.1 仮想ファイルシステム(VFS)の役割

  • アプリケーションとファイルシステムの間にある抽象的なレイヤ

  • VFSが扱うファイルシステム
    • ディスクベースのファイルシステム:ローカルディスクなどのデバイスの記憶領域を管理
    • ネットワークファイルシステム:ネットワーク接続された他のコンピュータに簡単にアクセスできるようにする
    • 特殊ファイルシステム:ローカル、リモートのどちらのディスク領域も管理しないファイルシステム
  • 全てのファイルシステムは、ルートファイルシステムのサブディレクトリとしてマウントされる
  • ファイルの中にファイルシステムをマウントする事もできる(仮想ブロック型デバイス)

12.1.1 共通ファイルモデル

  • 伝統的なUNIXファイルシステムを踏襲
    • ディレクトリが他ディレクトリのリストを持つ通常のファイルとして扱われる、など
  • 他のファイルシステムをVFSで使えるように変換
  • read,writeなどの関数はカーネルにポインタとして登録
    • これから使うファイルシステム用の関数をさすポインタ
  • 共有ファイルモデルは4つ型ののオブジェクトから構成
    • スーパーブロックオブジェクト:マウントされたファイルシステムに関する情報を保存
    • iノードオブジェクト:個々のファイルのついての情報を保存
    • ファイルオブジェクト:オープンされているファイルとプロセスの間のやり取りに関する情報を保存
    • dエントリオブジェクト:ディレクトリエントリと対応するファイルとのリンクついての情報を保存

  • dエントリキャッシュ:使用したdエントリオブジェクトを置くキャッシュ
    • 同じエントリが呼び出されるときに使用

12.1.2 VFSが取り扱うシステムコール

省略

12.2 VFSデータ構造

12.2.1 スーパーブロックオブジェクト

  • super_block構造体
  • システム上の全てのスーパーブロックオブジェクトは1つの双方向循環リストにリンクしている
  • ディスク上のデータを更新する必要があるかどうかのフラグ(s_dirt)を持つ
    • 汚れたスーパーブロックは定期的に書き出される
  • それぞれのファイルシステムは独自のスーパーブロック操作メソッドを持つ

12.2.2 iノードオブジェクト

  • inode構造体
  • 1つのファイルに対して一意のiノードオブジェクトを持つ
  • dirtyフラグを持つ
  • 3つの双方向循環リストのうち1つに必ず入っている
    • 未使用のiノードオブジェクトのリスト
    • 使用中のiノードオブジェクトのリスト
    • 汚れているiノードオブジェクトのリスト
  • ファイルシステムごとにスーパーブロックオブジェクトのメンバを先頭とした双方向循環リストにつながれている
  • inode_hashtableハッシュテーブルにも含まれている
    • iノードオブジェクト検索の高速化のため

12.2.3 ファイルオブジェクト

  • プロセスとプロセスによってオープンされたファイルがどのようにやり取りを行うかを記述
  • file構造体
  • ファイルオブジェクトはディスク上に存在しない
    • 必要なときにスラブキャッシュから割り当てる
  • それぞれが属するファイルシステムのスーパーブロックを基点とするリストにつながれている
  • 現在そのファイルオブジェクトを使用中のプロセス数を数えるカウンタを持つ

12.2.4 dエントリオブジェクト

  • dentry構造体
  • ディスク上に存在しない
  • プロセスが参照するパス名の要素1つにつき1つ存在
  • 各要素を対応するiノードと結びつける
  • 次の4つの状態のいずれかになる
    • 空き:スラブアロケータに利用される
    • 未使用:現在使用されていないが、iノードとの対応は保持している(キャッシュで使用)
    • 使用中
    • 負:dエントリに対応するiノードが存在しない状態

12.2.5 dエントリキャッシュ

  • 一度使用したdエントリオブジェクトをキャッシュしておくことで、エントリ処理を高速化
  • 2つのデータ構造からなる
    • 「使用中」「未使用」「負」いずれかの状態にあるdエントリオブジェクトの集合
    • ハッシュテーブル
  • iノードキャッシュの役割も持つ
  • 未使用のdエントリは最長不使用順リスト(LRUリスト)に置かれる
    • 最も使用されていない時間が長いdエントリをキャッシュから削除
    • 負のdエントリオブジェクトも登録されて優先的に削除される

12.2.6 プロセスとファイル

  • プロセスがファイルシステムとの間で処理するのデータ構造が必要
  • fs_struct構造体:プロセスディスクリプタはfs_struct構造体へのポインタを持つ
  • files_struct構造体:使用中のファイルオブジェクトへのポインタを持つ

12.3 ファイルシステム種別

12.3.1 特殊ファイルシステム

  • システムプログラムや管理者がカーネルのデータ構造を操作する為のシステム
  • 特殊ファイルシステムをマウントするとき、カーネルは擬似ブロック型デバイスを割り当てる
    • 通常のファイルシステムと同じように操作できるようになる

12.3.2 ファイルシステム種別の登録

  • カーネルのコンパイル時に必要なファイルシステムを認識するようにしてある
  • モジュールとして組み込む事もできる
  • VFSは、カーネルに含まれる全てのファイルシステムの種別を把握するため、ファイルシステム種別の登録を行う
    • file_system_typeオブジェクトに登録

12.4 ファイルシステムの操作

  • システムの初期化スクリプトや重要なシステムプログラムを含むルートファイルシステムは起動時にカーネルがマウント
  • その他のファイルシステムはマウント済みのファイルシステムの上にマウントできる
    • マウントする側、された側で子、親の関係をなすツリー構造を作る

12.4.1 名前空間

  • Linuxは全てのプロセスが独自のファイルシステムツリーを持つことができる(名前空間)
  • 大部分のプロセスはルートファイルシステムを基点とする名前空間を持つ
    • clone()システムコールなどで新しい名前空間を作ることができる

12.4.2 ファイルシステムのマウント

  • Linuxは同じファイルシステムを何回もマウントすることができる
    • ファイルシステムが増えるわけではない
  • 同じマウントポイントに複数のマウントを重ねることができる
  • マウント済みのファイルシステムの情報はマウント済みファイルシステムディスクリプタに登録され幾つかのリストに保持されている

12.4.3 一般ファイルシステムのマウント

  1. マウントポイントのパス名を検索(12.5節)
  2. 引数のフラグを見て、処理を決定
  3. ファイルをマウントする場合は do_kern_mount()関数を呼び出す
  4. 名前空間を変更
12.4.3.1 do_kern_mount()関数
  • ファイルシステム種別のフラグを確認し、マウント操作の方法を決定
  1. ファイル種別を識別する
  2. 新しいファイルシステムディスクリプタを確保
  3. スーパーブロックオブジェクトを割り当てる
  4. 構造体のメンバを新しいものに置き換える
12.4.3.2 スーパーブロックオブジェクトの割り当て
省略

12.4.4 ルートファイルシステムのマウント

  • ルートファイルシステムは様々な場所に置くことができる
  • 2段階のマウント処理を行う
第一段階 rootfsファイルシステムのマウント
  • 空のルートディレクトリを提供する特殊ファイルシステムrootfsをマウント
  1. do_kern_mount()でrootfsをマウント
  2. 名前空間オブジェクトを確保し、システム上の全てのプロセスをこの名前空間に設定
  3. プロセス0のルートディレクトリと作業ディレクトリをルートファイルシステムに設定
第二段階 真のルートファイルシステムのマウント
  1. rootfsに/dev/root デバイスファイルを作成
  2. プロセスのカレントディレクトリをrootに変更
  3. ファイルシステムのマウントポイントをrootfsファイルシステムのルートディレクトリ上に移動

12.4.5 ファイルシステムのアンマウント

省略

12.5 パス名の検索

  • パス名を分析し、ファイル名の並びへと分割する操作
  • dエントリキャッシュによって高速化できる
  • UNIXとVFSファイルシステムの特徴を考慮する必要がある
    • ディレクトリのアクセス権の確認
    • ファイルがシンボリックリンクの場合
    • シンボリックリンクが無限ループをなす場合
    • ファイルがマウントポイントである場合
    • パス名検索は、システムコールを発行したプロセスの名前空間内でなければならない

パス検索の流れ
  • path_lookup()関数
  1. カレントプロセスの読み書きロックを取得
  2. パス名が絶対パスか相対パスか判断
  3. /で区切られたパス名をnameidataデータ構造に格納
  4. ロックを解放
  5. link_path_walk()関数を呼び出す

12.5.1 標準的なパス名の検索

  1. パス名から/を全て省く
  2. 先頭からパス名を解決していく
    1. '.'(カレントディレクトリ)、'..'(親ディレクトリ)などを処理する
    2. dエントリキャッシュを検索し、なければディスクを直接検索して、dエントリオブジェクトを求める
  3. パス名の最後が/の場合はディレクトリとして処理
  4. 要素がシンボリックリンクの場合はリンクの処理を続ける

12.5.2 親パス名の検索

省略

12.5.3 シンボリックリンクの検索

  • パス名がシンボリックリンクの場合、カレントディレクトリをそのシンボリックリンクの位置にしてパス名検索を始める
  • シンボリックリンクは5回までしか参照されない
  • パスが40を超えた場合は処理を中断

12.6 VFSシステムコールの実装

12.6.1 open()システムコール

  1. パス名検索を利用してopenするファイルがあるかどうかを判定し、無ければ新たにファイルを作成する
  2. システムコールの各種フラグに従ってファイルのアクセス権限を設定
  3. openしたファイルオブジェクトのアドレスを返す

12.6.2 read()、write()システムコール

  1. openされているファイルオブジェクトのアドレスを確認
  2. ファイルが読み/書きが可能である事を確認し、データを転送する
  3. ファイルオブジェクトを解放

12.6.3 close()システムコール

省略

12.7 ファイルロック

  • ファイルが複数のプロセスからアクセスされないようにする

12.7.1 Linuxのファイルロック

  • 複数のファイルロック方式
    • 勧告ロック:ロックを無視するタイプのプロセスに対して勧告する
    • 強制ロック:確実なロック
    • 共有読み取りロック:あるファイルの読み取りロックを持つことができるプロセスに制限は無い
    • 排他的書き込みロック:書き込みロックを持てるのは1つのプロセスのみ、読み取りロックがかけられた領域は書き込みロックすることはできない
    • 共有モード強制ロック:他のプロセスがオープンすることができないようにする
    • リース:他のプロセスがオープンしようとすると、現在実行中のプロセスが矛盾が起きないようにファイルを更新してロックを解放する
  • ロックはファイルの任意の領域に対してかけることができる
    • flock()システムコール:ファイル全体にロックをかける
    • fcntl()システムコール:ファイルの一部だけにかけることができる
  • お互いのロック状況を知ることはできない
    • デッドロックをさけるため

12.7.2 ファイルロックデータ構造

  • file_lock構造体
  • プロセスが排他ロックを要求し、指定したファイルに共有ロックがかけられていた場合は、プロセスを待ちキューに挿入する
  • 2のリストでロックの状態を管理
    • アクティブなロック
    • ブロック中のロック

12.7.3 FL_FLOCKロック

  • あるファイルオブジェクトに対してプロセスが持つ全てのロックを他のロックに書き換える
  • 読み/書きロックを逆転したい場合などに利用

12.7.4 FL_POSIXロック

  • プロセスとiノードに結びつけられている
    • 片方が消滅するとロックは自動的に解除

メンバーのみ編集できます