JF Linux Kernel 2.2 Documentation: /usr/src/linux/Documentation/filesystems/vfs.txt

filesystems/vfs.txt

Virtual File System の概要 [プレインテキスト版]


        Virtual File System(VFS) の概要
        著者:Richard Gooch <rgooch@atnf.csiro.au>
        日本語訳:早川仁 <cz8cb01@linux.or.jp>
                  野本浩一 <hng@ps.ksky.ne.jp>
            校正:三浦広志さん <miura@blue.gr.jp>
                  山下義之さん <dica@eurus.dti.ne.jp>
                  武井伸光さん <takei@webmasters.gr.jp>
                  佐野武俊さん <kgh12351@nifty.ne.jp>
                  水原さん <mizuhara@acm.org>
                  森本淳さん <morimoto@xantia.citroen.org>
                  高城正平さん <takavoid@palette.plala.or.jp>
                  後藤正徳さん <gotom@debian.or.jp>
          
                  23-APR-1999 [訳注;関数の説明は 5-JUL-1999 版を元に
                              しています。]

この文書でのお約束                                           <section>
==================

この文書では各セクションのタイトルの右端に、文字列 "<section>" があり
ます。各サブセクションの右端には "<subsection>" があります。これらの文
字列は文書内を検索しやすくするためにあります。

この文書の原本は
http://www.atnf.csiro.au/~rgooch/linux/docs/vfs.txt から
オンラインで入手できます。

VFS とは?                                                   <section>
==========

Virtual File System (もしくは Virtual Filesystem Switch) とはカーネル
内のソフトウェアレイヤで、ユーザ空間でのプログラムに、ファイルシステム
のインターフェースを提供します。またカーネル内で抽象的概念を提供します。
これにより、異なった実装のファイルシステムが共存できます。

動作の簡単な説明                                             <section>
================

このセクションでは詳細に入る前に、どのように動作するのかを簡単に説明し
ます。まずユーザ空間のプログラムが、ファイルをオープンして操作した際に
何が起きるのかを説明し、次にどのようにファイルシステムがサポートされて
いるか、どのようにマウントされるかを他の視点から見ます。

ファイルのオープン                                        <subsection>
------------------

VFS は open(2), stat(2), chmod(2) や同様のシステムコールを実装していま
す。引数のパス名は VFS がディレクトリエントリキャッシュ (dentry キャッ
シュ、あるいは "dcache") を検索するために使われます。これは、あるパス
名 (ファイル名) を特定の dentry に変換するための、非常に速い検索の仕組
みを提供しています。

個々の dentry は通常 inode へのポインタを持っています。inode はディス
ク上に存在し、通常のファイル (データを書きこんだときに作成される普通の
ファイルのこと)、ディレクトリ、FIFO などを表現しています。dentry は
RAM 上に存在し、ディスクには保存されません - dentry はパフォーマンスを
上げるためだけに存在しています。inode はディスク上に存在していますが、
必要に応じて RAM に読み込まれます。変更があれば後でディスクへ書き戻さ
れます。RAM 上に存在する inode は VFS inode で、dentry のポインタが指
しているのはこれです。一つの inode を複数の dentry が指すこともあり得
ます (ハードリンクを想像してください)。

dcache はファイル空間全体の概観となることを意図しています。Linus とち
がって、われわれ凡人の大半は、ファイル空間全体を網羅する充分な数の
dentry を RAM に合わせることができません。ですから dcache は少々足りな
いかもしれません。パス名を dentry に解決するためには VFS は途中で
dentry を作成する必要があるかもしれません。inode は作成された後にロー
ドされます。これは inode を検索することにより行なわれます。

(通常はディスクから読み込まれる)inode を検索するには、VFS は親ディレク
トリの inode を指定して lookup() 関数を呼びだす必要があります。この関数は inode
が存在するファイルシステムの実装に応じたものが用意されています。これに
ついては後で詳しく述べます。

VFS が、必要な dentry(つまり inode) を取得してしまえば、ファイルを
open(2) したり、inode の情報を stat(2) で取り出すといった退屈なことは
すべてできるようになります。stat(2) の動作は非常に単純です - VFS は
dentry を取得すると inode 情報を見て、いくつかのデータをユーザ空間に渡
します。

