---
title: VFS — 仮想ファイルシステム層
url: https://doc.liz6.com/ja/linux-kernel/03-file-system/01-VFS
locale: ja
area: linux-kernel
tags:
- linux-kernel
- file-system
date: 2026-06-30
modified: 2026-07-16
description: 'カバー範囲: VFS オブジェクトモデル (super_block/inode/dentry/file) → システムコールパス (open/read/write/close) → file_operations → RCUパスウォーク → マウントメカニズム。カーネルバージョン: 2.6 ~ 6.x'
---

# VFS — 仮想ファイルシステム層

> カバー範囲: VFS オブジェクトモデル (super_block/inode/dentry/file) → システムコールパス (open/read/write/close) → file_operations → RCUパスウォーク → マウントメカニズム
> カーネルバージョン: 2.6 ~ 6.x

## 概要

VFS (Virtual File System / Virtual Filesystem Switch) は、Linuxにおける「すべてはファイルである」という理念の基盤です。VFSは抽象化されたオブジェクトと操作インターフェースのセットを定義しており、ext4、NFS、procfsなど全く異なるファイルシステムをユーザー空間に対して一貫して公開することを可能にします。`read()` システムコールを実行する際、それがローカルディスク上にあるのかネットワークマウントされているのかを意識する必要はありません。VFS層が正しいファイルシステム実装へとルーティングを行います。

VFSの核心的な設計哲学は**オブジェクト指向のC言語実装**です。各VFSオブジェクト内部には `*_operations` 関数テーブルが埋め込まれており、ファイルシステムはこの関数テーブルを埋めることで「インターフェースを実装」します。

---

## VFSの4大オブジェクト

```
super_block:  「このファイルシステムは何なのか？」  — マウントポイントごとに1つのsuper_blockが存在
inode:        「このファイルは何か？」              — ファイル/ディレクトリごとに1つ（ディスクまたはメモリ上に存在）
dentry:       「このパス名は誰を指しているのか？」  — パス名からinodeへのキャッシュマッピング
file:         「この開かれたファイルはどこにあるのか？」 — open() ごとに1つ作成され、現在のオフセットを含む
```

### super_block

```c
// include/linux/fs.h
struct super_block {
    unsigned long           s_blocksize;
    unsigned char           s_blocksize_bits;
    unsigned long long      s_maxbytes;        // ファイルの最大サイズ
    struct file_system_type *s_type;            // ファイルシステムタイプ (ext4, nfs, ...)
    const struct super_operations *s_op;        // 操作関数テーブル
    struct dentry           *s_root;            // ルートディレクトリのdentry
    struct list_head        s_inodes;           // 全inodeのリスト
    struct list_head        s_dentry_lru;       // 未使用のdentry (LRU)
    void                    *s_fs_info;         // ファイルシステム固有のデータ
};
```

### inode

```c
struct inode {
    umode_t                 i_mode;             // ファイルタイプと権限
    kuid_t                  i_uid; i_gid;
    loff_t                  i_size;             // ファイルサイズ
    struct timespec64       i_atime, i_mtime, i_ctime;

    const struct inode_operations *i_op;        // inode操作
    const struct file_operations  *i_fop;       // デフォルトのファイル操作 (open時にコピーされるテンプレート)
    struct super_block      *i_sb;
    struct address_space    *i_mapping;         // page cache (重要!)

    union {
        struct pipe_inode_info  *i_pipe;        // パイプ
        struct block_device     *i_bdev;        // ブロックデバイス
        struct cdev             *i_cdev;        // キャラクタデバイス
    };

    unsigned long           i_ino;              // inode番号 (ファイルシステム内で一意)
};
```

### dentry — パス名キャッシュ

```c
struct dentry {
    struct inode            *d_inode;           // 関連付けられたinode (NULLの場合あり: ネガティブ)
    struct dentry           *d_parent;          // 親ディレクトリ
    struct qstr             d_name;             // このコンポーネントの名前
    struct list_head        d_child;            // 親ディレクトリ内の子ディレクトリリスト
    struct list_head        d_subdirs;          // 子ディレクトリリストのヘッダ
    struct hlist_bl_node    d_hash;             // dcacheハッシュテーブル
    const struct dentry_operations *d_op;
};
```

**ネガティブdentry**: `d_inode == NULL`。これは「このパス名は存在しないことが確認済み」であることを意味します。見つからないファイルを検索した際などに生成されます。`open("/tmp/noexist")` のたびに再探索を行わなければならなければ、膨大なIOの無駄になります。

