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
;
inode
;
dentry — Path Name Cache
;
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
;
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
→ →
├─ Check
├─ Call f_op-> or f_op->
│ └─ For regular files:
│ └─
│ ├─ For each page-aligned range:
│ │ ├─ →
│ │ │ ├─ Page cache hit → Return
│ │ │ └─ Page cache miss →
│ │ │ └─ Read from block layer → Insert page into page cache
│ │ └─ →
│ └─ Update f_pos
└─ Return number of bytes read
write(): Write to Page Cache + Writeback
└─ f_op-> →
└─
├─
│ ├─ For each page-aligned range:
│ │ ├─ → Find or create page cache page
│ │ ├─ →
│ │ └─ Mark page
│ └─ 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
file_operations
// include/linux/fs.h
;
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:
;
// 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
# nr_dentry, nr_unused, age_limit
# Current Mounts
# File System Types
# Trace open/read/write
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_writeinclude/linux/fs.h— super_block, inode, dentry, file, file_operationsfs/namespace.c— Mount mechanismfs/dcache.c— Dentry cache
Keywords: VFS, super_block, inode, dentry, file, file_operations, RCU path walk, dcache, negative dentry, mount namespace