ファイルをオープンするには他の操作が必要となります - file 構造体 (これ
はファイルディスクリプタのカーネル側の実装です) の確保です。新しく確保
された file 構造体は dentry およびファイル操作関係の関数を指すポインタ
で初期化されます。これらは inode の情報から取得されます。そして次に
open() 関数が呼ばれますから、ファイルシステムに応じた実装で処理を行な
うことができます。ですからこれは VFS によって行なわれる、もう一つの切
替動作ということがわかるでしょう。

file 構造体は各プロセスごとのファイルディスクリプタテーブルへ格納され
ます。

(ユーザ空間のプログラムが) ファイルの読み書きやクローズ (およびその他
の関連する VFS 操作) を実行する際には、ユーザ空間に存在するファイルディ
スクリプタを経由して (カーネル側の) 適切な file 構造体を取得し、次にこ
れから実行する操作に必要となる file 構造体の関数を呼び出します。

ファイルがオープンされている間はずっと dentry も "open"(使用中) になっ
ています。つまりこれは VFS inode も使用中ということを意味しています。

全ての VFS システムコール (つまり open(2), stat(2), read(2), write(2),
chmod(2) など) はプロセスコンテキストから呼びだされます。これらのシス
テムコールは、カーネルロックをまったく行なっていない状態で呼びだされる
ということに注意してください。別のプロセッサ上のプロセスが、ファイルシ
ステムやドライバのコードの同じ部分を同時に実行する可能性があるというこ
とです。ですから共有されるリソースへアクセスする時は適切なロックを行な
い保護する必要があります。

ファイルシステムの登録とマウント                          <subsection>
--------------------------------

カーネルで新しいファイルシステムをサポートしたい場合、必要なことは
register_filesystem() を呼び出すだけです。そのファイルシステムの実装を
定義している構造体 (struct file_system_type) を渡すと、カーネル内部で
持っているサポートされるファイルシステムの表に追加されます。

% cat /proc/filesystems

を実行すれば、現在システムで利用できるファイルシステムを表示できます。

ファイル空間上のディレクトリにブロックデバイスをマウントさせようとする
と、VFS はそのファイルシステム固有の適切な関数を呼び出します。マウント
ポイントの dentry は、新しくマウントされたファイルシステムのルートの
inode を指すように更新されます。

さて、詳細を見ていきましょう。

struct file_system_type                                      <section>
=======================

ここではファイルシステムの説明を行ないます。カーネル 2.1.99 以降では、
次のように定義されています。

struct file_system_type {
    const char *name;
    int fs_flags;
    struct super_block *(*read_super) (struct super_block *, void *, int);
    struct file_system_type * next;
};

  name: "ext2", "iso9660", "msdos" といった、ファイルシステムの名前で
    す。

  fs_flags: 色々なフラグ (例えば FS_REQUIRES_DEV, FS_NO_DCACHE など)

  read_super: このファイルシステムの新しいインスタンスがマウントされた
    時に呼び出される関数

  next: VFS が内部で使用 - NULL に初期化してください。


read_super() 関数には次の引数があります。

  struct super_block *sb: スーパーブロックの構造体です。VFS は一部分し
    か初期化しませんから、残りを read_super() 関数で初期化する必要があ
    ります。

  void *data: 任意のマウントオプションで、通常は ASCII 文字列です。

  int silent: エラー時に報告をするかどうか
  
read_super() 関数はスーパーブロックで指定されているブロックデバイスに、
この関数がサポートしているファイルシステムがあるかどうかを調べなければ
なりません。read_super() 関数は処理に成功するとスーパーブロックへのポ
インタを、失敗すると NULL を返します。

read_super() 関数が設定する、superblock 構造体の最も興味深いメンバは
"s_op" です。これはファイルシステムの操作を実装している構造体
"struct super_operations" へのポインタです。

struct super_operations                                      <section>
=======================

ここでは VFS がファイルシステムのスーパーブロックをどのようにして扱っ
ているかを説明しています。カーネル 2.1.99 以降では次のように定義されて
います。

struct super_operations {
    void (*read_inode) (struct inode *);
    void (*write_inode) (struct inode *);
    void (*put_inode) (struct inode *);
    void (*delete_inode) (struct inode *);
    int (*notify_change) (struct dentry *, struct iattr *);
    void (*put_super) (struct super_block *);
    void (*write_super) (struct super_block *);
    int (*statfs) (struct super_block *, struct statfs *, int);
    int (*remount_fs) (struct super_block *, int *, char *);
    void (*clear_inode) (struct inode *);
};