### file — 開かれたファイルの記述

```c
struct file {
    struct path             f_path;             // dentry + vfsmount
    const struct file_operations *f_op;
    atomic_long_t           f_count;            // リファレンスカウント
    unsigned int            f_flags;            // O_RDONLY, O_NONBLOCK, ...
    fmode_t                 f_mode;             // FMODE_READ, FMODE_WRITE
    loff_t                  f_pos;              // 現在の読み書き位置
    struct address_space    *f_mapping;         // page cache
    void                    *private_data;      // ファイルシステム固有のデータ
};
```

---

## システムコールパス

### open(): パス名からfileへ

```mermaid
flowchart TD
    SYSCALL["open() システムコール"] --> GETNAME["getname()<br/>ユーザー空間からパス名をコピー"]

    GETNAME --> GETFD["get_unused_fd_flags()<br/>fd番号を割り当て"]

    GETFD --> DO_OPEN["do_filp_open()<br/>核心的なパス解析"]
    DO_OPEN --> PATH["path_openat()"]
    PATH --> WALK["link_path_walk()<br/>パスをコンポーネントごとに走査"]

    WALK --> LOOKUP{"lookup_fast()<br/>dcacheを参照 (RCU walk)"}
    LOOKUP -->|"ヒット"| DENTRY["dentryを返す<br/>ロックなし、極めて高速 ✅"]
    LOOKUP -->|"ミス"| SLOW["lookup_slow()<br/>i_op->lookup()を呼び出し<br/>ディスク/inodeブロックから探索<br/>新しいdentryを割り当て → dcacheに追加"]

    DENTRY --> LAST{"最終コンポーネントか？"}
    SLOW --> LAST

    LAST -->|"はい"| DO_LAST["do_last()"]
    LAST -->|"いいえ、次のコンポーネントへ"| LOOKUP

    DO_LAST --> CASE{"ファイルの状態は？"}
    CASE -->|"既に存在"| VFS_OPEN["vfs_open()<br/>→ f_op->open()"]
    CASE -->|"O_CREAT"| VFS_CREATE["vfs_create()<br/>→ i_op->create()"]
    CASE -->|"O_TMPFILE"| TMPFILE["無名一時ファイルを作成"]

    VFS_OPEN --> INSTALL["fd_install()<br/>fileをfdtableにインストール"]
    VFS_CREATE --> INSTALL
    TMPFILE --> INSTALL

    INSTALL --> RET["fdを返す ✅"]

    classDef sys call fill:#e3f2fd,stroke:#1565c0
    classDef step fill:#f3e5f5,stroke:#7b1fa2
    classDef decision fill:#fff3e0,stroke:#ef6c00
    classDef done fill:#e8f5e9,stroke:#2e7d32
    class SYSCALL syscall
    class GETNAME,GETFD,DO_OPEN,PATH,WALK,DENTRY,SLOW,VFS_OPEN,VFS_CREATE,TMPFILE,INSTALL step
    class LOOKUP,LAST,CASE decision
    class RET done
```

### RCU Path Walk — VFSのパフォーマンスの秘密

```
従来のパス解析 (2.6 より前):
  各コンポーネントごとにロックを取得 → 競合が高くなる → マルチコアでのパフォーマンスが劣る

RCU path walk (2.6.38以降):
  1. RCU読取りロックの下でdcacheを走査
  2. dentryとinodeの読み取りのみが必要（書き込みは不要）
  3. すべてのコンポーネントがdcache内にある場合 → 全体でロック不要
  4. dcacheにないコンポーネントに出会った場合 → ref-walk（ロック付き）にフォールバック

パフォーマンス: マルチコアシステムにおいてopen()のスループットが数倍向上
実装: fs/namei.c の link_path_walk() 内に2つのロジック（RCU/REF）が存在
```

### read(): page cacheからの読み込み

```c
// fs/read_write.c
SYSCALL_DEFINE3(read, ...) → ksys_read() → vfs_read()

vfs_read(file, buf, count, pos)
  ├─ 権限チェック (FMODE_READ)
  ├─ f_op->read() または f_op->read_iter() を呼び出し
  │   └─ 通常ファイルの場合: generic_file_read_iter()
  │       └─ filemap_read()
  │           ├─ 各ページアラインされた範囲に対して:
  │           │   ├─ filemap_get_page() → find_get_page()
  │           │   │   ├─ page cacheヒット → 直接返す (IOなし)
  │           │   │   └─ page cacheミス → page_cache_sync_readahead()
  │           │   │       └─ ブロック層から読み込み → ページをpage cacheに挿入
  │           │   └─ copy_page_to_iter() → copy_to_user()
  │           └─ f_pos を更新
  └─ 読み込んだバイト数を返す
```

