---
title: VFS Advanced
url: https://doc.liz6.com/en/linux-kernel/03-file-system/04-vfs-advanced
locale: en
area: linux-kernel
tags:
- linux-kernel
- file-system
date: 2026-06-30
modified: 2026-07-16
description: 'Coverage: page cache interaction with VFS → inode state machine → file locking (POSIX/OFD) → fsync/fdatasync guarantees → sendfile/splice/vmsplice zero-copy Kernel versions: 2.6 ~ 6.x'
---

# VFS Advanced

> Coverage: page cache interaction with VFS → inode state machine → file locking (POSIX/OFD) → fsync/fdatasync guarantees → sendfile/splice/vmsplice zero-copy
> Kernel versions: 2.6 ~ 6.x

## Page Cache and VFS Interaction

### Relationship between inode and address_space

```c
// inode->i_mapping points to the page cache for this file
// This is an address_space structure that manages all cached pages for this file in memory

// Most inode->i_mapping point to the embedded inode->i_data:
struct inode {
    struct address_space *i_mapping;  // usually = &inode->i_data
    struct address_space i_data;      // embedded (block device filesystems)
};

// For raw reads/writes on block devices:
struct block_device {
    struct inode bd_inode;            // block devices are also treated as inodes
    // bd_inode.i_mapping is the block device's own page cache
    // interacts with the filesystem via buffer_head
};
```

### inode State Machine

```c
// include/linux/fs.h
#define I_NEW           0   // inode just allocated, not yet read from disk
#define I_DIRTY_SYNC    1   // metadata dirty, needs sync (e.g., i_size)
#define I_DIRTY_DATASYNC 2  // data dirty (only fdatasync cares)
#define I_DIRTY_PAGES   3   // i_mapping contains dirty pages
#define I_WILL_FREE     4   // about to be freed
#define I_FREEING       5   // currently being freed

// inode lifecycle:
// alloc_inode()  → I_NEW
// insert_inode_locked() → added to inode hash
// unlock_new_inode() → clear I_NEW, wake up waiters
// ... active period ...
// iput() → i_count == 0 → evict() → I_FREEING → destroy_inode()
```

---

## File Locking

### POSIX Lock (fcntl)

```c
// POSIX advisory/mandatory locks
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 of the process holding the lock (filled for GETLK)
};

fcntl(fd, F_SETLK, &lock);   // non-blocking
fcntl(fd, F_SETLKW, &lock);  // blocking wait
fcntl(fd, F_GETLK, &lock);   // query

// Implementation: fs/locks.c
// Locks are attached to inode->i_flctx (file_lock_context)
// Conflict detection: posix_locks_conflict() → traverse conflict chain
// Process closes fd → automatically releases all POSIX locks (regardless of fork relationship)
```

### OFD Lock (Open File Description, 3.15+)

```c
// Problem with POSIX locks: different fds of the same process cannot hold locks independently
// OFD lock solves this: locks are attached to the "open file description" (file), not the process
fcntl(fd, F_OFD_SETLK, &lock);

// fork scenario:
//   POSIX: child process inherits parent's locks (because same PID)
//   OFD:   child process does not inherit (different file, not necessarily)
```

### FLOCK (BSD Lock)

```c
flock(fd, LOCK_SH);  // shared
flock(fd, LOCK_EX);  // exclusive
flock(fd, LOCK_UN);  // unlock

// Differences from POSIX locks:
//   FLOCK is attached to the file (not inode, not process)
//   Multiple flock calls on the same file are automatically associated
//   fork inherits flock (because file is shared)
//   Advisory only (no mandatory locks)
```

---

## fsync / fdatasync: Durability Guarantees

```c
// fs/sync.c
SYSCALL_DEFINE1(fsync, unsigned int, fd)
  → do_fsync(fd, 0) → vfs_fsync(file, 0)
    ├─ file_write_and_wait_range()  // 1. Write back dirty page cache
    │   └─ __filemap_fdatawrite_range() → do_writepages()
    │       → for each dirty folio → a_ops->writepage()
    │   └─ __filemap_fdatawait_range()  // wait for writeback completion
    │
    └─ vfs_fsync_range()
        └─ f_op->fsync()  // 2. Filesystem: write journal / force flush
            → ext4: jbd2 commit transaction + blkdev_issue_flush
            → XFS: force log flush + flush
            → NFS: COMMIT operation

// fdatasync (FDATASYNC): only flushes data part (does not flush i_atime/i_mtime)
//   → vfs_fsync_range(file, start, end, 1)
//   → filesystem can skip timestamp synchronization
```

---

## Zero-Copy: sendfile / splice / vmsplice

```c
// sendfile(): file → socket, bypasses user space
sendfile(out_fd, in_fd, &offset, count)
  → do_sendfile()
    └─ splice_file_to_pipe() → pipe buffer ← file content
        └─ splice_pipe_to_socket() → pipe buffer → socket

// splice(): pipe transfer between two fds (kernel space)
splice(in_fd, NULL, pipefd[1], NULL, count, 0);  // fd → pipe
splice(pipefd[0], NULL, out_fd, NULL, count, 0);  // pipe → fd

// vmsplice(): user space memory → pipe (used with splice)
vmsplice(pipefd[1], iov, nr_segs, SPLICE_F_GIFT);
  // user memory pages are "gifted" to the kernel (no longer user-space)
  // → pipe references these pages → splice to socket → zero-copy to NIC
```

### DMA Zero-Copy Path

<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="Zero-copy path comparison: decreasing CPU copy counts from traditional read/write, sendfile, to sendfile+DMA scatter-gather">
  <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">Zero-Copy Path Comparison: CPU Copy Counts Decrease from 2 to 0</text>

  <text x="40" y="54" font-size="12" font-weight="700" fill="#475569">Path A · Traditional Read/Write</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">Disk</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">User Buffer</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">socket buffer</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 context switches · 2 CPU copies</text>

  <text x="40" y="146" font-size="12" font-weight="700" fill="#3730a3">Path 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">Disk</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">socket buffer</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 context switches · 1 CPU copy</text>

  <text x="40" y="238" font-size="12" font-weight="700" fill="#0f766e">Path C · sendfile + DMA scatter-gather (Recommended)</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">Disk</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 Direct Fetch)</text>
  <text x="90" y="302" font-size="11" font-weight="700" fill="#0f766e">2 context switches · 0 CPU copies!</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">Copy counts drop steadily: Traditional 2 → sendfile 1 → sendfile+DMA 0; Context switches 4 → 2 → 2.</text>
  <text x="76" y="353" font-size="12.5" fill="#115e59">Zero-copy relies on NIC support for TX checksum offload + SG — almost all modern NICs satisfy this.</text>
</svg>

---

## Debugging

```bash
# inode cache statistics
cat /proc/sys/fs/inode-nr      # nr_inodes, nr_free_inodes
cat /proc/sys/fs/inode-state   # detailed inode usage status

# View process file locks
cat /proc/locks                 # all active POSIX/OFD/FLOCK locks
lslocks                         # better human-readable format

# Trace fsync behavior
strace -e trace=fsync,fdatasync -p <pid>
```

---

## References and Extensions

- **Kernel Documentation**: `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/)
- **Source Files**:
  - `fs/locks.c` — POSIX/OFD/FLOCK implementation
  - `fs/sync.c` — fsync/fdatasync
  - `fs/splice.c` — splice/sendfile/vmsplice
  - `fs/inode.c` — inode lifecycle

---

*Keywords: VFS, inode state, POSIX lock, OFD lock, FLOCK, fsync, fdatasync, sendfile, splice, zero-copy*