特に記述している場合を除いて、全ての関数は何のロックも無い状態で呼び出
されます。つまりほとんどの関数は安全に処理待ち (block) できるというこ
とです。全ての関数はプロセスコンテキストのみから呼び出されます (つまり
割り込みハンドラやボトムハーフからは呼び出されません)。

  read_inode: この関数はマウントされたファイルシステムから特定の inode
    を読み込むために呼び出されます。"struct inode" の "i_ino" メンバは
    VFS によって読み込む inode を指すように初期化されます。その他のメ
    ンバにはこの関数で代入します。

  write_inode: この関数は VFS が inode をディスクに書き出す必要がある
    時に呼び出されます。
    
  put_inode: VFS inode が inode キャッシュから取り除かれる時に呼び出さ
    れます。この関数の実装は任意 (optional) です。 

  delete_inode: VFS が inode を削除する時に呼び出されます。

  notify_change: VFS inode の属性が変わった時に呼び出されます。NULL の
    時には VFS は write_inode() 関数を呼び出します。これはカーネルロッ
    ク状態で呼び出されます。

  put_super: VFS がスーパーブロックを解放する時 (つまりアンマウント時)
    に呼び出されます。これはスーパーブロックをロックした状態で呼び出さ
    れます。

  write_super: VFS スーパーブロックをディスクに書き出す必要がある時に
    呼び出されます。この関数の実装は任意 (optional) です。 

  statfs: VFS がファイルシステムの統計情報を必要とする時に呼び出されま
    す。これはカーネルロック状態で呼び出されます。

  remount_fs: ファイルシステムが再マウントされる時に呼び出されます。こ
    れはカーネルロック状態で呼び出されます。

  clear_inode: VFS が inode をクリアする時に呼び出されます。この関数の
    実装は任意 (optional) です。

read_inode() 関数では "i_op" フィールドを設定しなければなりません。こ
れは各 inode 上で実行される関数を指す "struct inode_operations" へのポ
インタです。

struct inode_operations                                      <section>
=======================

ここでは VFS がファイルシステム上で inode を扱う方法について説明してい
ます。カーネル 2.1.99 以降では次のように定義されています。

struct inode_operations {
    struct file_operations * default_file_ops;
    int (*create) (struct inode *,struct dentry *,int);
    int (*lookup) (struct inode *,struct dentry *);
    int (*link) (struct dentry *,struct inode *,struct dentry *);
    int (*unlink) (struct inode *,struct dentry *);
    int (*symlink) (struct inode *,struct dentry *,const char *);
    int (*mkdir) (struct inode *,struct dentry *,int);
    int (*rmdir) (struct inode *,struct dentry *);
    int (*mknod) (struct inode *,struct dentry *,int,int);
    int (*rename) (struct inode *, struct dentry *,
            struct inode *, struct dentry *);
    int (*readlink) (struct dentry *, char *,int);
    struct dentry * (*follow_link) (struct dentry *, struct dentry *);
    int (*readpage) (struct file *, struct page *);
    int (*writepage) (struct file *, struct page *);
    int (*bmap) (struct inode *,int);
    void (*truncate) (struct inode *);
    int (*permission) (struct inode *, int);
    int (*smap) (struct inode *,int);
    int (*updatepage) (struct file *, struct page *, const char *,
                unsigned long, unsigned int, int);
    int (*revalidate) (struct dentry *);
};