### write(): page cacheへの書き込み + バックグラウンド書き戻し

```c
vfs_write(file, buf, count, pos)
  └─ f_op->write_iter() → generic_file_write_iter()
      └─ __generic_file_write_iter()
          ├─ filemap_write()
          │   ├─ 各ページアラインされた範囲に対して:
          │   │   ├─ filemap_get_page() → page cacheページを検索または作成
          │   │   ├─ copy_page_from_iter() → copy_from_user()
          │   │   └─ ページをダーティとしてマーク (SetPageDirty)
          │   └─ f_pos を更新
          │
          └─ 書き戻しはいつ行われるか？
              ここではありません！ generic_file_write_iter は単にpage cacheに書き込むだけです
              実際のwritebackはバックグラウンドのflusherスレッドによって非同期で行われます
              (または fsync/fdatasync によって同期的にトリガーされます)
```

---

## file_operations

```c
// include/linux/fs.h
struct file_operations {
    struct module *owner;
    loff_t (*llseek)(struct file *, loff_t, int);
    ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter)(struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter)(struct kiocb *, struct iov_iter *);
    int (*mmap)(struct file *, struct vm_area_struct *);
    unsigned long (*mmap_supported_flags);
    int (*open)(struct inode *, struct file *);
    int (*release)(struct inode *, struct file *);  // close() → 最後の参照が解放される
    int (*fsync)(struct file *, loff_t, loff_t, int datasync);
    int (*fasync)(int, struct file *, int);
    __poll_t (*poll)(struct file *, struct poll_table_struct *);
    long (*unlocked_ioctl)(struct file *, unsigned int, unsigned long);
    long (*compat_ioctl)(struct file *, unsigned int, unsigned long);
};
```

ファイルシステムは一部のみを埋め込むことも可能です。VFSがデフォルトの実装で補完します。例えば `read_iter` はなく `read` がある場合、VFSが自動的にラップします。

---

## マウントメカニズム

```c
// fs/namespace.c
// mount は、ファイルシステムをVFSツリー上の一点に付着させます:

struct vfsmount {
    struct mount            *mnt_parent;   // マウントポイントの親
    struct dentry           *mnt_root;     // マウントされたルートディレクトリ
    struct super_block      *mnt_sb;       // ファイルシステム
    struct list_head        mnt_mounts;    // このマウント上にマウントされた子マウント
    struct list_head        mnt_child;     // 親マウントの子ノード
};

// mount /dev/sda1 /mnt の場合:
//   1. /dev/sda1 のスーパーブロックを読み取り → super_block を作成
//   2. vfsmount を作成 → mnt_root = super_block->s_root
//   3. /mnt にマウント → /mnt のdentry がマウントポイントとなる
//   4. パス走査中にマウントポイントに到達 → vfsmount->mnt_root に切り替えて継続

// namespace:
//   各プロセスはマウントネームスペースに属する
//   CLONE_NEWNS → 新しいマウントネームスペース（コンテナの基盤）
```

---

## デバッグ

```bash
# VFS統計
cat /proc/sys/fs/dentry-state
# nr_dentry, nr_unused, age_limit

# 現在のマウント
cat /proc/mounts
findmnt  # ツリー形式で表示

# ファイルシステムタイプ
cat /proc/filesystems

# open/read/write のトレース
strace -e trace=open,read,write cat /etc/hostname
```

---

## 参考と拡張

- **カーネルドキュメント**: `Documentation/filesystems/vfs.rst`, `Documentation/filesystems/path-lookup.rst`
- **LWN**:
  - "Pathname lookup in Linux" (lwn.net/Articles/649115/)
  - "RCU-walk: faster pathname lookup" (lwn.net/Articles/649294/)
- **ソースコードファイル**:
  - `fs/namei.c` — パスウォーク、open()
  - `fs/open.c` — do_sys_open()
  - `fs/read_write.c` — vfs_read/vfs_write
  - `include/linux/fs.h` — super_block, inode, dentry, file, file_operations
  - `fs/namespace.c` — マウントメカニズム
  - `fs/dcache.c` — dentryキャッシュ

---

*キーワード: VFS, super_block, inode, dentry, file, file_operations, RCU path walk, dcache, negative dentry, mount namespace*
