Chapter 31: Virtual File System
- What the Virtual File System (VFS) layer is
- How VFS abstracts different filesystem types behind a common interface
- Key VFS structures: superblock, inode, dentry, file
- How system calls flow through VFS to the specific filesystem
- Mount points and the mount table
- Our VFS implementation
31.1 What is VFS?
The Virtual File System (VFS) is a kernel abstraction layer that provides a uniform interface to different filesystem types. User-space programs use the same system calls (open, read, write) regardless of whether the file is on an ext2 partition, a FAT32 SD card, or a tmpfs in memory. VFS translates these generic operations into filesystem-specific implementations.
31.2 VFS Data Structures
VFS defines four core object types:
| Object | Description |
|---|---|
| super_block | Represents a mounted filesystem (device, operations, root dentry) |
| inode | Represents a file or directory (metadata, operations, data blocks) |
| dentry | Directory entry (name, parent, inode, caching) |
| file | An open file (inode, position, mode, operations) |
/* VFS super_block */
struct super_block {
dev_t device; /* Device this is mounted from */
unsigned long blocksize;
struct dentry *s_root; /* Root dentry of this FS */
struct super_operations *s_op; /* FS-specific operations */
void *fs_data; /* Private data (e.g., sfs_superblock) */
struct list_head s_mounts; /* List of mount points */
};
/* VFS inode */
struct inode {
int i_ino; /* Inode number */
umode_t i_mode; /* File type + permissions */
uid_t i_uid;
gid_t i_gid;
loff_t i_size;
struct inode_operations *i_op; /* Inode operations (lookup, mkdir, etc.) */
struct file_operations *i_fop; /* Default file operations */
struct super_block *i_sb; /* Backing superblock */
void *i_private; /* FS-specific data */
};
/* VFS dentry */
struct dentry {
char *d_name; /* File name */
struct dentry *d_parent; /* Parent directory */
struct inode *d_inode; /* Inode for this name */
struct list_head d_subdirs; /* Children (for directories) */
struct qstr d_hash; /* Hash for dcache lookup */
};
/* VFS file (per open file descriptor) */
struct file {
struct dentry *f_dentry; /* Dentry for this file */
struct file_operations *f_op; /* File operations for this type */
loff_t f_pos; /* Current read/write position */
int f_flags; /* Open flags (O_RDONLY, etc.) */
void *private_data; /* FS-specific data (e.g., sfs_file) */
};
31.3 VFS Operations
Each VFS object has a set of function pointers that the specific filesystem fills in:
/* Filesystem must implement these */
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *inode);
int (*read_inode)(struct inode *inode);
int (*write_inode)(struct inode *inode);
int (*sync_fs)(struct super_block *sb);
};
struct inode_operations {
struct dentry *(*lookup)(struct inode *dir, struct dentry *dentry);
int (*create)(struct inode *dir, struct dentry *dentry, umode_t mode);
int (*link)(struct dentry *old_dentry, struct inode *dir, struct dentry *dentry);
int (*unlink)(struct inode *dir, struct dentry *dentry);
int (*mkdir)(struct inode *dir, struct dentry *dentry, umode_t mode);
int (*rmdir)(struct inode *dir, struct dentry *dentry);
};
struct file_operations {
ssize_t (*read)(struct file *file, void *buf, size_t count);
ssize_t (*write)(struct file *file, const void *buf, size_t count);
int (*mmap)(struct file *file, uint64_t va);
int (*ioctl)(struct file *file, int cmd, void *arg);
};
31.4 Path Resolution
When a program calls open("/home/user/file.txt", O_RDONLY), VFS resolves the path by walking from the root dentry:
/* Path resolution: walk a path name to its dentry */
struct dentry *path_walk(struct dentry *start, const char *path) {
if (path[0] == '/') {
start = current_process->fs_root; /* Start from root */
path++;
}
struct dentry *current = start;
char component[256];
const char *p = path;
while (*p) {
/* Skip slashes */
while (*p == '/') p++;
if (!*p) break;
/* Extract next component */
int len = 0;
while (*p && *p != '/') component[len++] = *p++;
component[len] = '\0';
if (len == 0) continue;
if (len == 1 && component[0] == '.') continue;
if (len == 2 && component[0] == '.' && component[1] == '.') {
current = current->d_parent; /* .. = parent */
continue;
}
/* Look up the component in the current directory */
struct dentry *next = current->d_inode->i_op->lookup(current->d_inode, component);
if (!next) return NULL; /* Not found */
current = next;
}
return current;
}
31.5 Mount Points
A mount point attaches a filesystem to a directory. When a filesystem is mounted at /mnt/usb, accessing /mnt/usb/file.txt transparently accesses the root of the mounted filesystem.
/* Mount table entry */
struct mount {
struct dentry *mnt_mountpoint; /* Directory where mounted */
struct dentry *mnt_root; /* Root of mounted FS */
struct super_block *mnt_sb; /* Superblock of mounted FS */
struct mount *mnt_parent;
struct list_head mnt_list;
};
/* Mount a filesystem */
int sys_mount(const char *source, const char *target, const char *fstype) {
/* Find the target dentry */
struct dentry *target_dentry = path_walk(current_process->fs_root, target);
/* Identify the filesystem type and create superblock */
struct super_block *sb = NULL;
if (strcmp(fstype, "sfs") == 0) sb = sfs_mount(source);
if (strcmp(fstype, "tmpfs") == 0) sb = tmpfs_mount();
if (!sb) return -1;
/* Create mount entry */
struct mount *mnt = kmalloc(sizeof(struct mount));
mnt->mnt_mountpoint = target_dentry;
mnt->mnt_root = sb->s_root;
mnt->mnt_sb = sb;
mnt->mnt_parent = find_mount_for_dentry(target_dentry);
mount_list_add(mnt);
/* The mount point's dentry now points to the new root */
target_dentry->d_inode = sb->s_root->d_inode;
return 0;
}
31.6 Our Implementation
Our kernel's VFS layer supports three filesystem types:
- sfs: our simple on-disk filesystem (Chapter 30)
- tmpfs: a RAM-based filesystem for temporary files
- devfs: a device filesystem that exposes devices as files (/dev/uart, /dev/fb)
The VFS layer caches dentries in the dcache for fast path resolution. Recently accessed directory entries are kept in a hash table, avoiding repeated disk reads. The inode cache caches recently used inodes.
System calls like open, read, write, stat, readdir all go through VFS. The flow is:
User: open("/home/file.txt", O_RDONLY)
-> sys_open()
-> path_walk() resolves path through dcache
-> dentry->d_inode->i_fop->open()
-> sfs_open() or tmpfs_open()
-> allocate fd table entry pointing to struct file
-> return fd
31.7 Exercises
Exercise 1: Add a New FS Type
Implement a minimal ramfs filesystem that stores files in kernel memory. Register it with VFS and mount it at /mnt/ram.
Exercise 2: Path Resolution Benchmark
Measure the time to resolve a deeply nested path (/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p) with and without dcache.
31.8 Summary
VFS is the abstraction layer that allows the kernel to support multiple filesystem types with a single set of system calls. It defines four core objects: super_block, inode, dentry, and file. Each filesystem registers implementation functions for these objects. Path resolution walks the directory tree from the root, using the dcache for caching. Mount points allow attaching filesystems at arbitrary directories. Our VFS supports sfs, tmpfs, and devfs with full dentry and inode caching.