On this page

VFS — Virtual File System Layer

Coverage: VFS object model (super_block/inode/dentry/file) → system call path (open/read/write/close) → file_operations → RCU path walk → mount mechanism Kernel version: 2.6 ~ 6.x

Overview

VFS (Virtual File System / Virtual Filesystem Switch) is the cornerstone of Linux's "everything is a file" philosophy. It defines a set of abstract objects and operation interfaces, allowing completely different file systems such as ext4, NFS, and procfs to be uniformly exposed to user space. A read() system call does not need to know whether the underlying storage is a local disk or a network mount—the VFS layer is responsible for routing to the correct file system implementation.

The core design philosophy of VFS is object-oriented C implementation: each VFS object embeds a *_operations function table, and file systems "implement the interface" by filling in these function tables.


The Four Major VFS Objects

super_block:  "What is this file system?"  — One super_block per mount point
inode:        "Who is this file?"          — One per file/directory (stored on disk or in memory)
dentry:       "Who does this path name point to?" — Cache mapping from path name to inode
file:         "Where is this open file?"   — Created for each open(), containing the current offset

super_block

// include/linux/fs.h
struct super_block {
    unsigned long           s_blocksize;
    unsigned char           s_blocksize_bits;
    unsigned long long      s_maxbytes;        // Maximum file size
    struct file_system_type *s_type;            // File system type (ext4, nfs, ...)
    const struct super_operations *s_op;        // Operation function table
    struct dentry           *s_root;            // Root directory dentry
    struct list_head        s_inodes;           // List of all inodes
    struct list_head        s_dentry_lru;       // Unused dentries (LRU)
    void                    *s_fs_info;         // File system private data
};

inode

struct inode {
    umode_t                 i_mode;             // File type and permissions
    kuid_t                  i_uid; i_gid;
    loff_t                  i_size;             // File size
    struct timespec64       i_atime, i_mtime, i_ctime;

    const struct inode_operations *i_op;        // Inode operations
    const struct file_operations  *i_fop;       // Default file operations (template copied at open)
    struct super_block      *i_sb;
    struct address_space    *i_mapping;         // Page cache (core!)

    union {
        struct pipe_inode_info  *i_pipe;        // Pipe
        struct block_device     *i_bdev;        // Block device
        struct cdev             *i_cdev;        // Character device
    };

    unsigned long           i_ino;              // Inode number (unique within the file system)
};

dentry — Path Name Cache

struct dentry {
    struct inode            *d_inode;           // Associated inode (can be NULL: negative)
    struct dentry           *d_parent;          // Parent directory
    struct qstr             d_name;             // Name of this component
    struct list_head        d_child;            // List of child components in parent directory
    struct list_head        d_subdirs;          // Head of the list of subdirectories
    struct hlist_bl_node    d_hash;             // Dcache hash table node
    const struct dentry_operations *d_op;
};

Negative dentry: d_inode == NULL. Indicates "this path name is confirmed to not exist." Commonly seen when a file lookup fails—if every open("/tmp/noexist") required a full traversal, it would waste a significant amount of I/O.

file — Open File Description

struct file {
    struct path             f_path;             // dentry + vfsmount
    const struct file_operations *f_op;
    atomic_long_t           f_count;            // Reference count
    unsigned int            f_flags;            // O_RDONLY, O_NONBLOCK, ...
    fmode_t                 f_mode;             // FMODE_READ, FMODE_WRITE
    loff_t                  f_pos;              // Current read/write position
    struct address_space    *f_mapping;         // Page cache
    void                    *private_data;      // File system private data
};

System Call Path

open(): From Path Name to File

