---
title: VFS 応用
url: https://doc.liz6.com/ja/linux-kernel/03-file-system/04-vfs-advanced
locale: ja
area: linux-kernel
tags:
- linux-kernel
- file-system
date: 2026-06-30
modified: 2026-07-16
description: 'カバー範囲: page cache と VFS の相互作用 → inode 状態マシン → ファイルロック (POSIX/OFD) → fsync/fdatasync の保証 → sendfile/splice/vmsplice によるゼロコピー。カーネルバージョン: 2.6 ~ 6.x'
---

# VFS 応用

> カバー範囲: page cache と VFS の相互作用 → inode 状態マシン → ファイルロック (POSIX/OFD) → fsync/fdatasync の保証 → sendfile/splice/vmsplice によるゼロコピー
> カーネルバージョン: 2.6 ~ 6.x

## Page Cache と VFS の相互作用

### inode と address_space の関係

```c
// inode->i_mapping は、そのファイルの page cache を指す
// これは address_space 構造体であり、メモリ内のそのファイルの全キャッシュページを管理する

// ほとんどの場合、inode->i_mapping は内蔵の inode->i_data を指す:
struct inode {
    struct address_space *i_mapping;  // 通常は &inode->i_data
    struct address_space i_data;      // 内蔵 (ブロックデバイスファイルシステム用)
};

// ブロックデバイスの裸の読み書きの場合:
struct block_device {
    struct inode bd_inode;            // ブロックデバイスも inode として扱う
    // bd_inode.i_mapping はブロックデバイス自身の page cache
    // ファイルシステムとのやり取りは buffer_head を介して行われる
};
```

### inode 状態マシン

```c
// include/linux/fs.h
#define I_NEW           0   // inode が新規割り当てされ、ディスクからまだ読み込まれていない
#define I_DIRTY_SYNC    1   // メタデータがダーティ、同期が必要 (例: i_size)
#define I_DIRTY_DATASYNC 2  // データがダーティ (fdatasync のみが関心を持つ)
#define I_DIRTY_PAGES   3   // i_mapping にダーティページが存在する
#define I_WILL_FREE     4   // 解放即将
#define I_FREEING       5   // 解放処理中

// inode のライフサイクル:
// alloc_inode()  → I_NEW
// insert_inode_locked() → inode ハッシュに追加
// unlock_new_inode() → I_NEW をクリアし、待機中のプロセスをウェイクアップ
// ... アクティブ期間 ...
// iput() → i_count == 0 → evict() → I_FREEING → destroy_inode()
```

---

## ファイルロック

### POSIX ロック (fcntl)

```c
// POSIX 勧告的/強制ロック
struct flock {
    short l_type;       // F_RDLCK / F_WRLCK / F_UNLCK
    short l_whence;     // SEEK_SET/SEEK_CUR/SEEK_END
    off_t l_start;
    off_t l_len;
    pid_t l_pid;        // ロックを保持しているプロセスの PID (GETLK 時に設定)
};

fcntl(fd, F_SETLK, &lock);   // 非ブロッキング
fcntl(fd, F_SETLKW, &lock);  // ブロッキング待機
fcntl(fd, F_GETLK, &lock);   // 照会

// 実装: fs/locks.c
// ロックは inode->i_flctx (file_lock_context) にアタッチされる
// 競合検出: posix_locks_conflict() → conflict チェーンを走査
// プロセスが fd をクローズすると、フォーク関係に関係なく、すべての POSIX ロックが自動的に解放される
```

### OFD ロック (Open File Description, 3.15 以降)

```c
// POSIX ロックの問題点: 同一プロセス内の異なる fd は、それぞれロックを保持できない
// OFD ロックの解決策: ロックは「プロセス」ではなく「オープンファイル記述子 (file)」にアタッチされる
fcntl(fd, F_OFD_SETLK, &lock);

// fork シナリオ:
//   POSIX: 子プロセスは親プロセスのロックを継承する (同一 PID であるため)
//   OFD:   子プロセスはロックを継承しない (異なる file であるため、必ずしも継承されない)
```

### FLOCK (BSD ロック)

```c
flock(fd, LOCK_SH);  // 共有ロック
flock(fd, LOCK_EX);  // 排他ロック
flock(fd, LOCK_UN);  // ロック解除

// POSIX ロックとの違い:
//   FLOCK は file にアタッチされる (inode でもプロセスでもない)
//   同一 file に対する複数の flock 呼び出しは自動的に結合される
//   fork 時に flock が継承される (file が共有されるため)
//   勧告的ロックのみ (強制ロックは存在しない)
```