これらの関数も特に記述している場合を除いて、ロックなしで呼び出されます。

  default_file_ops: これはファイルのオープンと、オープンしたファイルを
    扱う方法を定義している "struct file_operations" へのポインタです。

  create: open(2) や creat(2) システムコールによって呼び出されます。通
    常のファイルをサポートする時のみ必要となります。取得する dentry に
    は inode は含まれていないでしょう (つまり negative dentry のはずで
    す)。おそらくここで、dentry と新しく作成された inode を指定して
    d_instantiate() を呼び出すはずです。

  lookup: VFS が親ディレクトリの inode を検索する必要がある時に呼び出
    されます。探す名前は dentry の中から見つかります。この関数は見つけ
    た inode を dentry に登録するために d_add() を呼び出す必要がありま
    す。また inode 構造体の "i_count" フィールドを増やさなければなりま
    せん。その名前の inode が存在しない時には NULL の inode を dentry
    へ入れる必要があります (これは negative dentry と呼ばれます)。この
    ルーチンからエラーコードを返す場合、本当にエラーが発生した時だけに
    する必要があります。さもなければ creat(2), mknod(2), mkdir(2) など
    のシステムコールでの inode 作成に失敗してしまいます。dentry の関数
    に処理を詰め込みたければ dentry の "d_dop" フィールドを初期化する
    必要があります - これは "dentry_operations" 構造体へのポインタです。
    この関数はディレクトリ inode のセマフォを設定した状態で呼び出され
    ます。

  link: link(2) システムコールによって呼び出されます。ハードリンクをサ
    ポートしたい場合のみ必要です。create() 関数と同じく、おそらく
    d_instantiate() を呼び出す必要があるはずです。

  unlink: unlink(2) システムコールによって呼び出されます。inode の削除
    をサポートしたい場合のみ必要です。

  symlink: symlink(2) システムコールによって呼び出されます。シンボリッ
    クリンクをサポートしたい場合のみ必要です。create() 関数と同じく、
    おそらく d_instantiate() を呼び出す必要があるはずです。

  mkdir: mkdir(2) システムコールによって呼び出されます。サブディレクト
    リの作成をサポートしたい場合のみ必要です。create() 関数と同じく、
    おそらく d_instantiate() を呼び出す必要があるはずです。
    
  rmdir: rmdir(2) システムコールによって呼び出されます。サブディレクト
    リの削除をサポートしたい場合のみ必要です。

  mknod: mknod(2) システムコールによって、デバイスの (キャラクタもしく
    はブロック) inode や名前付きパイプ (FIFO)、ソケットなどを作成する
    時に呼び出されます。これらの種類の inode の作成をサポートしたい場
    合のみ必要です。create() 関数と同じく、おそらく d_instantiate() を
    呼び出す必要があるはずです。

  readlink: readlink(2) システムコールによって呼び出されます。シンボリッ
    クリンクの読み込みをサポートしたい場合のみ必要です。

  follow_link: VFS により、シンボリックリンクをたどってそれが指してい
    る inode を取得する時に呼び出されます。シンボリックリンクをサポー
    トしたい場合のみ必要です。

struct file_operations                                       <section>
======================

ここでは VFS がオープンされたファイルを扱う方法について説明しています。
カーネル 2.1.99 以降では次のように定義されています。

struct file_operations {
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *);
    int (*fasync) (struct file *, int);
    int (*check_media_change) (kdev_t dev);
    int (*revalidate) (kdev_t dev);
    int (*lock) (struct file *, int, struct file_lock *);
};

これらの関数も特に記述している場合を除いて、ロックなしで呼び出されます。

  llseek: VFS がファイルの位置を示すインデックスを移動する必要がある時
    に呼び出されます。

  read: read(2) や関連するシステムコールから呼び出されます。

  write: write(2) や関連するシステムコールから呼び出されます。

  readdir: VFS がディレクトリの内容を読み込む必要がある時に呼び出され
    ます。

  poll: VFS により、ファイルにアクティビティが存在するかどうか [訳注:
    ファイルの読み/書きの操作を待っている状態にあるかどうか] をあるプ
    ロセスからチェックしたい場合、また (任意ですが) アクティビティが発
    生するまでプロセスをスリープさせたい場合に select(2) および
    poll(2) システムコールを利用することによって VFS から呼び出されま
    す。

  ioctl: ioctl(2) システムコールから呼び出されます。

  mmap: mmap(2) システムコールから呼び出されます。

  open: VFS により、inode をオープンする必要がある時に呼び出されます。
    VFS はファイルをオープンする場合 "struct file" を作成し、ファイル
    操作関係の構造体 "f_op" を inode 構造体の "default_file_ops" フィー
    ルドで初期化します。そして新たに確保された file 構造体の open 関数
    を呼び出します。open 関数は実際には "struct inode_operations" の中
    にあるんじゃないかと思った方、おそらく正解です。おそらくこれはファ
    イルシステムの実装を単純にするためにこうなっているのだと思います。
    open() 関数は file 構造体の "private_data" メンバで device 構造体
    を指すようにする場合、初期化するのに絶好のタイミングです。

  release: オープンしているファイルへの最後の参照がクローズされた時に
    呼び出されます。

  fsync: fsync(2) システムコールから呼び出されます。

  fasync: ファイルへの非同期 (非ブロッキング) モードが有効になっている
    時に、fcntl(2) システムコールから呼び出されます。