flowchart TD
    SYSCALL["open() System Call"] --> GETNAME["getname()<br/>Copy path name from user space"]

    GETNAME --> GETFD["get_unused_fd_flags()<br/>Allocate fd number"]

    GETFD --> DO_OPEN["do_filp_open()<br/>Core path resolution"]
    DO_OPEN --> PATH["path_openat()"]
    PATH --> WALK["link_path_walk()<br/>Traverse path component by component"]

    WALK --> LOOKUP{"lookup_fast()<br/>Query dcache (RCU walk)"}
    LOOKUP -->|"Hit"| DENTRY["Return dentry<br/>Lock-free, extremely fast ✅"]
    LOOKUP -->|"Miss"| SLOW["lookup_slow()<br/>Call i_op->lookup()<br/>Search from disk/inode blocks<br/>Allocate new dentry → Add to dcache"]

    DENTRY --> LAST{"Last component?"}
    SLOW --> LAST

    LAST -->|"Yes"| DO_LAST["do_last()"]
    LAST -->|"No, continue to next component"| LOOKUP

    DO_LAST --> CASE{"File status?"}
    CASE -->|"Exists"| VFS_OPEN["vfs_open()<br/>→ f_op->open()"]
    CASE -->|"O_CREAT"| VFS_CREATE["vfs_create()<br/>→ i_op->create()"]
    CASE -->|"O_TMPFILE"| TMPFILE["Create anonymous temporary file"]

    VFS_OPEN --> INSTALL["fd_install()<br/>Install file into fdtable"]
    VFS_CREATE --> INSTALL
    TMPFILE --> INSTALL

    INSTALL --> RET["Return 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 — The Performance Secret of VFS

Traditional path resolution (pre-2.6):
  Lock acquisition for each component → High contention → Poor multi-core performance

RCU path walk (2.6.38+):
  1. Traverse dcache under RCU read lock
  2. Only read dentry and inode (no modifications)
  3. If all components are in dcache → Lock-free throughout
  4. If a component is not in dcache → Degenerate to ref-walk (with locking)

Performance: Multi-core system open() throughput increased by several times
Implementation: Two sets of logic in fs/namei.c's link_path_walk() (RCU/REF)

read(): Read from Page Cache

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

vfs_read(file, buf, count, pos)
  ├─ Check permissions (FMODE_READ)
  ├─ Call f_op->read() or f_op->read_iter()
  │   └─ For regular files: generic_file_read_iter()
  │       └─ filemap_read()
  │           ├─ For each page-aligned range:
  │           │   ├─ filemap_get_page()find_get_page()
  │           │   │   ├─ Page cache hit → Return directly (no I/O)
  │           │   │   └─ Page cache miss → page_cache_sync_readahead()
  │           │   │       └─ Read from block layer → Insert page into page cache
  │           │   └─ copy_page_to_iter()copy_to_user()
  │           └─ Update f_pos
  └─ Return number of bytes read

write(): Write to Page Cache + Writeback

vfs_write(file, buf, count, pos)
  └─ f_op->write_iter()generic_file_write_iter()
      └─ __generic_file_write_iter()
          ├─ filemap_write()
          │   ├─ For each page-aligned range:
          │   │   ├─ filemap_get_page() → Find or create page cache page
          │   │   ├─ copy_page_from_iter()copy_from_user()
          │   │   └─ Mark page dirty (SetPageDirty)
          │   └─ Update f_pos
          │
          └─ When does writeback happen?
              Not here! generic_file_write_iter only writes to page cache
              Writeback is handled asynchronously by background flusher threads
              (or triggered synchronously by fsync/fdatasync)

file_operations

// 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() → Release last reference
    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);
};

A file system can fill in only a subset—VFS will provide default implementations for the rest. For example, if there is no read_iter but there is read, VFS will automatically wrap it.


Mount Mechanism

// fs/namespace.c
// mount attaches a file system to a point in the VFS tree:

struct vfsmount {
    struct mount            *mnt_parent;   // Parent of the mount point
    struct dentry           *mnt_root;     // Root directory of the mounted file system
    struct super_block      *mnt_sb;       // File system
    struct list_head        mnt_mounts;    // Child mounts mounted on this mount
    struct list_head        mnt_child;     // Child node of the parent mount
};

// mount /dev/sda1 /mnt:
//   1. Read superblock of /dev/sda1 → Create super_block
//   2. Create vfsmount → mnt_root = super_block->s_root
//   3. Attach to /mnt → dentry of /mnt becomes the mountpoint
//   4. Path traversal encounters mountpoint → Switch to vfsmount->mnt_root to continue

// Namespace:
//   Each process belongs to a mount namespace
//   CLONE_NEWNS → New mount namespace (basis for containers)

Debugging

# VFS Statistics
cat /proc/sys/fs/dentry-state
# nr_dentry, nr_unused, age_limit

# Current Mounts
cat /proc/mounts
findmnt  # Tree view display

# File System Types
cat /proc/filesystems

# Trace open/read/write
strace -e trace=open,read,write cat /etc/hostname

References and Further Reading

  • Kernel Documentation: 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/)
  • Source Files:
    • fs/namei.c — Path walk, 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 — Mount mechanism
    • fs/dcache.c — Dentry cache

Keywords: VFS, super_block, inode, dentry, file, file_operations, RCU path walk, dcache, negative dentry, mount namespace