---

## fsync / fdatasync: 永続性の保証

```c
// fs/sync.c
SYSCALL_DEFINE1(fsync, unsigned int, fd)
  → do_fsync(fd, 0) → vfs_fsync(file, 0)
    ├─ file_write_and_wait_range()  // 1. ダーティな page cache の書き戻し
    │   └─ __filemap_fdatawrite_range() → do_writepages()
    │       → 各ダーティな folio に対して a_ops->writepage() を呼び出し
    │   └─ __filemap_fdatawait_range()  // 書き戻しの完了を待機
    │
    └─ vfs_fsync_range()
        └─ f_op->fsync()  // 2. ファイルシステム: ジャナルの書き込み / 強制フラッシュ
            → ext4: jbd2 トランザクションのコミット + blkdev_issue_flush
            → XFS: 強制ログのディスクへの書き出し + flush
            → NFS: COMMIT 操作

// fdatasync (FDATASYNC): データ部分のみをフラッシュ (i_atime/i_mtime は対象外)
//   → vfs_fsync_range(file, start, end, 1)
//   → ファイルシステムはタイムスタンプの同期をスキップできる
```

---

## ゼロコピー: sendfile / splice / vmsplice

```c
// sendfile(): ファイルからソケットへ、ユーザー空間を経由しない
sendfile(out_fd, in_fd, &offset, count)
  → do_sendfile()
    └─ splice_file_to_pipe() → パイプバッファ ← ファイル内容
        └─ splice_pipe_to_socket() → パイプバッファ → ソケット

// splice(): 2つの fd の間でパイプ経由の転送 (カーネル空間内)
splice(in_fd, NULL, pipefd[1], NULL, count, 0);  // fd → パイプ
splice(pipefd[0], NULL, out_fd, NULL, count, 0);  // パイプ → fd

// vmsplice(): ユーザー空間メモリからパイプへ (splice と組み合わせて使用)
vmsplice(pipefd[1], iov, nr_segs, SPLICE_F_GIFT);
  // ユーザーメモリのページが「カーネルに譲渡」される (もはやユーザー空間のものではない)
  // → パイプがこれらのページを参照 → splice でソケットへ転送 → ゼロコピーで NIC へ送信
```

### DMA ゼロコピーの経路

