第5章:虚拟文件系统(VFS)
章节大纲
-
开篇与VFS概述 - VFS的设计哲学与目标 - VFS在Linux内核中的位置 - 统一文件模型的重要性
-
VFS核心架构 - 2.1 四大核心对象
- 超级块(superblock)
- 索引节点(inode)
- 目录项(dentry)
- 文件对象(file)
- 2.2 对象间的关系与生命周期
- 2.3 VFS操作表(operations)
-
文件系统注册与挂载 - 3.1 文件系统类型注册 - 3.2 挂载过程分析 - 3.3 命名空间与挂载传播 - 3.4 根文件系统的特殊处理
-
路径名查找与目录项缓存 - 4.1 路径解析算法 - 4.2 目录项缓存(dcache)机制 - 4.3 RCU路径遍历优化 - 4.4 符号链接处理
-
文件操作接口实现 - 5.1 系统调用到VFS的映射 - 5.2 文件描述符管理 - 5.3 读写操作路径 - 5.4 内存映射(mmap)支持
-
算法焦点 - 哈希表在dcache中的应用 - LRU缓存管理策略 - RCU在路径查找中的应用 - 负目录项(negative dentry)优化
-
历史故事 - VFS层的诞生与演进 - Al Viro的VFS重构贡献 - 从单一文件系统到多文件系统支持
-
高级话题 - overlayfs与容器存储 - FUSE用户态文件系统 - VFS层的并发优化 - 延迟分配与预读策略
本章学习目标
完成本章学习后,您将能够:
- 理解VFS的抽象层设计及其在内核中的核心作用
- 掌握VFS四大对象的数据结构和相互关系
- 分析文件系统挂载和路径查找的实现机制
- 理解dcache的优化策略和RCU的应用
- 掌握从系统调用到具体文件系统的完整调用链
- 能够实现简单的虚拟文件系统
1. 引言
虚拟文件系统(Virtual File System,VFS)是Linux内核中最优雅的设计之一。它通过在用户空间和具体文件系统实现之间建立一个抽象层,使得Linux能够支持数十种不同的文件系统,从传统的ext4、XFS,到网络文件系统NFS、SMB,再到特殊用途的proc、sysfs,所有这些都能通过统一的接口被访问。本章将深入剖析VFS的设计理念、核心数据结构、关键算法以及性能优化技术,帮助您理解Linux如何实现"一切皆文件"的设计哲学。
2. VFS核心架构
2.1 VFS的设计哲学
VFS的核心设计目标是提供一个统一的文件访问接口,隐藏底层文件系统的实现细节。这种抽象带来了三个关键优势:
- 透明性:应用程序无需关心文件存储在哪种文件系统上
- 可扩展性:新文件系统可以通过实现VFS接口轻松集成
- 一致性:所有文件操作遵循相同的语义和错误处理机制
VFS在内核中的位置可以用以下ASCII图表示:
用户空间应用程序
|
系统调用接口 (open, read, write, close...)
|
+---------+
| VFS | <--- 抽象层
+---------+
|
+----+----+----+----+
| | | | |
ext4 XFS NFS proc tmpfs <--- 具体文件系统
2.2 四大核心对象
VFS通过四个核心对象来抽象文件系统的概念:
2.2.1 超级块(struct super_block)
超级块代表一个已挂载的文件系统实例,包含文件系统的全局信息:
struct super_block {
struct list_head s_list; /* 超级块链表 */
dev_t s_dev; /* 设备标识符 */
unsigned char s_blocksize_bits;
unsigned long s_blocksize; /* 块大小 */
loff_t s_maxbytes; /* 最大文件大小 */
struct file_system_type *s_type; /* 文件系统类型 */
const struct super_operations *s_op; /* 超级块操作表 */
unsigned long s_flags; /* 挂载标志 */
unsigned long s_magic; /* 魔数 */
struct dentry *s_root; /* 根目录项 */
int s_count; /* 引用计数 */
atomic_t s_active; /* 活动引用计数 */
struct list_head s_inodes; /* inode链表 */
struct list_head s_dirty; /* 脏inode链表 */
struct block_device *s_bdev; /* 块设备 */
struct backing_dev_info *s_bdi; /* 后备设备信息 */
void *s_fs_info; /* 文件系统私有信息 */
/* 配额、安全等其他字段... */
};
超级块操作表(super_operations)定义了文件系统级别的操作:
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*dirty_inode)(struct inode *, int flags);
int (*write_inode)(struct inode *, struct writeback_control *wbc);
int (*drop_inode)(struct inode *);
void (*evict_inode)(struct inode *);
void (*put_super)(struct super_block *);
int (*sync_fs)(struct super_block *sb, int wait);
int (*statfs)(struct dentry *, struct kstatfs *);
int (*remount_fs)(struct super_block *, int *, char *);
/* 更多操作... */
};
2.2.2 索引节点(struct inode)
inode代表文件系统中的一个对象(文件、目录、设备等),包含对象的元数据:
struct inode {
umode_t i_mode; /* 文件类型和权限 */
unsigned short i_opflags;
kuid_t i_uid; /* 所有者用户ID */
kgid_t i_gid; /* 所有者组ID */
unsigned int i_flags;
const struct inode_operations *i_op; /* inode操作表 */
struct super_block *i_sb; /* 所属超级块 */
struct address_space *i_mapping; /* 页缓存映射 */
unsigned long i_ino; /* inode号 */
union {
const unsigned int i_nlink; /* 硬链接计数 */
unsigned int __i_nlink;
};
dev_t i_rdev; /* 设备号(设备文件) */
loff_t i_size; /* 文件大小 */
struct timespec64 i_atime; /* 访问时间 */
struct timespec64 i_mtime; /* 修改时间 */
struct timespec64 i_ctime; /* 状态改变时间 */
spinlock_t i_lock; /* 保护inode的自旋锁 */
unsigned short i_bytes; /* 最后一个块的字节数 */
u8 i_blkbits; /* 块大小的位数 */
atomic_t i_count; /* 引用计数 */
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
};
const struct file_operations *i_fop; /* 文件操作表 */
struct file_lock_context *i_flctx; /* 文件锁上下文 */
struct address_space i_data; /* 设备的地址空间 */
struct list_head i_devices; /* 设备链表 */
void *i_private; /* 文件系统私有数据 */
};
2.2.3 目录项(struct dentry)
目录项是路径名的一个组成部分,它将文件名与对应的inode关联起来。目录项的设计是VFS性能优化的关键:
struct dentry {
unsigned int d_flags; /* 目录项标志 */
seqcount_spinlock_t d_seq; /* 用于RCU查找的序列计数器 */
struct hlist_bl_node d_hash; /* 哈希表链接 */
struct dentry *d_parent; /* 父目录项 */
struct qstr d_name; /* 文件名 */
struct inode *d_inode; /* 关联的inode */
unsigned char d_iname[DNAME_INLINE_LEN]; /* 短文件名优化 */
struct lockref d_lockref; /* 引用计数和自旋锁 */
const struct dentry_operations *d_op; /* 目录项操作表 */
struct super_block *d_sb; /* 所属超级块 */
unsigned long d_time; /* 重验证时间 */
void *d_fsdata; /* 文件系统私有数据 */
union {
struct list_head d_lru; /* LRU链表 */
wait_queue_head_t *d_wait; /* 等待队列 */
};
struct list_head d_child; /* 父目录的子项链表 */
struct list_head d_subdirs; /* 子目录链表 */
union {
struct hlist_node d_alias; /* inode别名链表 */
struct hlist_bl_node d_in_lookup_hash; /* 查找哈希表 */
struct rcu_head d_rcu; /* RCU回收头 */
} d_u;
};
目录项有三种状态:
- 使用中(in use):d_inode指向有效inode,引用计数>0
- 未使用(unused):d_inode指向有效inode,引用计数=0,保留在缓存中
- 负目录项(negative):d_inode为NULL,表示不存在的文件,用于加速查找失败
2.2.4 文件对象(struct file)
文件对象代表进程打开的一个文件,包含文件的当前状态:
struct file {
union {
struct llist_node fu_llist; /* 文件对象链表 */
struct rcu_head fu_rcuhead; /* RCU回收头 */
} f_u;
struct path f_path; /* 包含dentry和vfsmount */
struct inode *f_inode; /* 缓存的inode指针 */
const struct file_operations *f_op; /* 文件操作表 */
spinlock_t f_lock; /* 保护文件对象的锁 */
enum rw_hint f_write_hint; /* 写入提示 */
atomic_long_t f_count; /* 引用计数 */
unsigned int f_flags; /* 文件标志(O_RDONLY等) */
fmode_t f_mode; /* 文件模式 */
struct mutex f_pos_lock; /* 保护f_pos */
loff_t f_pos; /* 文件位置 */
struct fown_struct f_owner; /* 异步I/O的所有者 */
const struct cred *f_cred; /* 打开文件时的凭证 */
struct file_ra_state f_ra; /* 预读状态 */
u64 f_version; /* 版本号 */
void *private_data; /* 私有数据 */
struct address_space *f_mapping; /* 页缓存映射 */
errseq_t f_wb_err; /* 写回错误 */
errseq_t f_sb_err; /* 超级块错误 */
};
2.3 VFS操作表
VFS通过操作表(operations structure)实现多态性。每个对象都有对应的操作表:
- super_operations:文件系统级操作(创建/删除inode、同步等)
- inode_operations:inode级操作(创建、删除、查找等)
- dentry_operations:目录项操作(比较、哈希、验证等)
- file_operations:文件操作(读、写、打开、关闭等)
- address_space_operations:地址空间操作(页面读写、内存映射等)
这种设计允许不同文件系统提供自己的实现,同时保持接口的一致性。
2.4 对象间的关系
VFS对象之间存在复杂的引用关系:
super_block
|
| s_root
↓
dentry (/)
|
| d_inode
↓
inode
|
| i_mapping
↓
address_space
进程 → file → dentry → inode
↓
f_path.mnt → vfsmount → super_block
这些关系确保了:
- 文件系统的层次结构
- 引用计数的正确管理
- 内存回收的安全性
- 并发访问的一致性
3. 文件系统注册与挂载
3.1 文件系统类型注册
Linux支持的每种文件系统都必须先注册到内核中。文件系统类型由struct file_system_type表示:
struct file_system_type {
const char *name; /* 文件系统名称 */
int fs_flags; /* 文件系统标志 */
#define FS_REQUIRES_DEV 1 /* 需要块设备 */
#define FS_BINARY_MOUNTDATA 2 /* 二进制挂载数据 */
#define FS_HAS_SUBTYPE 4 /* 有子类型 */
#define FS_USERNS_MOUNT 8 /* 可在用户命名空间挂载 */
#define FS_DISALLOW_NOTIFY_PERM 16 /* 禁用fanotify权限事件 */
struct dentry *(*mount)(struct file_system_type *, int,
const char *, void *);
void (*kill_sb)(struct super_block *);
struct module *owner; /* 所属模块 */
struct file_system_type *next; /* 链表中的下一个 */
struct hlist_head fs_supers; /* 该类型的超级块链表 */
struct lock_class_key s_lock_key;
struct lock_class_key s_umount_key;
struct lock_class_key s_vfs_rename_key;
struct lock_class_key s_writers_key[SB_FREEZE_LEVELS];
struct lock_class_key i_lock_key;
struct lock_class_key i_mutex_key;
struct lock_class_key i_mutex_dir_key;
};
文件系统注册通过register_filesystem()完成:
int register_filesystem(struct file_system_type *fs)
{
int res = 0;
struct file_system_type **p;
if (fs->next)
return -EBUSY;
write_lock(&file_systems_lock);
p = find_filesystem(fs->name, strlen(fs->name));
if (*p)
res = -EBUSY;
else
*p = fs;
write_unlock(&file_systems_lock);
return res;
}
3.2 挂载过程分析
文件系统挂载是将存储设备上的文件系统连接到目录树的过程。挂载涉及以下关键步骤:
3.2.1 挂载系统调用
SYSCALL_DEFINE5(mount, char __user *, dev_name, char __user *, dir_name,
char __user *, type, unsigned long, flags, void __user *, data)
{
return ksys_mount(dev_name, dir_name, type, flags, data);
}
3.2.2 挂载过程的核心步骤
- 路径查找:找到挂载点的dentry
- 文件系统查找:根据类型名找到file_system_type
- 超级块创建:调用文件系统的mount方法创建超级块
- 挂载连接:创建mount结构,连接到挂载树
struct mount {
struct hlist_node mnt_hash; /* 挂载哈希表 */
struct mount *mnt_parent; /* 父挂载点 */
struct dentry *mnt_mountpoint; /* 挂载点目录项 */
struct vfsmount mnt; /* VFS挂载信息 */
union {
struct rcu_head mnt_rcu;
struct llist_node mnt_llist;
};
struct mnt_pcp __percpu *mnt_pcp;
struct list_head mnt_mounts; /* 子挂载链表 */
struct list_head mnt_child; /* 父挂载的子项 */
struct list_head mnt_instance; /* 挂载实例链表 */
const char *mnt_devname; /* 设备名 */
struct list_head mnt_list;
struct list_head mnt_expire; /* 过期链表 */
struct list_head mnt_share; /* 共享挂载链表 */
struct list_head mnt_slave_list;/* 从属挂载链表 */
struct list_head mnt_slave; /* 从属链表 */
struct mount *mnt_master; /* 主挂载点 */
struct mnt_namespace *mnt_ns; /* 挂载命名空间 */
struct mountpoint *mnt_mp; /* 挂载点 */
union {
struct hlist_node mnt_mp_list;
struct hlist_node mnt_umount;
};
struct list_head mnt_umounting;
struct fsnotify_mark_connector __rcu *mnt_fsnotify_marks;
__u32 mnt_fsnotify_mask;
int mnt_id; /* 挂载ID */
int mnt_group_id; /* 组ID */
int mnt_expiry_mark; /* 过期标记 */
struct hlist_head mnt_pins;
struct hlist_head mnt_stuck_children;
};
3.3 命名空间与挂载传播
Linux支持挂载命名空间,允许不同进程看到不同的文件系统视图:
3.3.1 挂载命名空间
struct mnt_namespace {
atomic_t count; /* 引用计数 */
struct ns_common ns;
struct mount *root; /* 根挂载点 */
struct list_head list; /* 挂载点链表 */
struct user_namespace *user_ns;
struct ucounts *ucounts;
u64 seq; /* 序列号 */
wait_queue_head_t poll;
u64 event;
unsigned int mounts; /* 挂载点数量 */
unsigned int pending_mounts;
};
3.3.2 挂载传播类型
Linux支持四种挂载传播类型:
- MS_SHARED:共享挂载,挂载/卸载操作会传播到同组其他挂载点
- MS_PRIVATE:私有挂载,挂载/卸载操作不传播
- MS_SLAVE:从属挂载,单向接收主挂载点的传播
- MS_UNBINDABLE:不可绑定挂载,不能作为bind mount的源
传播机制的实现:
static int propagate_one(struct mount *m)
{
struct mount *child;
int type;
if (IS_MNT_NEW(m))
return 0;
/* 判断传播类型 */
type = CL_SLAVE;
if (IS_MNT_SHARED(m))
type |= CL_MAKE_SHARED;
/* 复制挂载 */
child = copy_tree(source, source->mnt.mnt_root, type);
if (IS_ERR(child))
return PTR_ERR(child);
/* 附加到目标 */
mnt_set_mountpoint(m, mp, child);
child->mnt_parent = m;
child->mnt_master = m->mnt_master;
/* 加入挂载哈希表 */
list_add_tail(&child->mnt_hash, tree_list);
return count_mounts(m->mnt_ns, child);
}
3.4 根文件系统的特殊处理
根文件系统是系统启动时第一个挂载的文件系统,具有特殊的初始化过程:
3.4.1 早期根文件系统(initramfs)
static int __init populate_rootfs(void)
{
char *err;
/* 解压内置的initramfs */
err = unpack_to_rootfs(__initramfs_start,
__initramfs_size);
if (err)
panic("Failed to unpack initramfs: %s\n", err);
/* 如果有外部initrd */
if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM
/* 创建/initrd.image */
int fd = ksys_open("/initrd.image",
O_WRONLY|O_CREAT, 0700);
if (fd >= 0) {
ksys_write(fd, (char *)initrd_start,
initrd_end - initrd_start);
ksys_close(fd);
}
#else
/* 直接解压 */
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start);
#endif
}
return 0;
}
rootfs_initcall(populate_rootfs);
3.4.2 真实根文件系统切换
从initramfs切换到真实根文件系统的过程:
void __init prepare_namespace(void)
{
if (root_delay) {
printk(KERN_INFO "Waiting %d sec before mounting root device...\n",
root_delay);
ssleep(root_delay);
}
/* 等待根设备出现 */
wait_for_device_probe();
/* 挂载根文件系统 */
mount_root();
/* 切换根目录 */
ksys_chdir("/root");
mount_block_root();
mount_root_generic();
/* 移动挂载点 */
ksys_mount(".", "/", NULL, MS_MOVE, NULL);
ksys_chroot(".");
}
4. 路径名查找与目录项缓存
路径名查找是VFS最频繁的操作之一,其性能直接影响系统整体性能。Linux通过目录项缓存(dcache)和RCU优化实现了高效的路径查找。
4.1 路径解析算法
路径名解析将文件路径转换为对应的dentry和inode。核心函数是path_lookupat():
static int path_lookupat(struct nameidata *nd, unsigned flags, struct path *path)
{
const char *s = path_init(nd, flags);
int err;
if (IS_ERR(s))
return PTR_ERR(s);
while (!(err = link_path_walk(s, nd)) &&
(s = lookup_last(nd)) != NULL)
;
if (!err) {
*path = nd->path;
nd->path.mnt = NULL;
nd->path.dentry = NULL;
}
terminate_walk(nd);
return err;
}
4.1.1 路径查找数据结构
struct nameidata {
struct path path; /* 当前路径 */
struct qstr last; /* 最后一个组件 */
struct path root; /* 根目录 */
struct inode *inode; /* 当前inode */
unsigned int flags; /* 查找标志 */
unsigned seq; /* 序列号(RCU) */
int last_type; /* 最后组件类型 */
unsigned depth; /* 符号链接深度 */
int total_link_count; /* 总链接数 */
struct saved {
struct path link;
struct delayed_call done;
const char *name;
unsigned seq;
} *stack, internal[EMBEDDED_LEVELS];
struct filename *name;
struct nameidata *saved;
unsigned root_seq;
int dfd;
kuid_t dir_uid;
umode_t dir_mode;
};
4.1.2 路径遍历核心算法
static int link_path_walk(const char *name, struct nameidata *nd)
{
int err;
if (IS_ERR(name))
return PTR_ERR(name);
while (*name == '/')
name++;
if (!*name)
return 0;
for(;;) {
u64 hash_len;
int type;
err = may_lookup(nd);
if (err)
return err;
hash_len = hash_name(nd->path.dentry, name);
type = LAST_NORM;
if (name[0] == '.') switch (hashlen_len(hash_len)) {
case 2:
if (name[1] == '.') {
type = LAST_DOTDOT;
nd->flags |= LOOKUP_JUMPED;
}
break;
case 1:
type = LAST_DOT;
}
if (likely(type == LAST_NORM)) {
struct dentry *parent = nd->path.dentry;
nd->flags &= ~LOOKUP_JUMPED;
if (unlikely(parent->d_flags & DCACHE_OP_HASH)) {
struct qstr this = { { .hash_len = hash_len }, .name = name };
err = parent->d_op->d_hash(parent, &this);
if (err < 0)
return err;
hash_len = this.hash_len;
name = this.name;
}
}
nd->last.hash_len = hash_len;
nd->last.name = name;
nd->last_type = type;
name += hashlen_len(hash_len);
if (!*name)
return 0;
do {
name++;
} while (unlikely(*name == '/'));
if (unlikely(!*name)) {
nd->flags |= LOOKUP_DIRECTORY | LOOKUP_FOLLOW | LOOKUP_DOTDOT;
nd->last_type = LAST_ROOT;
return 0;
}
err = walk_component(nd, WALK_MORE);
if (err < 0)
return err;
if (err) {
const char *s = get_link(nd);
if (IS_ERR(s))
return PTR_ERR(s);
if (likely(s)) {
nd->stack[nd->depth - 1].name = name;
name = s;
continue;
}
}
}
}
4.2 目录项缓存(dcache)机制
dcache是VFS性能的关键,它缓存了最近使用的目录项,避免重复的磁盘访问。
4.2.1 dcache哈希表
dcache使用全局哈希表快速查找目录项:
static struct hlist_bl_head *dentry_hashtable __read_mostly;
static inline struct hlist_bl_head *d_hash(unsigned int hash)
{
return dentry_hashtable + (hash >> (32 - d_hash_shift));
}
static inline unsigned int d_hash_name(const struct dentry *parent,
const struct qstr *name)
{
unsigned int hash = init_name_hash(parent);
unsigned int len = name->len;
const unsigned char *str = name->name;
while (len--)
hash = partial_name_hash(*str++, hash);
return end_name_hash(hash);
}
4.2.2 dcache查找
struct dentry *__d_lookup(const struct dentry *parent, const struct qstr *name)
{
unsigned int hash = d_hash_name(parent, name);
struct hlist_bl_head *b = d_hash(hash);
struct hlist_bl_node *node;
struct dentry *dentry;
rcu_read_lock();
hlist_bl_for_each_entry_rcu(dentry, node, b, d_hash) {
if (dentry->d_name.hash != hash)
continue;
spin_lock(&dentry->d_lock);
if (dentry->d_parent != parent)
goto next;
if (d_unhashed(dentry))
goto next;
if (!d_same_name(dentry, parent, name))
goto next;
dentry->d_lockref.count++;
found = dentry;
spin_unlock(&dentry->d_lock);
break;
next:
spin_unlock(&dentry->d_lock);
}
rcu_read_unlock();
return found;
}
4.3 RCU路径遍历优化
RCU(Read-Copy-Update)允许无锁的路径查找,极大提升了并发性能。
4.3.1 RCU模式的路径查找
static int lookup_fast(struct nameidata *nd,
struct path *path, struct inode **inode,
unsigned *seqp)
{
struct vfsmount *mnt = nd->path.mnt;
struct dentry *dentry, *parent = nd->path.dentry;
int status = 1;
if (nd->flags & LOOKUP_RCU) {
unsigned seq;
dentry = __d_lookup_rcu(parent, &nd->last, &seq);
if (unlikely(!dentry)) {
if (unlazy_walk(nd))
return -ECHILD;
return 0;
}
*inode = d_backing_inode(dentry);
if (unlikely(read_seqcount_retry(&dentry->d_seq, seq)))
return -ECHILD;
if (unlikely(__read_seqcount_retry(&parent->d_seq, nd->seq)))
return -ECHILD;
*seqp = seq;
status = d_revalidate(dentry, nd->flags);
if (likely(status > 0)) {
path->mnt = mnt;
path->dentry = dentry;
if (unlikely(!*inode))
return -ENOENT;
if (likely(__follow_mount_rcu(nd, path, inode, seqp)))
return 1;
}
if (unlazy_child(nd, dentry, seq))
return -ECHILD;
if (unlikely(status == -ECHILD))
status = d_revalidate(dentry, nd->flags);
} else {
dentry = __d_lookup(parent, &nd->last);
if (unlikely(!dentry))
return 0;
status = d_revalidate(dentry, nd->flags);
}
if (unlikely(status <= 0)) {
if (!status)
d_invalidate(dentry);
dput(dentry);
return status;
}
path->mnt = mnt;
path->dentry = dentry;
return 1;
}
4.3.2 序列锁保护
RCU模式使用序列锁检测并发修改:
static inline int read_seqcount_retry(const seqcount_t *s, unsigned start)
{
smp_rmb();
return unlikely(s->sequence != start);
}
static inline unsigned read_seqbegin(const seqlock_t *sl)
{
unsigned ret = READ_ONCE(sl->sequence);
if (unlikely(ret & 1)) {
cpu_relax();
goto repeat;
}
smp_rmb();
return ret;
}
4.4 符号链接处理
符号链接的处理需要特别注意避免无限循环:
static const char *get_link(struct nameidata *nd)
{
struct saved *last = nd->stack + nd->depth - 1;
struct dentry *dentry = last->link.dentry;
struct inode *inode = nd->link_inode;
int error;
const char *res;
if (unlikely(nd->total_link_count >= MAXSYMLINKS)) {
path_put(&last->link);
return ERR_PTR(-ELOOP);
}
nd->total_link_count++;
touch_atime(&last->link);
error = security_inode_follow_link(dentry, inode,
nd->flags & LOOKUP_RCU);
if (unlikely(error))
return ERR_PTR(error);
res = READ_ONCE(inode->i_link);
if (!res) {
const char * (*get)(struct dentry *, struct inode *,
struct delayed_call *);
get = inode->i_op->get_link;
if (nd->flags & LOOKUP_RCU) {
res = get(NULL, inode, &last->done);
if (res == ERR_PTR(-ECHILD)) {
if (unlazy_walk(nd))
return ERR_PTR(-ECHILD);
res = get(dentry, inode, &last->done);
}
} else {
res = get(dentry, inode, &last->done);
}
if (IS_ERR_OR_NULL(res))
return res;
}
if (*res == '/') {
error = nd_jump_root(nd);
if (unlikely(error))
return ERR_PTR(error);
}
return res;
}
4.5 负目录项优化
负目录项(negative dentry)缓存不存在的文件,避免重复的查找失败:
static void d_instantiate_negative(struct dentry *dentry, struct inode *inode)
{
BUG_ON(dentry->d_inode);
BUG_ON(!hlist_unhashed(&dentry->d_u.d_alias));
spin_lock(&dentry->d_lock);
dentry->d_inode = NULL;
dentry->d_flags |= DCACHE_NEGATIVE;
spin_unlock(&dentry->d_lock);
}
负目录项的好处:
- 减少对不存在文件的重复查找
- 加速编译等需要搜索大量路径的操作
- 提高include路径搜索效率
5. 文件操作接口实现
VFS通过系统调用为用户空间提供文件操作接口。这些系统调用最终转换为对具体文件系统的操作。
5.1 系统调用到VFS的映射
5.1.1 open系统调用
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;
return do_sys_open(AT_FDCWD, filename, flags, mode);
}
long do_sys_open(int dfd, const char __user *filename, int flags, umode_t mode)
{
struct open_how how = build_open_how(flags, mode);
return do_sys_openat2(dfd, filename, &how);
}
static long do_sys_openat2(int dfd, const char __user *filename,
struct open_how *how)
{
struct filename *tmp;
int fd;
tmp = getname(filename);
if (IS_ERR(tmp))
return PTR_ERR(tmp);
fd = get_unused_fd_flags(how->flags);
if (fd >= 0) {
struct file *f = do_filp_open(dfd, tmp, &how);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
fsnotify_open(f);
fd_install(fd, f);
}
}
putname(tmp);
return fd;
}
5.1.2 文件打开的核心过程
struct file *do_filp_open(int dfd, struct filename *pathname,
const struct open_flags *op)
{
struct nameidata nd;
int flags = op->lookup_flags;
struct file *filp;
set_nameidata(&nd, dfd, pathname);
filp = path_openat(&nd, op, flags | LOOKUP_RCU);
if (unlikely(filp == ERR_PTR(-ECHILD)))
filp = path_openat(&nd, op, flags);
if (unlikely(filp == ERR_PTR(-ESTALE)))
filp = path_openat(&nd, op, flags | LOOKUP_REVAL);
restore_nameidata();
return filp;
}
static struct file *path_openat(struct nameidata *nd,
const struct open_flags *op, unsigned flags)
{
struct file *file;
int error;
file = alloc_empty_file(op->open_flag, current_cred());
if (IS_ERR(file))
return file;
if (unlikely(file->f_flags & __O_TMPFILE)) {
error = do_tmpfile(nd, flags, op, file);
} else if (unlikely(file->f_flags & O_PATH)) {
error = do_o_path(nd, flags, file);
} else {
const char *s = path_init(nd, flags);
while (!(error = link_path_walk(s, nd)) &&
(s = open_last_lookups(nd, file, op)) != NULL)
;
if (!error)
error = do_open(nd, file, op);
terminate_walk(nd);
}
if (likely(!error)) {
if (likely(file->f_mode & FMODE_OPENED))
return file;
WARN_ON(1);
error = -EINVAL;
}
fput(file);
if (error == -EOPENSTALE) {
if (flags & LOOKUP_RCU)
error = -ECHILD;
else
error = -ESTALE;
}
return ERR_PTR(error);
}
5.2 文件描述符管理
5.2.1 文件描述符表
每个进程维护一个文件描述符表:
struct files_struct {
atomic_t count;
bool resize_in_progress;
wait_queue_head_t resize_wait;
struct fdtable __rcu *fdt;
struct fdtable fdtab;
spinlock_t file_lock ____cacheline_aligned_in_smp;
unsigned int next_fd;
unsigned long close_on_exec_init[1];
unsigned long open_fds_init[1];
unsigned long full_fds_bits_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
struct fdtable {
unsigned int max_fds;
struct file __rcu **fd;
unsigned long *close_on_exec;
unsigned long *open_fds;
unsigned long *full_fds_bits;
struct rcu_head rcu;
};
5.2.2 分配文件描述符
int get_unused_fd_flags(unsigned flags)
{
return __get_unused_fd_flags(flags, 0, rlimit(RLIMIT_NOFILE));
}
int __get_unused_fd_flags(unsigned flags, unsigned long nofile)
{
struct files_struct *files = current->files;
int fd;
spin_lock(&files->file_lock);
repeat:
fd = find_next_fd(files, files->next_fd);
if (fd >= nofile) {
fd = -EMFILE;
goto out;
}
error = expand_files(files, fd);
if (error < 0)
goto out;
if (error)
goto repeat;
__set_open_fd(fd, files->fdt);
if (flags & O_CLOEXEC)
__set_close_on_exec(fd, files->fdt);
else
__clear_close_on_exec(fd, files->fdt);
files->next_fd = fd + 1;
out:
spin_unlock(&files->file_lock);
return fd;
}
5.3 读写操作路径
5.3.1 read系统调用
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
return ksys_read(fd, buf, count);
}
ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos, *ppos = file_ppos(f.file);
if (ppos) {
pos = *ppos;
ppos = &pos;
}
ret = vfs_read(f.file, buf, count, ppos);
if (ret >= 0 && ppos)
f.file->f_pos = pos;
fdput_pos(f);
}
return ret;
}
5.3.2 VFS读操作
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
if (!(file->f_mode & FMODE_READ))
return -EBADF;
if (!(file->f_mode & FMODE_CAN_READ))
return -EINVAL;
if (unlikely(!access_ok(buf, count)))
return -EFAULT;
ret = rw_verify_area(READ, file, pos, count);
if (ret)
return ret;
if (count > MAX_RW_COUNT)
count = MAX_RW_COUNT;
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, pos);
else if (file->f_op->read_iter)
ret = new_sync_read(file, buf, count, pos);
else
ret = -EINVAL;
if (ret > 0) {
fsnotify_access(file);
add_rchar(current, ret);
}
inc_syscr(current);
return ret;
}
5.3.3 写操作实现
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
if (!(file->f_mode & FMODE_WRITE))
return -EBADF;
if (!(file->f_mode & FMODE_CAN_WRITE))
return -EINVAL;
if (unlikely(!access_ok(buf, count)))
return -EFAULT;
ret = rw_verify_area(WRITE, file, pos, count);
if (ret)
return ret;
if (count > MAX_RW_COUNT)
count = MAX_RW_COUNT;
file_start_write(file);
if (file->f_op->write)
ret = file->f_op->write(file, buf, count, pos);
else if (file->f_op->write_iter)
ret = new_sync_write(file, buf, count, pos);
else
ret = -EINVAL;
if (ret > 0) {
fsnotify_modify(file);
add_wchar(current, ret);
}
inc_syscw(current);
file_end_write(file);
return ret;
}
5.4 内存映射(mmap)支持
5.4.1 mmap系统调用
SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, off)
{
if (offset_in_page(off) != 0)
return -EINVAL;
return ksys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
}
unsigned long ksys_mmap_pgoff(unsigned long addr, unsigned long len,
unsigned long prot, unsigned long flags,
unsigned long fd, unsigned long pgoff)
{
struct file *file = NULL;
unsigned long retval;
if (!(flags & MAP_ANONYMOUS)) {
file = fget(fd);
if (!file)
return -EBADF;
if (is_file_hugepages(file)) {
len = ALIGN(len, huge_page_size(hstate_file(file)));
} else if (unlikely(flags & MAP_HUGETLB)) {
retval = -EINVAL;
goto out_fput;
}
} else if (flags & MAP_HUGETLB) {
struct ucounts *ucounts = NULL;
file = hugetlb_file_setup(HUGETLB_ANON_FILE, len,
VM_NORESERVE,
&ucounts, HUGETLB_ANONHUGE_INODE,
(flags >> MAP_HUGE_SHIFT) & MAP_HUGE_MASK);
if (IS_ERR(file))
return PTR_ERR(file);
}
retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
out_fput:
if (file)
fput(file);
return retval;
}
5.4.2 文件映射的VFS支持
static int do_mmap(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flags, unsigned long pgoff,
unsigned long *populate, struct list_head *uf)
{
struct mm_struct *mm = current->mm;
vm_flags_t vm_flags;
int pkey = 0;
*populate = 0;
if (!len)
return -EINVAL;
if ((prot & PROT_READ) && (current->personality & READ_IMPLIES_EXEC))
if (!(file && path_noexec(&file->f_path)))
prot |= PROT_EXEC;
if (!(flags & MAP_FIXED))
addr = round_hint_to_min(addr);
len = PAGE_ALIGN(len);
if (!len)
return -ENOMEM;
if ((pgoff + (len >> PAGE_SHIFT)) < pgoff)
return -EOVERFLOW;
if (mm->map_count > sysctl_max_map_count)
return -ENOMEM;
addr = get_unmapped_area(file, addr, len, pgoff, flags);
if (IS_ERR_VALUE(addr))
return addr;
if (flags & MAP_FIXED_NOREPLACE) {
if (find_vma_intersection(mm, addr, addr + len))
return -EEXIST;
}
if (prot == PROT_EXEC) {
pkey = execute_only_pkey(mm);
if (pkey < 0)
pkey = 0;
}
vm_flags = calc_vm_prot_bits(prot, pkey) | calc_vm_flag_bits(flags) |
mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
if (file) {
struct inode *inode = file_inode(file);
unsigned long flags_mask = VM_MERGEABLE | VM_MAYSHARE |
VM_DONTEXPAND | VM_SOFTDIRTY;
if (!file_mmap_ok(file, inode, pgoff, len))
return -EOVERFLOW;
flags_mask |= file->f_op->mmap_supported_flags;
switch (flags & MAP_TYPE) {
case MAP_SHARED:
flags_mask |= VM_SHARED | VM_MAYSHARE;
break;
case MAP_SHARED_VALIDATE:
flags_mask |= VM_SHARED | VM_MAYSHARE;
if (flags & ~flags_mask)
return -EOPNOTSUPP;
break;
case MAP_PRIVATE:
break;
default:
return -EINVAL;
}
if (!file->f_op->mmap)
return -ENODEV;
if (vm_flags & (VM_GROWSDOWN|VM_GROWSUP))
return -EINVAL;
}
addr = mmap_region(file, addr, len, vm_flags, pgoff, uf);
if (!IS_ERR_VALUE(addr) &&
((vm_flags & VM_LOCKED) ||
(flags & (MAP_POPULATE | MAP_NONBLOCK)) == MAP_POPULATE))
*populate = len;
return addr;
}