file_operations は inode の存在するファイルシステムに依存した実装になっ
ています。(キャラクタもしくはブロック型) デバイスノードをオープンする
場合、多くのファイルシステムでは必要なデバイスドライバの情報を見つけて
くれる、VFS の特別なサポートルーチンを呼び出します。これらのルーチンは
ファイルシステムの file 操作関数をデバイスドライバの物で置き換えて、オー
プンするファイル用の新しい open() 関数を呼び出します。これが、ファイル
システムのデバイスファイルをオープンする時に、最終的にデバイスドライバ
の open() 関数が呼ばれる仕組みです。devfs(Device FileSystem) にはデバ
イスノードからデバイスドライバへの、より直接的な仕組みがあります (これ
は非公式パッチです)。

struct dentry_operations                                     <section>
========================

ここではファイルシステムが通常の dentry 操作を置き換える方法について説
明しています。dentry や dcache は VFS や個々のファイルシステムの実装の
領域です。ここではデバイスドライバには何の関係もありません。これらの関
数は任意あるいは VFS のデフォルトの動作のため、NULL に設定されているか
もしれません。カーネル 2.1.99 以降では、次のように定義されています。

struct dentry_operations {
    int (*d_revalidate)(struct dentry *);
    int (*d_hash) (struct dentry *, struct qstr *);
    int (*d_compare) (struct dentry *, struct qstr *, struct qstr *);
    void (*d_delete)(struct dentry *);
    void (*d_release)(struct dentry *);
    void (*d_iput)(struct dentry *, struct inode *);
};

  d_revalidate: VFS が dentry を再評価する必要がある時に呼び出されます。
    これは名前を検索するルーチンが dentry を dcache 中に見つけた時には
    いつでも呼びだされます。これは多くのファイルシステムで NULL になっ
    ています。なぜならそのようなファイルシステムでは dcache の dentry
    はどれも (一つも) 無効にならないからです。

  d_hash: VFS がハッシュテーブルに dentry を追加する時に呼び出されます。

  d_compare: dentry を比較する場合に呼び出されます。

  d_delete: dentry への最後の参照が削除された時に呼び出されます。つま
    りこれは、その denty を誰も使用していませんが、dcache の中でまだ有
    効だということを意味しています。

  d_release: dentry が実際に解放される時に呼び出されます。

  d_iput: dentry が自身の inode を解放する時に呼び出されます (解放され
    る直前に実行されます)。これがデフォルトの NULL の場合、VFS は
    iput() を呼び出します。この関数を定義する場合、自前で iput() を呼
    び出す必要があります。

各 dentry は子 dentry のハッシュリストと同様に、親 dentry へのポインタ
を持ちます。子 dentry は基本的にはディレクトリ内のファイルのようなもの
です。

ファイルシステムに dentry の操作を許可する、いくつかの関数が定義されて
います。

  dget: 既に存在している dentry への新しいハンドルをオープンします (こ
    れは単に使用カウント数を増やすだけです)。

  dput: dentry へのハンドルをクローズします (使用カウント数を減らしま
    す)。使用カウント数が 0 になった時は "d_delete" 関数が呼ばれ、
    dentry がまだ親のハッシュリストに存在する場合 dentry は unused
    list に置かれます。dentry を unused list に置くと、システムが RAM
    を必要とする場合、dentry の unused list を経て、それらが解放されま
    す。すでに、dentry がハッシュ上に無く、使用カウントが 0 になった場
    合、"d_delete" 関数が呼び出された後、dentry が解放されます。

  d_drop: dentry を親ハッシュリストから取り除きます。使用カウント数が
    0 になった場合、次に呼ばれる dput() が dentry を解放します。

  d_delete: dentry を削除します。dentry を他に参照しているものが無くなっ
    た時には dentry は negative dentry になります (d_iput() 関数が呼び
    出されます)。参照しているものが他にある時には d_drop() が代わりに
    呼び出されます。

  d_add: dentry を親ハッシュリストに追加し、d_instantiate() を呼び出し
    ます。

  d_instantiate: dentry を inode のエイリアスハッシュリストに追加し、
    "d_inode" メンバを更新します。inode 構造体の "i_count" メンバを設
    定する/増やす必要があるでしょう。inode ポインタが NULL の場合、
    dentry は "negative dentry" と呼ばれます。この関数は通常、既に存在
    する negative dentry に対して inode が作成される時に呼び出されます。

Linux カーネル 2.2 付属文書一覧へ戻る