<svg viewBox="0 0 720 380" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="ゼロコピー経路の比較: 従来の読み書き、sendfile、sendfile+DMA scatter-gather で CPU コピー回数が減少する">
  <defs><marker id="zcah" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="380" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="18" font-weight="700" fill="#1f2933">ゼロコピー経路の比較: CPU コピー回数が 2 回から 0 回に減少</text>

  <text x="40" y="54" font-size="12" font-weight="700" fill="#475569">経路 A · 従来の読み書き</text>
  <rect x="90" y="68" width="54" height="32" rx="6" fill="#e2e8f0"/>
  <text x="117" y="88" text-anchor="middle" font-size="11" fill="#334155">ディスク</text>
  <line x1="144" y1="84" x2="160" y2="84" stroke="#475569" stroke-width="1.6" marker-end="url(#zcah)"/>
  <rect x="160" y="68" width="90" height="32" rx="6" fill="#e2e8f0"/>
  <text x="205" y="88" text-anchor="middle" font-size="11" fill="#334155">page cache</text>
  <line x1="250" y1="84" x2="266" y2="84" stroke="#475569" stroke-width="1.6" marker-end="url(#zcah)"/>
  <rect x="266" y="68" width="94" height="32" rx="6" fill="#e2e8f0"/>
  <text x="313" y="88" text-anchor="middle" font-size="11" fill="#334155">ユーザーバッファ</text>
  <line x1="360" y1="84" x2="376" y2="84" stroke="#475569" stroke-width="1.6" marker-end="url(#zcah)"/>
  <rect x="376" y="68" width="104" height="32" rx="6" fill="#e2e8f0"/>
  <text x="428" y="88" text-anchor="middle" font-size="11" fill="#334155">ソケットバッファ</text>
  <line x1="480" y1="84" x2="496" y2="84" stroke="#475569" stroke-width="1.6" marker-end="url(#zcah)"/>
  <rect x="496" y="68" width="54" height="32" rx="6" fill="#e2e8f0"/>
  <text x="523" y="88" text-anchor="middle" font-size="11" fill="#334155">NIC</text>
  <text x="90" y="118" font-size="11" fill="#64748b">コンテキストスイッチ 4 回 · CPU コピー 2 回</text>

  <text x="40" y="146" font-size="12" font-weight="700" fill="#3730a3">経路 B · sendfile</text>
  <rect x="90" y="160" width="54" height="32" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="117" y="180" text-anchor="middle" font-size="11" fill="#3730a3">ディスク</text>
  <line x1="144" y1="176" x2="160" y2="176" stroke="#475569" stroke-width="1.6" marker-end="url(#zcah)"/>
  <rect x="160" y="160" width="90" height="32" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="205" y="180" text-anchor="middle" font-size="11" fill="#3730a3">page cache</text>
  <line x1="250" y1="176" x2="266" y2="176" stroke="#475569" stroke-width="1.6" marker-end="url(#zcah)"/>
  <rect x="266" y="160" width="104" height="32" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="318" y="180" text-anchor="middle" font-size="11" fill="#3730a3">ソケットバッファ</text>
  <line x1="370" y1="176" x2="386" y2="176" stroke="#475569" stroke-width="1.6" marker-end="url(#zcah)"/>
  <rect x="386" y="160" width="54" height="32" rx="6" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="413" y="180" text-anchor="middle" font-size="11" fill="#3730a3">NIC</text>
  <text x="90" y="210" font-size="11" fill="#4f46e5">コンテキストスイッチ 2 回 · CPU コピー 1 回</text>

  <text x="40" y="238" font-size="12" font-weight="700" fill="#0f766e">経路 C · sendfile + DMA scatter-gather (推奨)</text>
  <rect x="90" y="252" width="54" height="32" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="117" y="272" text-anchor="middle" font-size="11" fill="#0f766e">ディスク</text>
  <line x1="144" y1="268" x2="160" y2="268" stroke="#475569" stroke-width="1.6" marker-end="url(#zcah)"/>
  <rect x="160" y="252" width="90" height="32" rx="6" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="205" y="272" text-anchor="middle" font-size="11" fill="#0f766e">page cache</text>
  <line x1="250" y1="268" x2="266" y2="268" stroke="#475569" stroke-width="1.6" marker-end="url(#zcah)"/>
  <rect x="266" y="252" width="160" height="32" rx="6" fill="#0d9488"/>
  <text x="346" y="272" text-anchor="middle" font-size="11" font-weight="700" fill="#ffffff">NIC (DMA が直接取得)</text>
  <text x="90" y="302" font-size="11" font-weight="700" fill="#0f766e">コンテキストスイッチ 2 回 · CPU コピー 0 回!</text>

  <rect x="60" y="318" width="600" height="42" rx="8" fill="#f0fdfa" stroke="#99f6e4"/>
  <text x="76" y="336" font-size="12.5" fill="#115e59">コピー回数は減少し続ける: 従来 2 回 → sendfile 1 回 → sendfile+DMA 0 回; コンテキストスイッチは 4 → 2 → 2。</text>
  <text x="76" y="353" font-size="12.5" fill="#115e59">0 コピーには、NIC が TX チェックサムオフロードと SG (Scatter-Gather) をサポートしている必要がある。ほぼすべての現代的な NIC がこれを満たしている。</text>
</svg>

---

## デバッグ

```bash
# inode キャッシュの統計
cat /proc/sys/fs/inode-nr      # nr_inodes, nr_free_inodes
cat /proc/sys/fs/inode-state   # 詳細な inode 使用状況

# プロセスのファイルロックの表示
cat /proc/locks                 # 現在アクティブなすべての POSIX/OFD/FLOCK
lslocks                         # より人間 readable な形式

# fsync の動作を追跡
strace -e trace=fsync,fdatasync -p <pid>
```

---

## 参考と拡張

- **カーネルドキュメント**: `Documentation/filesystems/locks.rst`, `Documentation/filesystems/vfs.rst`
- **LWN**: 
  - "Better file locking with OFD locks" (lwn.net/Articles/586904/)
  - "Splice and sendfile" (lwn.net/Articles/178199/)
- **ソースファイル**:
  - `fs/locks.c` — POSIX/OFD/FLOCK の実装
  - `fs/sync.c` — fsync/fdatasync
  - `fs/splice.c` — splice/sendfile/vmsplice
  - `fs/inode.c` — inode のライフサイクル

---

*キーワード: VFS, inode state, POSIX lock, OFD lock, FLOCK, fsync, fdatasync, sendfile, splice, zero-copy*
